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

Pusher Beams Enquiry #369

Open
Ifriqiya opened this issue Jan 19, 2023 · 23 comments
Open

Pusher Beams Enquiry #369

Ifriqiya opened this issue Jan 19, 2023 · 23 comments

Comments

@Ifriqiya
Copy link

I need help with a web push notifications setup that works except that it only relays the message body.

I have this job that sends a push:

public function handle() 
    {
        $body = 'You have a new chat message from '.$this->message->user->first_name.'!';
        $id = $this->message->user->id;
        $beamsClient = new PushNotifications(
            [
                'instanceId' => config('services.beams_instance_id'),
                'secretKey' => config('services.beams_secret_key'),
            ]
        );

        $beamsClient->publishToUsers([strval($this->message->recipient->id)], 
            [
                'web' => [
                    'notification' => [
                        'body' => $body, 
                        'icon' => config('app.url').'/img/favicon-16x16.png', 
                        'deep_link' => config('app.url').'/chat/messages/'.$this->message->recipient->id
                    ],
                    'data' => ['id' => $id]
                ]                
            ]
        );
    }

I get the push but only the message body is sent. I get this dev console:

{notification: {…}, data: {…}, topic: 'cbbb7d3ffcc5fcf960dd3c6c12e2c63f'}
data
: 
{pusher: {…}}
notification
: 
body
: 
"You have a new chat message from Augustina!"
[[Prototype]]
: 
Object
topic
: 
"cbbb7d3ffcc5fcf960dd3c6c12e2c63f"
[[Prototype]]
: 
Object

How do I resolve this?

@benw-pusher
Copy link

Are you looking to add a title to your notification? Or are you having issues with the icon/deep_link?

@Ifriqiya
Copy link
Author

Hi @benw-pusher, I'm having issues with the icon/deep_link.

@benw-pusher
Copy link

These options should be handled by the client, what library are you using and how does the notification display there?

@Ifriqiya
Copy link
Author

I'm using "@pusher/push-notifications-web". The notification shows up on the browser.

@benjamin-tang-pusher
Copy link
Contributor

Hi, can you hard code your deeplink to a test value like "https://www.google.com". If you then click the notification, does it take you there (or show in your dev console)?

@Ifriqiya
Copy link
Author

Ifriqiya commented Apr 5, 2023

Hi, thanks for your response. I tried your suggestion but I still don't get navigated to the hard coded url. I get the correct url in dev console in both cases-with $this->url and the hard coded value. It just doesn't navigate there.

@benjamin-tang-pusher
Copy link
Contributor

There may be an issue with the receiving browser. Which browser are you using?

@Ifriqiya
Copy link
Author

I primarily use Brave, but I have tried with Edge and Mozilla.

@benjamin-tang-pusher
Copy link
Contributor

Could you try another machine, just in the off chance there is machine issue stopping the notification opening the link in the browser?

@benw-pusher
Copy link

Could you share the code used in the client for subscribing to the interest and receiving the notification?

@Ifriqiya
Copy link
Author

Ifriqiya commented Jun 26, 2023

This is it:

//
import * as PusherPushNotifications from "@pusher/push-notifications-web"
//
let globalBeamsClient
//
onMounted(() => {
//

  window.navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
    const userId = user.value.id
    const currentUserId = userId.toString()
    const beamsClient = new PusherPushNotifications.Client({
      instanceId: import.meta.env.VITE_PUSHER_BEAMS_INSTANCE_ID,
      serviceWorkerRegistration: serviceWorkerRegistration
    })
    globalBeamsClient = beamsClient
    const beamsTokenProvider = new PusherPushNotifications.TokenProvider({
      url: route("pusher.beams-auth")
    })
    beamsClient
      .start()
      .then((beamsClient) => beamsClient.setUserId(currentUserId, beamsTokenProvider))
      .catch(console.error)
  })

service worker code:

// This is the "Offline copy of assets" service worker
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js')

const {BackgroundSyncPlugin} =  workbox.backgroundSync
const {registerRoute} =  workbox.routing
const {StaleWhileRevalidate} =  workbox.strategies
const {ExpirationPlugin} =  workbox.expiration

const CACHE = "offlineAssets"
const QUEUE_NAME = "bgSyncQueue"

self.__WB_DISABLE_DEV_LOGS = true

self.addEventListener('install', () => self.skipWaiting())

self.addEventListener('activate', () => self.clients.claim())

const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
  maxRetentionTime: 24 * 60 // Retry for max of 24 Hours (specified in minutes)
})

const expPlugin = new ExpirationPlugin({
  maxEntries: 5,
  maxAgeSeconds: 1 * 24 * 60 * 60,
  purgeOnQuotaError: true,
  matchOptions: {
    ignoreVary: true,
  }
})

registerRoute(
  new RegExp('/*'),
  new StaleWhileRevalidate({
    cacheName: CACHE,
    plugins: [
      bgSyncPlugin,
      expPlugin
    ]
  })
)

self.addEventListener('push', (e) => {
  if (!(self.Notification && self.Notification.permission === 'granted')) return
  if (!e.data) return
   
  const msg = e.data.json()

  e.waitUntil(clients.matchAll()
    .then((clients) => {
    // console.log(msg, clients)
    clients.forEach((client) => {
      const url = client.url
      const id = url.slice(url.lastIndexOf('/') + 1)
      const clientId = client.id
      if (!client.url.includes(`chat/messages/${id}`)) { //send only if not on chat url and if clientId matches client id of sender.
        self.registration.showNotification('SmartWealth Push Notification', {
          body: msg.notification.body,
          icon: msg.notification.icon,
          actions: msg.notification.actions,
          deep_link: msg.notification.deep_link
        })
      }
    })
  }))
})

self.addEventListener('notificationclick', () => {}) //to do

@benw-pusher
Copy link

Your service worker doesn't appear to have the Beams service worker defined - you need to add this as per https://pusher.com/docs/beams/guides/existing-service-worker/#import-the-beams-service-worker-in-your-existing-service-worker-file.

I can also see you have provided some custom code for handling the receipt and display of the notification - however this differed from the documented approach for this - https://pusher.com/docs/beams/guides/handle-incoming-notifications/web/#overriding-default-sdk-behavior.

I am able to access the deep_link as expected with the following payload:

web: {
            notification: {
                title: 'hi!!',
                body: 'hi. 😃👍🎉!',
                deep_link: 'https://bbc.co.uk',
                icon: 'http://localhost:9000/img/favicon-16x16.png'
                
            }
        },

and the following service worker:

importScripts("https://js.pusher.com/beams/service-worker.js");

PusherPushNotifications.onNotificationReceived = ({
    payload,
    pushEvent,
    handleNotification,
}) => {

  const promiseChain = isClientFocused().then((clientIsFocused) => {
    if (clientIsFocused) {
      self.console.log(payload);
      console.log("Don't need to show a notification.");
      return;
    }
  
    // Client isn't focused, we need to show a notification.
    self.console.log(payload);
    console.log("had to show a notification.");
    return self.registration.showNotification('Had to show a notification.');
  });
  
  pushEvent.waitUntil(promiseChain);
  
           /*pushEvent.waitUntil(
           self.registration.showNotification(payload.notification.title, {
              body: payload.notification.body,
              icon: payload.notification.icon,
              data: payload.data, 
            }) 
          )};*/
          };
          


          function isClientFocused() {
            return clients
              .matchAll({
                type: 'window',
                includeUncontrolled: true,
              })
              .then((windowClients) => {
                let clientIsFocused = false;
          
                for (let i = 0; i < windowClients.length; i++) {
                  const windowClient = windowClients[i];
                  if (windowClient.focused) {
                    clientIsFocused = true;
                    break;
                  }
                }
          
                return clientIsFocused;
              });
          }

Here I am using the payload parameter in the PusherPushNotification.onNotificationReceived handler: if you were to move to using the onNotificationRecived handler with this payload instead of the addEventListener and const msg = e.data.json() you should see some results.

@Ifriqiya
Copy link
Author

OK, thanks. I'll try it out.

@Ifriqiya
Copy link
Author

Hi @benw-pusher I wanted to test out your suggestion but I can't get past beams authentication which previously worked. What could be wrong here:

//this my method
    public function beamsAuth(Request $request, User $user)
    {
        $userID = strval($user->id);
        $userIDInQueryParam = $request->input('user_id');

        $beamsClient = new PushNotifications(
            [
                'instanceId' => config('services.pusher.beams_instance_id'),
                'secretKey' => config('services.pusher.beams_secret_key'),
            ]
        );

        if ($userID != $userIDInQueryParam) {
            return response('Inconsistent request', 401);
        } else {
            $beamsToken = $beamsClient->generateToken($userID);

            return response()->json($beamsToken);
        }
    }
//my route
    Route::get('pusher/beams-auth', [NotificationsController::class, 'beamsAuth'])->name('pusher.beams-auth');
//section of front-end where I attempt authorisation 
  window.navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
    const userId = user.value.id
    // const currentUserId = userId.toString()
    const beamsClient = new PusherPushNotifications.Client({
      instanceId: import.meta.env.VITE_PUSHER_BEAMS_INSTANCE_ID,
      serviceWorkerRegistration: serviceWorkerRegistration
    })
    globalBeamsClient = beamsClient
    const beamsTokenProvider = new PusherPushNotifications.TokenProvider({
      url: route("pusher.beams-auth"),
      queryParams: {
        user_id: userId,
      },
    })
    beamsClient
      .start()
      .then((beamsClient) => beamsClient.setUserId(userId, beamsTokenProvider))
      .catch(console.error)
  })
//console errors
@pusher_push-notifications-web.js?v=2e7db2b7:604     GET http://127.0.0.1:8000/pusher/beams-auth?user_id=3446ydfm66 
 401 (Unauthorized)
Error: Unexpected status code 401: Cannot parse error response
    at _callee2$ (@pusher_push-notifications-web.js?v=2e7db2b7:670:21)
    at tryCatch (@pusher_push-notifications-web.js?v=2e7db2b7:48:42)
    at Generator.invoke [as _invoke] (@pusher_push-notifications-web.js?v=2e7db2b7:203:24)
    at prototype.<computed> [as throw] (@pusher_push-notifications-web.js?v=2e7db2b7:80:23)
    at asyncGeneratorStep (@pusher_push-notifications-web.js?v=2e7db2b7:509:24)
    at _throw (@pusher_push-notifications-web.js?v=2e7db2b7:530:9)

//beams storage with token but null user_id
<html>
<body>
<!--StartFragment-->

"xxx" | {instance_id: 'xxx', device_id: 'webxxx', token: 'eyJlbmRwb2ludCI6Imh0dHBzOi8vZmNtLmdvb2dsZWFwaXMuY2…ZIiwiYXV0aCI6Imp3OGJ0eEFnWTVoSXlBdXpUUFBhLWcifX0=', user_id: null, last_seen_sdk_version: '1.1.0', …}device_id: "web-xxx"instance_id: "xxx"last_seen_sdk_version: "1.1.0"last_seen_user_agent: "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Mobile Safari/537.36"token: "eyJlbmRwb2ludCI6Imh0dHBzOi8vZmNtLmdvb2dsZWFwaXMuY29tL2ZjbS9zZW5kL2RPM2RpZGlLTWp3OkFQQTkxYkZmMHRHWjFzRTI4Y1BHR3J4ZnA0c3EtSFdRZDRPX1RDOVkxdzNSYWxzXzVwQTZTbno4VTF1dzhaQkwzOWR0V2VOcHNqZ242bmNxdk1oMGEzeE9PTG1VXzFSanFlc2JsYkVOQUNRM3FrdWpUamo5WV84TnNQZkUxWllTMEtqaU84YWd4SmNGIiwiZXhwaXJhdGlvblRpbWUiOm51bGwsImtleXMiOnsicDI1NmRoIjoiQkNEekpQZ3dTbVVnd3ZyYnJudW9nQXlRT0xsZ2YteUxtMUdvN2Zxd0t4bFdRZVEtZEo5WEdwWlItRF9Jd2hOcC1Bd0tkXy1iVTVQMnU2YlRQWl9hTnlZIiwiYXV0aCI6Imp3OGJ0eEFnWTVoSXlBdXpUUFBhLWcifX0="user_id: null
-- | --


<!--EndFragment-->
</body>
</html>

What could be the issue?

@benw-pusher
Copy link

The error suggests your auth endpoint is returning 401. Have you verified that the auth is passing the check if ($userID != $userIDInQueryParam) {?

@Ifriqiya
Copy link
Author

Hi @benw-pusher, thanks for your time on this matter. Implementing your approach produced this error when I click on the notification
service-worker.js:1421 Uncaught TypeError: Cannot read properties of null (reading 'pusher') at service-worker.js:1421:36 at this point which seems to need a 'pusher' variable:

self.addEventListener('notificationclick', function (e) {
  var pusher = e.notification.data.pusher;
  var isPusherNotification = pusher !== undefined;

  if (isPusherNotification) {
    // Report analytics event, best effort
    self.PusherPushNotifications.reportEvent({
      eventType: 'open',
      pusherMetadata: pusher.pusherMetadata
    });

    if (pusher.customerPayload.notification.deep_link) {
      e.waitUntil(clients.openWindow(pusher.customerPayload.notification.deep_link));
    }

    e.notification.close();
  }
});

It appears that the 'optons' variable requirement was not provided:

           case 8:
              title = payloadFromCallback.notification.title || '';
              body = payloadFromCallback.notification.body || '';
              icon = payloadFromCallback.notification.icon;
              options = {
                body: body,
                icon: icon,
                data: {
                  pusher: {
                    customerPayload: payloadFromCallback,
                    pusherMetadata: pusherMetadata
                  }
                }
              };

This is my current SW code:

// This is the "Offline copy of assets" service worker
importScripts('https://storage.googleapis.com/workbox-cdn/releases/6.5.4/workbox-sw.js')
importScripts("https://js.pusher.com/beams/service-worker.js")

const {BackgroundSyncPlugin} =  workbox.backgroundSync
const {registerRoute} =  workbox.routing
const {StaleWhileRevalidate} =  workbox.strategies
const {ExpirationPlugin} =  workbox.expiration

const CACHE = "offlineAssets"
const QUEUE_NAME = "bgSyncQueue"

self.__WB_DISABLE_DEV_LOGS = true

self.addEventListener('install', () => self.skipWaiting())

self.addEventListener('activate', () => self.clients.claim())

const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
  maxRetentionTime: 24 * 60 // Retry for max of 24 Hours (specified in minutes)
})

const expPlugin = new ExpirationPlugin({
  maxEntries: 5,
  maxAgeSeconds: 1 * 24 * 60 * 60,
  purgeOnQuotaError: true,
  matchOptions: {
    ignoreVary: true,
  }
})

registerRoute(
  new RegExp('/*'),
  new StaleWhileRevalidate({
    cacheName: CACHE,
    plugins: [
      bgSyncPlugin,
      expPlugin
    ]
  })
)

PusherPushNotifications.onNotificationReceived = ({ payload, pushEvent, handleNotification, }) => {

  const promiseChain = isClientFocused().then((clientIsFocused) => {
    if (clientIsFocused) {
      self.console.log(payload);
      console.log("Don't need to show a notification.");
      return
    }

    // Client isn't focused, we need to show a notification.
    self.console.log(payload);
    console.log("had to show a notification.");
    return self.registration.showNotification(payload.notification.title, {
      body: payload.notification.body,
      icon: payload.notification.icon,
      // actions: payload.notification.actions,
      deep_link: payload.notification.deep_link
    })
  })

  pushEvent.waitUntil(promiseChain)
}

function isClientFocused() {
  return clients
    .matchAll({
      type: 'window',
      includeUncontrolled: true,
    })
    .then((windowClients) => {
      let clientIsFocused = false

      for (let i = 0; i < windowClients.length; i++) {
        const windowClient = windowClients[i]
        if (windowClient.focused) {
          clientIsFocused = true
          break
        }
      }

      return clientIsFocused
    })
}

What could I have missed or done wrong? Do I need to add some pusher related data to my payload? I also noticed that handleNotification is defined but not used, could it be a reason, what is this meant to do?

@benw-pusher
Copy link

I believe I previously shared a slightly erroneous Service Worker with you that included some other testing I was conducting at the time.
You can remove the following from the SW:

PusherPushNotifications.onNotificationReceived = ({ payload, pushEvent, handleNotification, }) => {

  const promiseChain = isClientFocused().then((clientIsFocused) => {
    if (clientIsFocused) {
      self.console.log(payload);
      console.log("Don't need to show a notification.");
      return
    }

    // Client isn't focused, we need to show a notification.
    self.console.log(payload);
    console.log("had to show a notification.");
    return self.registration.showNotification(payload.notification.title, {
      body: payload.notification.body,
      icon: payload.notification.icon,
      // actions: payload.notification.actions,
      deep_link: payload.notification.deep_link
    })
  })

  pushEvent.waitUntil(promiseChain)
}

function isClientFocused() {
  return clients
    .matchAll({
      type: 'window',
      includeUncontrolled: true,
    })
    .then((windowClients) => {
      let clientIsFocused = false

      for (let i = 0; i < windowClients.length; i++) {
        const windowClient = windowClients[i]
        if (windowClient.focused) {
          clientIsFocused = true
          break
        }
      }

      return clientIsFocused
    })

@Ifriqiya
Copy link
Author

If I remove what you are now suggesting from the SW that means there's no longer a push notifications feature. My original issue is the the deep link feature wasn't working. Are you now saying I should import the pusher service worker file and retain my previous push notifications feature code, and that this would sort the deep link issue? See your original suggestion below:

Your service worker doesn't appear to have the Beams service worker defined - you need to add this as per https://pusher.com/docs/beams/guides/existing-service-worker/#import-the-beams-service-worker-in-your-existing-service-worker-file.

I can also see you have provided some custom code for handling the receipt and display of the notification - however this differed from the documented approach for this - https://pusher.com/docs/beams/guides/handle-incoming-notifications/web/#overriding-default-sdk-behavior.

I am able to access the deep_link as expected with the following payload:

web: {
            notification: {
                title: 'hi!!',
                body: 'hi. 😃👍🎉!',
                deep_link: 'https://bbc.co.uk',
                icon: 'http://localhost:9000/img/favicon-16x16.png'
                
            }
        },

and the following service worker:

importScripts("https://js.pusher.com/beams/service-worker.js");

PusherPushNotifications.onNotificationReceived = ({
    payload,
    pushEvent,
    handleNotification,
}) => {

  const promiseChain = isClientFocused().then((clientIsFocused) => {
    if (clientIsFocused) {
      self.console.log(payload);
      console.log("Don't need to show a notification.");
      return;
    }
  
    // Client isn't focused, we need to show a notification.
    self.console.log(payload);
    console.log("had to show a notification.");
    return self.registration.showNotification('Had to show a notification.');
  });
  
  pushEvent.waitUntil(promiseChain);
  
           /*pushEvent.waitUntil(
           self.registration.showNotification(payload.notification.title, {
              body: payload.notification.body,
              icon: payload.notification.icon,
              data: payload.data, 
            }) 
          )};*/
          };
          


          function isClientFocused() {
            return clients
              .matchAll({
                type: 'window',
                includeUncontrolled: true,
              })
              .then((windowClients) => {
                let clientIsFocused = false;
          
                for (let i = 0; i < windowClients.length; i++) {
                  const windowClient = windowClients[i];
                  if (windowClient.focused) {
                    clientIsFocused = true;
                    break;
                  }
                }
          
                return clientIsFocused;
              });
          }

Here I am using the payload parameter in the PusherPushNotification.onNotificationReceived handler: if you were to move to using the onNotificationRecived handler with this payload instead of the addEventListener and const msg = e.data.json() you should see some results.

@Ifriqiya
Copy link
Author

Testing this further, I updated my SW push feature to just this:

PusherPushNotifications.onNotificationReceived = ({ payload, pushEvent }) => {

   pushEvent.waitUntil(self.registration.showNotification(payload.notification.title, {
    body: payload.notification.body,
    icon: payload.notification.icon,
    data: payload.data,
    deep_link: payload.notification.deep_link
  }))
}

I add this segment that I didn't previously have data: payload.data, and I no longer have the error I mentioned earlier about a need for a 'pusher' variable, but I still wasn't navigated to the url in my notification data, meaning the deep_link feature still isn't working. My understanding is that this is part of the pusher SW import, is that correct or do I need to populate that variable?

@benw-pusher
Copy link

Does the deep_link show correctly if you log it out?

The format of your onNotificationReceived is still different to the documented mechanism at https://pusher.com/docs/beams/guides/handle-incoming-notifications/web/#adding-additional-custom-logic,-keeping-default-behavior

@Ifriqiya
Copy link
Author

Does the deep_link show correctly if you log it out?

The format of your onNotificationReceived is still different to the documented mechanism at https://pusher.com/docs/beams/guides/handle-incoming-notifications/web/#adding-additional-custom-logic,-keeping-default-behavior

Yes it does. How so? Looks the same to me.

@benw-pusher
Copy link

You are calling the showNotification method and not the handleNotification method that is documented.

@Ifriqiya
Copy link
Author

You are calling the showNotification method and not the handleNotification method that is documented.

Doesn't this https://pusher.com/docs/beams/guides/handle-incoming-notifications/web/#overriding-default-sdk-behavior use showNotification

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

No branches or pull requests

3 participants