feat: mark Web Push updates read (Closes #561)
CI / lint (pull_request) Successful in 1m12s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-11 09:57:05 +00:00
1 parent 320d6348ca
commit b21888fd7a
4 files changed
+187 -17

No files matched your search

+49 -13
View File
@@ -4,6 +4,7 @@ const CACHE = 'stackchain-dashboard-shell-v88';
const OFFLINE_LEASE_URL = new URL(BASE + '__offline-session-lease', self.location.origin).href;
const OUTAGE_STATUSES = new Set([500, 502, 503, 504]);
const NAVIGATION_TIMEOUT_MS = self.__STACKCHAIN_NAVIGATION_TIMEOUT_MS || 4000;
const PUSH_ACTION_TIMEOUT_MS = self.__STACKCHAIN_PUSH_ACTION_TIMEOUT_MS || 8000;
const SHELL = [
BASE,
BASE + 'manifest.webmanifest',
@@ -276,26 +277,61 @@ self.addEventListener('push', event => {
catch (_error) { return; }
const route = String(payload.route || '');
const tag = String(payload.tag || '');
const notificationId = Number(payload.notification_id);
if (!/^#\/my-work\/update\/\d+$/.test(route) || !/^stackchain-update-\d+$/.test(tag)) return;
event.waitUntil(self.registration.showNotification('New work update', {
const options = {
body: 'Tap to review it in Stackchain.',
tag,
data: {route},
}));
};
if (
Number.isSafeInteger(notificationId) && notificationId > 0
&& route === '#/my-work/update/' + notificationId
&& tag === 'stackchain-update-' + notificationId
) {
options.actions = [{ action: 'mark-read', title: 'Mark read' }];
options.data.notificationId = notificationId;
}
event.waitUntil(self.registration.showNotification('New work update', options));
});
self.addEventListener('notificationclick', event => {
event.notification.close();
const route = String(event.notification.data?.route || '');
if (!route.startsWith('#/my-work/')) return;
async function openWorkRoute(route) {
const target = new URL(BASE + route, self.location.origin).href;
event.waitUntil((async () => {
const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
const client = windows.find(candidate => candidate.url.startsWith(self.location.origin + BASE));
if (!client) return self.clients.openWindow(target);
if (client.navigate) await client.navigate(target);
return client.focus();
})());
const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
const client = windows.find(candidate => candidate.url.startsWith(self.location.origin + BASE));
if (!client) return self.clients.openWindow(target);
if (client.navigate) await client.navigate(target);
return client.focus();
}
self.addEventListener('notificationclick', event => {
const route = String(event.notification.data?.route || '');
if (event.action === 'mark-read') {
const notificationId = Number(event.notification.data?.notificationId);
if (
!Number.isSafeInteger(notificationId) || notificationId <= 0
|| route !== '#/my-work/update/' + notificationId
) return;
event.waitUntil((async () => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), PUSH_ACTION_TIMEOUT_MS);
try {
await fetchJson(BASE + 'api/v1/notifications/' + notificationId + '/read', {
method: 'PATCH', headers: { Accept: 'application/json' }, signal: controller.signal,
});
event.notification.close();
} catch (_error) {
await openWorkRoute(route);
event.notification.close();
} finally {
clearTimeout(timeout);
}
})());
return;
}
event.notification.close();
if (!route.startsWith('#/my-work/')) return;
event.waitUntil(openWorkRoute(route));
});
self.addEventListener('fetch', event => {