title: How to customize browser webpush notification body & icon?
body: <p>How can we make the browser notification <code>icon</code> and <code>body</code> dynamically customized based upon the <code>@challenge.image</code> and the <code>@challenge.description</code>?</p>
  
  <p><a href="https://i.stack.imgur.com/HIQ24.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/HIQ24.png" alt="enter image description here"></a></p>
  
  <p>I got the notification to pop up by clicking <code>webpush-button</code>, but only the <code>title</code> of the message is dynamically changing. How can I make it work for <code>body</code> and <code>icon</code> too?</p>
  
  <p><strong>challenges/show.html.erb</strong></p>
  
  <pre><code>&lt;script&gt;
    $('.webpush-button').on('click', (e) =&gt; {
      navigator.serviceWorker.ready
      .then((serviceWorkerRegistration) =&gt; {
        serviceWorkerRegistration.pushManager.getSubscription()
        .then((subscription) =&gt; {
          $.post('/push', {
            subscription: subscription.toJSON(),
            message: "&lt;%= @challenge.name %&gt;", # This works dynamically for the title, but how can I get message to also dynamically send body and icon so I can use &lt;%= @challenge.image %&gt; for icon and &lt;%= @challenge.description %&gt; for body?
          });
        });
      });
    });
  &lt;/script&gt;
  </code></pre>
  
  <p><strong>push_notifications_controller</strong></p>
  
  <pre><code>class PushNotificationsController &lt; 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:sender@example.com",
          public_key: ENV['VAPID_PUBLIC_KEY'],
          private_key: ENV['VAPID_PRIVATE_KEY']
        }
      )
    end
  end
  </code></pre>
  
  <p><strong>application.js</strong></p>
  
  <pre><code>  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) =&gt; {
      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");
        }
      });
    }
  </code></pre>
  
  <p><strong>serviceworker.js.erb</strong></p>
  
  <pre><code>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
          '&lt;%#= asset_path "application.js" %&gt;',
  
          '&lt;%= asset_path "application.css" %&gt;',
  
          '/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' &amp;&amp; event.request.headers.get('accept').includes('text/html'))) {
            console.log('[Serviceworker]', "Fetching offline content", event);
            return caches.match('/offline.html');
          }
        })
      })
    );
  }
  
  self.addEventListener("push", (event) =&gt; {
    let title = (event.data &amp;&amp; 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);
  </code></pre>
  
  <p><strong>serviceworker-companion.js</strong></p>
  
  <pre><code>if (navigator.serviceWorker) {
    navigator.serviceWorker.register('/serviceworker.js', { scope: './' })
      .then(function(reg) {
        console.log('[Companion]', 'Service worker registered!');
      });
  }
  </code></pre>
  
  <p>I implemented push via <a href="https://github.com/rossta/serviceworker-rails" rel="nofollow noreferrer">serviceworker gem</a>, <a href="https://github.com/zaru/webpush" rel="nofollow noreferrer">webpush gem</a>, <a href="https://rossta.net/blog/using-the-web-push-api-with-vapid.html" rel="nofollow noreferrer">VAPID tutorial</a>.</p>