Skip to content
quarry Artifact [cf405cb3e9]
Login

Artifact [cf405cb3e9]

Artifact cf405cb3e97d211ea8841af12d1be050223514d43899be1a6db71277b7106fe2:


// quarry's service worker: the part of RFC 0011 §6 that runs when no
// tab is open. Three jobs, no framework, no build step.
//
// Scope is the whole site, which is why it must be served from the
// root with Service-Worker-Allowed: / (see ServiceWorker in this
// package) — a worker served from a subdirectory can only claim that
// subdirectory, and a notification click could then not focus a page.
'use strict';

// A push arrives as JSON from notify's webpush provider: the workflow's
// rendered title and body, plus whatever the step's data carried. The
// url is what quarry-side workflows put in data_json (workflows.go).
self.addEventListener('push', function (event) {
  var data = {};
  if (event.data) {
    try {
      data = event.data.json();
    } catch (e) {
      // A provider that sends plain text is not a reason to show
      // nothing: a notification with no title at all is worse.
      data = { body: event.data.text() };
    }
  }
  var body = data.body || data.content || '';
  var url = (data.data && data.data.url) || data.url || '/';
  event.waitUntil(
    self.registration.showNotification(data.title || data.subject || 'quarry', {
      body: body,
      data: { url: url },
      // Tagged by destination so a second notification about the same
      // thing replaces the first rather than stacking: an inbox that
      // buzzes twice for one reply is an inbox people silence.
      tag: url,
    })
  );
});

// A click goes to the thing the notification is about — focusing a tab
// already showing it rather than opening a second copy.
self.addEventListener('notificationclick', function (event) {
  event.notification.close();
  var url = (event.notification.data && event.notification.data.url) || '/';
  event.waitUntil(
    self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(function (list) {
      for (var i = 0; i < list.length; i++) {
        var client = list[i];
        if (client.url.indexOf(url) !== -1 && 'focus' in client) {
          return client.focus();
        }
      }
      if (self.clients.openWindow) {
        return self.clients.openWindow(url);
      }
      return undefined;
    })
  );
});

// A push service may rotate a subscription without asking. Re-subscribe
// with the same application key and tell quarry the new endpoint;
// without this the device goes quiet and nobody finds out why.
self.addEventListener('pushsubscriptionchange', function (event) {
  event.waitUntil(
    fetch('/api/v1/me/push/key', { credentials: 'same-origin' })
      .then(function (r) {
        return r.json();
      })
      .then(function (k) {
        if (!k.publicKey) {
          return undefined;
        }
        return self.registration.pushManager
          .subscribe({ userVisibleOnly: true, applicationServerKey: k.publicKey })
          .then(function (sub) {
            return fetch('/api/v1/me/push', {
              method: 'POST',
              credentials: 'same-origin',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify(sub),
            });
          });
      })
      .catch(function () {
        // Best-effort by construction: a worker that throws here is a
        // worker the browser may stop running altogether.
      })
  );
});