Sending data / payload to the Google Chrome Push Notification with Javascript

前端 未结 6 414
一向
一向 2020-12-02 14:49

I\'m working on the Google Chrome Push Notification and I\'m trying to send the payload to the google chrome worker but, I have no idea how I receive this payload.

I

相关标签:
6条回答
  • 2020-12-02 15:15

    Follow these steps to achieve this:

    In the browser:

    You need to get the subscription object and save it, so your server has access to it: Read more about it

    navigator.serviceWorker.ready.then(serviceWorkerRegistration => {
                serviceWorkerRegistration.pushManager.subscribe({userVisibleOnly: true})
                  .then(subscription => {
                      //save subscription.toJSON() object to your server
        })});
    

    In the server:

    install web-push npm package

    And send a web push like this:

        const webpush = require('web-push');
    
    
        setImmediate(async () => {
    
          const params = {
            payload: {title: 'Hey', body: 'Hello World'}
          };
          //this is the subscription object you should get in the browser. This is a demo of how it should look like
          const subscription = {"endpoint":"https://android.googleapis.com/gcm/send/deC24xZL8z4:APA91bE9ZWs2KvLdo71NGYvBHGX6ZO4FFIQCppMsZhiTXtM1S2SlAqoOPNxzLlPye4ieL2ulzzSvPue-dGFBszDcFbSkfb_VhleiJgXRA8UwgLn5Z20_77WroZ1LofWQ22g6bpIGmg2JwYAqjeca_gzrZi3XUpcWHfw","expirationTime":null,"keys":{"p256dh":"BG55fZ3zZq7Cd20vVouPXeVic9-3pa7RhcR5g3kRb13MyJyghTY86IO_IToVKdBmk_2kA9znmbqvd0-o8U1FfA3M","auth":"1gNTE1wddcuF3FUPryGTZOA"}};
    
          if (subscription.keys) {
            params.userPublicKey = subscription.keys.p256dh;
            params.userAuth      = subscription.keys.auth;
          }
    
    // this key you should take from firebase console for example
    // settings -> cloud messaging -> Server key     
    webpush.setGCMAPIKey('AAAASwYmslc:APfA91bGy3tdKvuq90eOvz4AoUm6uPtbqZktZ9dAnElrlH4gglUiuvereTJJWxz8_dANEQciX9legijnJrxvlapI84bno4icD2D0cdVX3_XBOuW3aWrpoqsoxLDTdth86CjkDD4JhqRzxV7RrDXQZd_sZAOpC6f32nbA');
    
          try {
            const r = await webpush.sendNotification(subscription, JSON.stringify(params));
            console.log(r);
          }
          catch (e) {
            console.error(e);
          }
        });
    
    0 讨论(0)
  • 2020-12-02 15:17

    I just ran into this problem. Newer versions of firefox and chrome( version 50+) support payload transferring.

    The dev docs here details the implementation on how this works. An important thing to note is that google GCM or possibly client/chome (I dont know which one) will actually ignore the payload entirely if it is not encrypted.

    This website has both client/server implementations of how to do the push and retrieval through service workers. The push library that examples use is merely a wrapper around a normal REST call

    service worker example implementation:

    self.addEventListener('push', function(event) {
    var payload = event.data ? event.data.text() : 'no payload';
    
    event.waitUntil(
       self.registration.showNotification('ServiceWorker Cookbook', {
         body: payload,
       })
     );
    });
    

    Server example implementation:

    var webPush = require('web-push');
    
    webPush.setGCMAPIKey(process.env.GCM_API_KEY);
    
    module.exports = function(app, route) {
     app.post(route + 'register', function(req, res) {
     res.sendStatus(201);
    });
    
    app.post(route + 'sendNotification', function(req, res) {
      setTimeout(function() {
       webPush.sendNotification(req.body.endpoint, {
         TTL: req.body.ttl,
         payload: req.body.payload,
         userPublicKey: req.body.key,
         userAuth: req.body.authSecret,
       }).then(function() {
        res.sendStatus(201);
       });
      }, req.body.delay * 1000);
     });
    };
    

    Client side javascript implementation example of printing out the the required fields.

    navigator.serviceWorker.register('serviceWorker.js')
    .then(function(registration) {
    
        return registration.pushManager.getSubscription()
            .then(function(subscription) {
                if (subscription) {
                    return subscription;
                }
                return registration.pushManager.subscribe({
                    userVisibleOnly: true
                });
            });
    }).then(function(subscription) {
        var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
        key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
        var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
        authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
        endpoint = subscription.endpoint;
        console.log("Endpoint: " + endpoint);
        console.log("Key: " + key);
        console.log("AuthSecret: " + authSecret);
    });
    
    0 讨论(0)
  • Unfortunately it seems like an intended behavior:

    A downside to the current implementation of the Push API in Chrome is that you can’t send a payload with a push message. Nope, nothing. The reason for this is that in a future implementation, payload will have to be encrypted on your server before it’s sent to a push messaging endpoint. This way the endpoint, whatever push provider it is, will not be able to easily view the content of the push payload. This also protects against other vulnerabilities like poor validation of HTTPS certificates and man-in-the-middle attacks between your server and the push provider. However, this encryption isn’t supported yet, so in the meantime you’ll need to perform a fetch request to get information needed to populate a notification.

    As stated above, the workaround is to contact back your backend after receiving the push and fetch the stored data on the 3rd party server.

    0 讨论(0)
  • 2020-12-02 15:24

    @gauchofunky's answer is correct. With some guidance from the folks on the Chromium dev slack channel and @gauchofunky I was able to piece something together. Here's how to work around the current limitations; hopefully my answer becomes obsolete soon!

    First figure out how you're going to persist notifications on your backend. I'm using Node/Express and MongoDB with Mongoose and my schema looks like this:

    var NotificationSchema = new Schema({
      _user: {type: mongoose.Schema.Types.ObjectId, ref: 'User'},
      subscriptionId: String,
      title: String,
      body: String,
      sent: { type: Boolean, default: false }
    });
    

    Be sure to add an icon if you'd like to alter the icon. I use the same icon every time so mine's hardcoded in the service worker.

    Figuring out the correct REST web service took some thought. GET seemed like an easy choice but the call to get a notification causes side effects, so GET is out. I ended up going with a POST to /api/notifications with a body of {subscriptionId: <SUBSCRIPTION_ID>}. Within the method we basically perform a dequeue:

    var subscriptionId = req.body.subscriptionId;
    
    Notification
    .findOne({_user: req.user, subscriptionId: subscriptionId, sent: false})
    .exec(function(err, notification) {
      if(err) { return handleError(res, err); }
      notification.sent = true;
      notification.save(function(err) {
        if(err) { return handleError(res, err); }
        return res.status(201).json(notification);
      });
    });
    

    In the service worker we need to for sure get the subscription before we make the fetch.

    self.addEventListener('push', function(event) {
      event.waitUntil(
        self.registration.pushManager.getSubscription().then(function(subscription) {
          fetch('/api/notifications/', {
            method: 'post',
            headers: {
              'Authorization': 'Bearer ' + self.token,
              'Accept': 'application/json',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify(subscription)
          })
          .then(function(response) { return response.json(); })
          .then(function(data) {
            self.registration.showNotification(data.title, {
              body: data.body,
              icon: 'favicon-196x196.png'
            });
          })
          .catch(function(err) {
            console.log('err');
            console.log(err);
          });
        })
      );
    });
    

    It's also worth noting that the subscription object changed from Chrome 43 to Chrome 45. In Chrome 45 the subscriptionId property was removed, just something to look out for - this code was written to work with Chrome 43.

    I wanted to make authenticated calls to my backend so I needed to figure out how to get the JWT from my Angular application to my service worker. I ended up using postMessage. Here's what I do after registering the service worker:

    navigator.serviceWorker.register('/service-worker.js', {scope:'./'}).then(function(reg) {
      var messenger = reg.installing || navigator.serviceWorker.controller;
      messenger.postMessage({token: $localStorage.token});
    }).catch(function(err) {
      console.log('err');
      console.log(err);
    });
    

    In the service worker listen for the message:

    self.onmessage.addEventListener('message', function(event) {
      self.token = event.data.token;
    });
    

    Strangely enough, that listener works in Chrome 43 but not Chrome 45. Chrome 45 works with a handler like this:

    self.addEventListener('message', function(event) {
      self.token = event.data.token;
    });
    

    Right now push notifications take quite a bit of work to get something useful going - I'm really looking forward to payloads!

    0 讨论(0)
  • 2020-12-02 15:38

    To retrieve that data, you need to parse "event.data.text()" to a JSON object. I'm guessing something was updated since you tried to get this to work, but it works now. Unlucky!

    However, since I made it to this post when searching for a solution myself, others would probably like a working answer. Here it is:

    // Push message event handler
    self.addEventListener('push', function(event) {
    
      // If true, the event holds data
      if(event.data){
    
        // Need to parse to JSON format
        // - Consider event.data.text() the "stringify()"
        //   version of the data
        var payload = JSON.parse(event.data.text());
        // For those of you who love logging
        console.log(payload); 
    
        var title = payload.data.title;
        var body  = payload.data.body;
        var icon  = './assets/icons/icon.ico'
        var tag   = 'notification-tag';
    
        // Wait until payload is fetched
        event.waitUntil(
          self.registration.showNotification(title, {
            body: body,
            icon: icon,
            tag: tag,
            data: {} // Keeping this here in case I need it later
          })
        );
    
      } else {
        console.log("Event does not have data...");
      }
    
    }); // End push listener
    
    // Notification Click event
    self.addEventListener('notificationclick', function(event) {
      console.log("Notification Clicked");
    }); // End click listener
    

    Personally, I will be creating a "generic" notification in case my data is funky, and will also be using try/catch. I suggest doing the same.

    0 讨论(0)
  • 2020-12-02 15:38

    Actually, payload should be implemented in Chrome 50 (release date - April 19, 2016). In Chrome 50 (and in the current version of Firefox on desktop) you can send some arbitrary data along with the push so that the client can avoid making the extra request. All payload data must be encrypted.

    Here is the the encryption details from developer : https://developers.google.com/web/updates/2016/03/web-push-encryption?hl=en

    0 讨论(0)
提交回复
热议问题