Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Notification Grant Status #24

Open
luke- opened this issue Jul 4, 2023 · 36 comments · May be fixed by #28
Open

Notification Grant Status #24

luke- opened this issue Jul 4, 2023 · 36 comments · May be fixed by #28
Assignees
Labels
enhancement New feature or request

Comments

@luke-
Copy link
Contributor

luke- commented Jul 4, 2023

It would be good to have a status whether the user has agreed to the notifications or not. Ideally with a possibility to request them again.

A Widget would be cool, which we can add to Notification settings page. https://community.humhub.com/notification/user
(I can help to add the Widget from FCM Module to this page)

Options/Output could be:

  • You have accepted Push Notifications [ RESEND TOKEN ]
  • You have NOT accepted Push Notificataion [ ASK PERMISSION AGAIN]

I'm not sure if it's possible or exactly how it works in conjunction with the Server Worker.

@luke- luke- added the enhancement New feature or request label Jul 4, 2023
@serh-mosk
Copy link
Contributor

@luke- Unfortunately, it is not possible to receive the confirmation status of receiving push-notifications by the user. Above all, because one user can have several devices. Secondly, push notification technology does not provide for this at the moment. Although Google FCM provides information about the delivery status of a push-notification.

When you send a push-notification using FCM, you receive feedback that contains the message delivery status. This feedback may include information such as:

  • Message ID: The unique identifier of the notification that was sent via FCM.
  • Status: Delivery status, which can be "ok" (the message was delivered successfully) or "error" (an error occurred during delivery).
  • Error Code: If the status is "error", this field contains an error code detailing the reason for the failed delivery.

@luke-
Copy link
Contributor Author

luke- commented Aug 3, 2023

@yuriimaz It is not about whether a user has received the push notifications. It is about whether the user has agreed to receive them in the current device/browser.

image

E.g. If a user clicked Block we want to show a Message: "You have blocked Notifcations. Click here to enable."
If a user clicked Allow we want to show a Message: "Push Notifications are active on this device. Click here to disable."

image

@serh-mosk
Copy link
Contributor

@luke- Sorry, I didn't quite understand and thank you for the clarification.
In this case, I will try to figure out if it is possible to use it somehow.

serh-mosk added a commit that referenced this issue Aug 15, 2023
@serh-mosk serh-mosk linked a pull request Aug 15, 2023 that will close this issue
@serh-mosk
Copy link
Contributor

@luke- Since the Notification.permission parameter can have one of three values: 'default', 'granted', and 'denied', and at the same time, a window for changing permissions is possible show only when the 'default' value is set, so I have implemented only the basis of the solution for preliminary testing and coordination. Tested in Chrome, Firefox and Safari. Works well.

It remains to add:

  • Issuance of a block with a Push Notification permission message.
  • Translation of sentences about Push Notification permissions.
  • Maybe something else needs to be changed after the review Enh #24: Notification Grant Status #28.

Let me know what you think about these changes and what should be changed.

@luke-
Copy link
Contributor Author

luke- commented Aug 22, 2023

@yuriimaz This looks really good and promising.

I would prefer:

The existing widget stub https://github.com/humhub/fcm-push/blob/master/widgets/PushNotificationInfoWidget.php should have a view about all different cases (Not Supported, Denied, Granted, Default).

  • For Granted: You have agreed to receive Push Notification. [Undo]
  • If Denied: You have rejected Push Notification. (Help text to reset).
  • Default: Click here to enable Push Notification [Enable].
  • Unsupported: Your current browser does not support push notifications.

Per Javascript we can hide/show relevant messages.


I can help you then how we can show the widget from the module (FcmPush) in the Core on the Notifications page.

@luke-
Copy link
Contributor Author

luke- commented May 10, 2024

Related: #17

@ArchBlood
Copy link
Contributor

ArchBlood commented Jul 27, 2024

@luke- @serh-mosk maybe this is what you're looking for but displayed under the Notification Overview?

Screenshot_1
Screenshot_2
Screenshot_3

Modified #28

/**
 * @fileoverview HumHub Firebase Module
 * This module handles Firebase Cloud Messaging (FCM) integration for HumHub.
 * It manages notification permissions, token handling, and message reception.
 * @module firebase
 */

humhub.module('firebase', function (module, require, $) {
    let messaging;

    /**
     * Initializes the Firebase module.
     * Sets up the Firebase app if not already initialized and configures message handling.
     */
    const init = function () {
        if (!firebase.apps.length) {
            firebase.initializeApp({messagingSenderId: this.senderId()});
            this.messaging = firebase.messaging();

            this.messaging.onMessage(function (payload) {
                module.log.info("Received FCM Push Notification", payload);
            });

            // Callback fired if Instance ID token is updated.
            this.messaging.onTokenRefresh(function () {
                this.messaging.getToken().then(function (refreshedToken) {
                    this.deleteTokenLocalStore();
                    this.sendTokenToServer(refreshedToken);
                }).catch(function (err) {
                    console.log('Unable to retrieve refreshed token ', err);
                });
            });
        }
    };

    /**
     * Generates the appropriate content for notification permission status.
     * @returns {string} HTML content describing the current notification permission status.
     */
    const getNotificationPermissionContent = function () {
        if (!("Notification" in window)) {
            return 'Not Supported: This browser does not support notifications.';
        }
        console.log('Notification.permission:', Notification.permission);
        switch (Notification.permission) {
            case "granted":
                return 'Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site.';
            case "denied":
                return 'Denied: You have blocked Push Notifications.<br>You can enable it in browser settings for this site.';
            default:
                return 'Default: Push Notifications are not yet enabled.<br><a href="#" id="enablePushBtn"><i class="fa fa-unlock"></i> Click here to enable</a>';
        }
    }

    /**
     * Displays the notification permission request window.
     * Handles the permission request process and updates the UI accordingly.
     */
    const showNotificationPermissionWindow = function () {
        function handlePermission(permission) {
            addPushNotificationPermissionsInfo(permission, true);
        }

        if (!("Notification" in window)) {
            console.log("This browser does not support notifications.");
            handlePermission("not-supported");
        } else {
            Notification.requestPermission().then((permission) => {
                handlePermission(permission);
            });
        }
    }

    /**
     * Adds or updates the push notification permissions information in the UI.
     * @param {string} permission - The current notification permission status.
     * @param {boolean} rewrite - Whether to rewrite existing content or add new content.
     */
    const addPushNotificationPermissionsInfo = function (permission, rewrite = false) {
        if (rewrite) {
            const contentContainer = document.getElementById('notificationPermissions');
            contentContainer.innerHTML = getNotificationPermissionContent()
        } else {
            const content = '<div class="panel panel-default panel-pn-permissions"><div class="panel-body" id="notificationPermissions">' + getNotificationPermissionContent() + '</div></div>';
            $('.layout-sidebar-container').prepend($(content));
        }

        $('#enablePushBtn').on('click', showNotificationPermissionWindow);
    }

    /**
     * Handles tasks after service worker registration.
     * Requests notification permission and manages token retrieval and storage.
     * @param {ServiceWorkerRegistration} registration - The service worker registration object.
     */
    const afterServiceWorkerRegistration = function (registration) {
        const that = this;

        this.messaging.useServiceWorker(registration);

        this.messaging.requestPermission().then(function () {
            addPushNotificationPermissionsInfo('granted');

            that.messaging.getToken().then(function (currentToken) {
                if (currentToken) {
                    that.sendTokenToServer(currentToken);
                } else {
                    module.log.info('No Instance ID token available. Request permission to generate one.');
                    that.deleteTokenLocalStore();
                }
            }).catch(function (err) {
                module.log.error('An error occurred while retrieving token. ', err);
                that.deleteTokenLocalStore();
            });
        }).catch(function (err) {
            module.log.info('Could not get Push Notification permission!', err);
            addPushNotificationPermissionsInfo(Notification.permission);
        });
    };

    /**
     * Sends the FCM token to the server.
     * @param {string} token - The FCM token to be sent.
     */
    const sendTokenToServer = function (token) {
        const that = this;
        if (!that.isTokenSentToServer(token)) {
            module.log.info("Send FCM Push Token to Server");
            $.ajax({
                method: "POST",
                url: that.tokenUpdateUrl(),
                data: {token: token},
                success: function (data) {
                    that.setTokenLocalStore(token);
                }
            });
        } else {
            //console.log('Token already sent to server so won\'t send it again unless it changes');
        }
    };

    /**
     * Checks if the token has been sent to the server.
     * @param {string} token - The token to check.
     * @returns {boolean} True if the token has been sent to the server, false otherwise.
     */
    const isTokenSentToServer = function (token) {
        return (this.getTokenLocalStore() === token);
    };

    /**
     * Deletes the token from local storage.
     */
    const deleteTokenLocalStore = function () {
        window.localStorage.removeItem('fcmPushToken_' + this.senderId())
    };

    /**
     * Stores the token in local storage with an expiry time.
     * @param {string} token - The token to store.
     */
    const setTokenLocalStore = function (token) {
        const item = {
            value: token,
            expiry: (Date.now() / 1000) + (24 * 60 * 60),
        };
        window.localStorage.setItem('fcmPushToken_' + this.senderId(), JSON.stringify(item))
    };

    /**
     * Retrieves the token from local storage.
     * @returns {string|null} The stored token if valid, null otherwise.
     */
    const getTokenLocalStore = function () {
        const itemStr = window.localStorage.getItem('fcmPushToken_' + this.senderId())

        // if the item doesn't exist, return null
        if (!itemStr) {
            return null
        }
        const item = JSON.parse(itemStr)
        const now = (Date.now() / 1000)
        if (now > item.expiry) {
            this.deleteTokenLocalStore();
            return null;
        }
        return item.value;
    };

    /**
     * Returns the URL for updating the token on the server.
     * @returns {string} The token update URL.
     */
    const tokenUpdateUrl = function () {
        return module.config.tokenUpdateUrl;
    };

    /**
     * Returns the sender ID for FCM.
     * @returns {string} The sender ID.
     */
    const senderId = function () {
        return module.config.senderId;
    };

    module.export({
        init: init,
        isTokenSentToServer: isTokenSentToServer,
        sendTokenToServer: sendTokenToServer,
        afterServiceWorkerRegistration: afterServiceWorkerRegistration,
        senderId: senderId,
        tokenUpdateUrl: tokenUpdateUrl,
        setTokenLocalStore: setTokenLocalStore,
        getTokenLocalStore: getTokenLocalStore,
        deleteTokenLocalStore: deleteTokenLocalStore,
    });
});

/**
 * Global function to handle service worker registration for the Firebase module.
 * @param {ServiceWorkerRegistration} registration - The service worker registration object.
 */
function afterServiceWorkerRegistration(registration) {
    humhub.modules.firebase.afterServiceWorkerRegistration(registration);
}

@luke-
Copy link
Contributor Author

luke- commented Jul 30, 2024

@ArchBlood Thanks, looks goods interesting.

Is it also possible to re-trigger the approval request manually?

Would you like to provide a PR for this?

@ArchBlood
Copy link
Contributor

@ArchBlood Thanks, looks goods interesting.

Is it also possible to re-trigger the approval request manually?

Would you like to provide a PR for this?

Yes, if the settings are reset for some reason it is very possible to re-trigger the approval request manually. I've provided a screenshot for each status that I could produce on my end of things, other than one for no support as I do not know of any browsers that are installable without support.

@ArchBlood
Copy link
Contributor

@luke- see #44

@luke-
Copy link
Contributor Author

luke- commented Jul 30, 2024

@ArchBlood Awesome thanks! Can you provide the screenshots?

@yurabakhtin Can you please test this behavior?

@ArchBlood
Copy link
Contributor

@ArchBlood Awesome thanks! Can you provide the screenshots?

@yurabakhtin Can you please test this behavior?

Screenshots are attached in the original comment #24 (comment)

@yurabakhtin yurabakhtin self-assigned this Jul 31, 2024
@yurabakhtin
Copy link
Contributor

@yurabakhtin Can you please test this behavior?

@luke- I don't see the widget with green border on my test server as displayed on the screenshots #24 (comment), maybe I should configure the module as described here https://marketplace.humhub.com/module/fcm-push/installation ?

I think all messages like Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site. should be translatable from php side.

@ArchBlood
Copy link
Contributor

@yurabakhtin Can you please test this behavior?

@luke- I don't see the widget with green border on my test server as displayed on the screenshots #24 (comment), maybe I should configure the module as described here https://marketplace.humhub.com/module/fcm-push/installation ?

I think all messages like Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site. should be translatable from php side.

The border was to highlight the widget nothing more.

@yurabakhtin
Copy link
Contributor

@luke- I think it works as expected, you can see my tests on Windows Chrome browser:

push-notifications


I think all messages like Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site. should be translatable from php side.

@ArchBlood It can be implemented with code:

class FcmPushAsset extends AssetBundle
{
    ...
    public static function register($view)
    {
        ...
            Yii::$app->view->registerJsConfig('firebase', [
                'text' => [
                    'status.granted' => Yii::t('FcmPushModule.base', 'Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site.'),
                ],
            ]);
         ...
    }
}

and replace the JS string with code module.text('status.granted');

@ArchBlood
Copy link
Contributor

@luke- I think it works as expected, you can see my tests on Windows Chrome browser:

push-notifications

I think all messages like Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site. should be translatable from php side.

@ArchBlood It can be implemented with code:

class FcmPushAsset extends AssetBundle
{
    ...
    public static function register($view)
    {
        ...
            Yii::$app->view->registerJsConfig('firebase', [
                'text' => [
                    'status.granted' => Yii::t('FcmPushModule.base', 'Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site.'),
                ],
            ]);
         ...
    }
}

and replace the JS string with code module.text('status.granted');

Done in da92e33 & 9345b2b

@yurabakhtin
Copy link
Contributor

Done in da92e33 & 9345b2b

@ArchBlood Thanks, please review the requested changes.

@luke-
Copy link
Contributor Author

luke- commented Aug 2, 2024

After we want to display the status in the notification settings. We can implement the following:

  • In Core: Add BaseStack Widget "NotificationSettingsInfo"
  • in FCM Push: Add Info Widget to this BaseStack?
  • image

Or any other idea?

@ArchBlood
Copy link
Contributor

After we want to display the status in the notification settings. We can implement the following:

  • In Core: Add BaseStack Widget "NotificationSettingsInfo"
  • in FCM Push: Add Info Widget to this BaseStack?
  • image

Or any other idea?

We could convert the existing widget to do this or is there any specific use that it currently uses?

https://github.com/humhub/fcm-push/blob/enh/24-notification-grant-status/widgets/PushNotificationInfoWidget.php
https://github.com/humhub/fcm-push/blob/enh/24-notification-grant-status/widgets/views/push-notification-info.php

@luke-
Copy link
Contributor Author

luke- commented Aug 2, 2024

@ArchBlood Yes, we can use it for that!

@luke-
Copy link
Contributor Author

luke- commented Aug 2, 2024

@ArchBlood Do you want to implement this? Or maybe something for @yurabakhtin ?

@ArchBlood
Copy link
Contributor

@ArchBlood Do you want to implement this? Or maybe something for @yurabakhtin ?

I don't mind doing this, is there s specific design/look that would be required or anything to make it more noticeable?

@luke-
Copy link
Contributor Author

luke- commented Aug 2, 2024

I think @Eladnarlea will look over it again. At the moment, just a line without a lot of styling is enough.

@ArchBlood
Copy link
Contributor

ArchBlood commented Aug 2, 2024

I think @Eladnarlea will look over it again. At the moment, just a line without a lot of styling is enough.

@luke- I wasn't able to implement within /notification/user without core editing which isn't normally recommended, another method would be creating another menu link like this which I did get working without issue;

Red: Haven't found a good method of implementing.
Green: Works perfectly.

Screenshot_1

With core editing;
Screenshot_2

If you want a better solution then I'm open for other options

@luke-
Copy link
Contributor Author

luke- commented Aug 2, 2024

@ArchBlood Yes, in this case I would make a change in the core develop branch:

  • Core: We add an empty widget (BaseStack like sidebar) at this position in the core view (Notification Settings)
  • FCM module: We add the status widget to the empty core widget

If you want, we can also take over the part here?

@ArchBlood
Copy link
Contributor

ArchBlood commented Aug 2, 2024

@ArchBlood Yes, in this case I would make a change in the core develop branch:

  • Core: We add an empty widget (BaseStack like sidebar) at this position in the core view (Notification Settings)
  • FCM module: We add the status widget to the empty core widget

If you want, we can also take over the part here?

Sounds fine with me, I'd also like to note that the script will also have to be updated in this case as well, as currently it creates a widget on the dashboard which would have to be refactored in this case as well.

The following are the updated widget and widget view that I currently use;

PushNotificationInfoWidget

Details
<?php

namespace humhub\modules\fcmPush\widgets;

use Yii;
use yii\helpers\Url;
use humhub\components\Widget;
use humhub\modules\fcmPush\Module;
use humhub\modules\fcmPush\services\DriverService;

class PushNotificationInfoWidget extends Widget
{
    /**
     * @inheritdoc
     */
    public function run()
    {
        /** @var Module $module */
        $module = Yii::$app->getModule('fcm-push');
        $pushDriver = (new DriverService($module->getConfigureForm()))->getWebDriver();

        if ($pushDriver === null) {
            return '';
        }

        return $this->render('push-notification-info', [
            'tokenUpdateUrl' => Url::to(['/fcm-push/token/update']),
            'senderId' => $pushDriver->getSenderId(),
        ]);
    }
}

View

Details
<?php

use yii\helpers\Html;
use humhub\widgets\Button;

/* @var $this \humhub\modules\ui\view\components\View */
/* @var $tokenUpdateUrl string */
/* @var $senderId string */

// Prepare the status texts and JSON encode them for use in JavaScript
$statusTexts = [
    'granted' => Yii::t('FcmPushModule.base', 'Granted: Push Notifications are active on this browser.<br>You can disable it in browser settings for this site.'),
    'denied' => Yii::t('FcmPushModule.base', 'Denied: You have blocked Push Notifications.<br>You can enable it in browser settings for this site.'),
    'default' => Yii::t('FcmPushModule.base', 'Default: Push Notifications are not yet enabled.') . '<br>' .
        Button::primary(Yii::t('FcmPushModule.base', 'Click here to enable'))
            ->icon('fa-unlock')
            ->id('enablePushBtn')
            ->sm()
            ->loader(false),
    'not-supported' => Yii::t('FcmPushModule.base', 'Not Supported: This browser does not support notifications.'),
];
$statusTextsJson = json_encode($statusTexts);

$this->registerJsConfig('firebase', [
    'tokenUpdateUrl' => $tokenUpdateUrl,
    'senderId' => $senderId,
]);

$js = <<<JS
    var statusTexts = {$statusTextsJson};

    function updatePushStatus() {
        var status = 'not-supported';
        if ("Notification" in window) {
            status = Notification.permission;
        }
        $('#pushNotificationStatus').html(statusTexts[status]);
    }

    $(document).ready(function() {
        updatePushStatus();

        $(document).on('click', '#enablePushBtn', function() {
            Notification.requestPermission().then(function(permission) {
                updatePushStatus();
            });
        });
    });
JS;

$this->registerJs($js);
?>

<div id="push-notification-info" class="panel panel-default">
    <div class="panel-heading">
        <?= Yii::t('FcmPushModule.base', 'Push Notifications') ?>
    </div>
    <div class="panel-body" id="pushNotificationStatus">
        <!-- Status will be populated by JavaScript -->
    </div>
</div>

@luke-
Copy link
Contributor Author

luke- commented Aug 2, 2024

@ArchBlood should @yurabakhtin take a look into this? Our do you have time on work on this?

@ArchBlood
Copy link
Contributor

@ArchBlood should @yurabakhtin take a look into this? Our do you have time on work on this?

I've provide the current widget and widget view that I currently use (see #24 (comment)), I don't have much time to rework the JavaScript sorry.

@ArchBlood
Copy link
Contributor

@luke- I believe that everything is in order and complies with everything requested, all that is needed is for review of both P/Rs.

@yurabakhtin
Copy link
Contributor

@luke- Some help is required from me here?

I just see you requested "Core: We add an empty widget (BaseStack like sidebar) at this position in the core view (Notification Settings)" and it seems @ArchBlood started it in the PR humhub/humhub#7155 with new core widget NotificationSettingsInfo.

I think we don't need the new widget completely, because there we already the core widget UserInfoWidget which can be used for the second your point "FCM module: We add the status widget to the empty core widget" from the FCM Push module side like this:

config.php:

[UserInfoWidget::class, UserInfoWidget::EVENT_INIT, [Events::class, 'onInitUserInfoWidget']],

Events.php:

public static function onInitUserInfoWidget($event)
{
    /* @var UserInfoWidget $widget */
    $widget = $event->sender;

    $widget->addWidget(PushNotificationInfoWidget::class);
}

I.e. it can be done currently without touching the core, because the core widget UserInfoWidget is available since 1.14 version.

@ArchBlood
Copy link
Contributor

@luke- Some help is required from me here?

I just see you requested "Core: We add an empty widget (BaseStack like sidebar) at this position in the core view (Notification Settings)" and it seems @ArchBlood started it in the PR humhub/humhub#7155 with new core widget NotificationSettingsInfo.

I think we don't need the new widget completely, because there we already the core widget UserInfoWidget which can be used for the second your point "FCM module: We add the status widget to the empty core widget" from the FCM Push module side like this:

config.php:

[UserInfoWidget::class, UserInfoWidget::EVENT_INIT, [Events::class, 'onInitUserInfoWidget']],

Events.php:

public static function onInitUserInfoWidget($event)
{
    /* @var UserInfoWidget $widget */
    $widget = $event->sender;

    $widget->addWidget(PushNotificationInfoWidget::class);
}

I.e. it can be done currently without touching the core, because the core widget UserInfoWidget is available since 1.14 version.

I can agree to this method, if it is preferred @luke-?

@luke-
Copy link
Contributor Author

luke- commented Aug 9, 2024

@ArchBlood Please use the widgets suggested by @yurabakhtin. I've already added them for this case, few versions ago.

@luke-
Copy link
Contributor Author

luke- commented Aug 9, 2024

@ArchBlood Also let us know, if yura should finish this task!

@ArchBlood
Copy link
Contributor

@luke- done in 35a16cc and 15f4657, although I'd like to note that now the module no longer works because the legacy API has been completely removed and should be completely migrated.

@luke-
Copy link
Contributor Author

luke- commented Aug 9, 2024

@ArchBlood Thanks, can you please rebase the PR or update the base branch. The PR contains many changes which makes it hard to review: https://github.com/humhub/fcm-push/pull/44/files#diff-2bfab3db5cc1baf4c6d3ff6b19901926e3bdf4411ec685dac973e5fcff1c723b

Let's create a new issue for that. #46

@ArchBlood
Copy link
Contributor

@ArchBlood Thanks, can you please rebase the PR or update the base branch. The PR contains many changes which makes it hard to review: https://github.com/humhub/fcm-push/pull/44/files#diff-2bfab3db5cc1baf4c6d3ff6b19901926e3bdf4411ec685dac973e5fcff1c723b

Let's create a new issue for that. #46

Done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants