// 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.
})
);
});