0
votes

How can we make the browser notification icon and body dynamically customized based upon the @challenge.image and the @challenge.description?

enter image description here

I got the notification to pop up by clicking webpush-button, but only the title of the message is dynamically changing. How can I make it work for body and icon too?

challenges/show.html.erb

<script>
  $('.webpush-button').on('click', (e) => {
    navigator.serviceWorker.ready
    .then((serviceWorkerRegistration) => {
      serviceWorkerRegistration.pushManager.getSubscription()
      .then((subscription) => {
        $.post('/push', {
          subscription: subscription.toJSON(),
          message: "<%= @challenge.name %>", # This works dynamically for the title, but how can I get message to also dynamically send body and icon so I can use <%= @challenge.image %> for icon and <%= @challenge.description %> for body?
        });
      });
    });
  });
</script>

push_notifications_controller

class PushNotificationsController < ApplicationController
  def push
    Webpush.payload_send(
      message: params[:message],
      endpoint: params[:subscription][:endpoint],
      p256dh: params[:subscription][:keys][:p256dh],
      auth: params[:subscription][:keys][:auth],
      vapid: {
        subject: "mailto:[email protected]",
        public_key: ENV['VAPID_PUBLIC_KEY'],
        private_key: ENV['VAPID_PRIVATE_KEY']
      }
    )
  end
end

application.js

  if (navigator.serviceWorker) {
    navigator.serviceWorker.register('/serviceworker.js')
    .then(function(reg) {
       console.log('Service worker change, registered the service worker');
    });
  }
  // Otherwise, no push notifications :(
  else {
    console.error('Service worker is not supported in this browser');
  }

  // When serviceWorker is supported, installed, and activated,
  // subscribe the pushManager property with the vapidPublicKey
  navigator.serviceWorker.ready.then((serviceWorkerRegistration) => {
    serviceWorkerRegistration.pushManager.subscribe({
      userVisibleOnly: true,
      applicationServerKey: window.vapidPublicKey
    });
  });

  // Let's check if the browser supports notifications
  if (!("Notification" in window)) {
    console.error("This browser does not support desktop notification");
  }

  // Let's check whether notification permissions have already been granted
  else if (Notification.permission === "granted") {
    console.log("Permission to receive notifications has been granted");
  }

  // Otherwise, we need to ask the user for permission
  else if (Notification.permission !== 'denied') {
    Notification.requestPermission(function (permission) {
    // If the user accepts, let's create a notification
      if (permission === "granted") {
        console.log("Permission to receive notifications has been granted");
      }
    });
  }

serviceworker.js.erb

console.log('[Service Worker] Hello world!');

var CACHE_VERSION = 'v1';
var CACHE_NAME = CACHE_VERSION + ':sw-cache-';

function onInstall(event) {
  console.log('[Serviceworker]', "Installing!", event);
  event.waitUntil(
    caches.open(CACHE_NAME).then(function prefill(cache) {
      return cache.addAll([

        // make sure serviceworker.js is not required by application.js
        // if you want to reference application.js from here
        '<%#= asset_path "application.js" %>',

        '<%= asset_path "application.css" %>',

        '/offline.html',

      ]);
    })
  );
}

function onActivate(event) {
  console.log('[Serviceworker]', "Activating!", event);
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.filter(function(cacheName) {
          // Return true if you want to remove this cache,
          // but remember that caches are shared across
          // the whole origin
          return cacheName.indexOf(CACHE_VERSION) !== 0;
        }).map(function(cacheName) {
          return caches.delete(cacheName);
        })
      );
    })
  );
}

// Borrowed from https://github.com/TalAter/UpUp
function onFetch(event) {
  event.respondWith(
    // try to return untouched request from network first
    fetch(event.request).catch(function() {
      // if it fails, try to return request from the cache
      return caches.match(event.request).then(function(response) {
        if (response) {
          return response;
        }
        // if not found in cache, return default offline content for navigate requests
        if (event.request.mode === 'navigate' ||
          (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
          console.log('[Serviceworker]', "Fetching offline content", event);
          return caches.match('/offline.html');
        }
      })
    })
  );
}

self.addEventListener("push", (event) => {
  let title = (event.data && event.data.text()) || "Yay a message";
  let body = "We have received a push message";
  let icon = '/assets/default.png';

  event.waitUntil(
    self.registration.showNotification(title, { title,body, icon })
  )
});

self.addEventListener('install', onInstall);
self.addEventListener('activate', onActivate);
self.addEventListener('fetch', onFetch);

serviceworker-companion.js

if (navigator.serviceWorker) {
  navigator.serviceWorker.register('/serviceworker.js', { scope: './' })
    .then(function(reg) {
      console.log('[Companion]', 'Service worker registered!');
    });
}

I implemented push via serviceworker gem, webpush gem, VAPID tutorial.

2

2 Answers

1
votes

The event.data object in the self.addEventListener("push", (event)=> {...}), has a method json(), that method convert to json the event.data value.

This will works only if the sended data is in the proper json format.

Here is an example how I'm using web push:

self.addEventListener("push", function onPush(event) {
  var data = event.data.json()
  event.waitUntil(self.registration.showNotification(data.message.title, {
    body: data.message.body,
    icon: data.message.icon,
    actions: [ 
      { action: 'Button one', title: "Button one text" },
      { action: 'Button two', title: "Button two text" }
    ]
  }));
});

Here is a method from my user.rb model:

 def send_web_push(title: , body: )
    #  the web_push is a payload with I save to the user record,
    #  that's allow me to send web push with background worker. 
    payload = {
      endpoint: web_push['endpoint'], 
      keys: {
        auth: web_push['auth'],
        p256dh: web_push['p256dh'] 
      }
    }
    push = WebPush.new(payload)
    push.set_vapid_details(
      'mailto:[email protected]',
      Rails.application.secrets.web_push_public_key,
      Rails.application.secrets.web_push_private_key
    )
    # below is a `:message` key in json format
    push.send_notification({
      message: {
        title: title,                 
        body: body,                  
        icon: "/this_is_my_icon.png" 
      }
    }.to_json)
  end

I use some other ruby gem for web_push, but its common I assume. From that example you should see how is the body, title and icon keys can be changed.

1
votes

view

<%= content_tag(:button, "Foo", class: "webpush-button") %>

<script>
  $('.webpush-button').on('click', (e) => {
    navigator.serviceWorker.ready
    .then((serviceWorkerRegistration) => {
      serviceWorkerRegistration.pushManager.getSubscription()
      .then((subscription) => {
        $.post('/push', {
          subscription: subscription.toJSON(),
          message: "<%= @challenge.name %>", 
          body: "<%= @challenge.why %>",
          icon: "/assets/default.png"
        });
      });
    });
  });
</script>

controller

class PushNotificationsController < ApplicationController
  def push
    Webpush.payload_send(
      message: JSON.generate({ 
        title: params[:message],
        body: params[:body],
        icon: params[:icon]
        }),
      endpoint: params[:subscription][:endpoint],
      p256dh: params[:subscription][:keys][:p256dh],
      auth: params[:subscription][:keys][:auth],
      vapid: {
        subject: "mailto:[email protected]",
        public_key: ENV['VAPID_PUBLIC_KEY'],
        private_key: ENV['VAPID_PRIVATE_KEY']
      }
    )
  end
end

js

self.addEventListener("push", (event) => {
  var data = event.data.json();
  let title = (event.data && data.title) || "Yay a message";
  let body =  (event.data && data.body) || "We have received a push message";
  let icon =  (event.data && data.icon) || "/assets/blue-astronaut.png";

  event.waitUntil(
    self.registration.showNotification(title, { title,body, icon })
  )
});