fix(js): merge all service worker logic into a single worker, and allow precaching to be cancelled

This commit is contained in:
Gabe Kangas
2026-02-04 11:46:11 -08:00
parent bbe910f4f3
commit 4e2e957a68
9 changed files with 270 additions and 142 deletions

View File

@@ -13,7 +13,8 @@
"i18n/index.js",
"i18next-parser.config.mjs",
"types/index.ts",
"scripts/i18n-extract.js"
"scripts/i18n-extract.js",
"worker/**"
],
"ignoreDependencies": [
"@fontsource/inter",

View File

@@ -25,7 +25,6 @@ import { TitleNotifier } from '../../TitleNotifier/TitleNotifier';
import { ServerRenderedHydration } from '../../ServerRendered/ServerRenderedHydration';
import { Theme } from '../../theme/Theme';
import styles from './Main.module.scss';
import { PushNotificationServiceWorker } from '../../workers/PushNotificationServiceWorker/PushNotificationServiceWorker';
import { AppStateOptions } from '../../stores/application-state';
import { Noscript } from '../../ui/Noscript/Noscript';
import { ServerStatus } from '../../../interfaces/server-status.model';
@@ -96,7 +95,6 @@ export const Main: FC = () => {
>
<ClientConfigStore />
</ErrorBoundary>
<PushNotificationServiceWorker />
<TitleNotifier name={name} />
<Theme />
<Script strategy="afterInteractive" src="/customjavascript" />

View File

@@ -1,12 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

View File

@@ -1,27 +0,0 @@
/* eslint-disable react/no-danger */
import { FC, useEffect } from 'react';
export const PushNotificationServiceWorker: FC = () => {
const add = () => {
navigator.serviceWorker.register('/serviceWorker.js').then(
registration => {
console.debug('Service Worker registration successful with scope: ', registration.scope);
},
err => {
console.error('Service Worker registration failed: ', err);
},
);
};
useEffect(() => {
if ('serviceWorker' in navigator) {
window.addEventListener('load', add);
}
return () => {
window.removeEventListener('load', add);
};
}, []);
return null;
};

View File

@@ -6,6 +6,7 @@ const { PHASE_DEVELOPMENT_SERVER } = require('next/constants');
const withPWA = require('next-pwa')({
dest: 'public',
customWorkerDir: 'worker',
runtimeCaching: [],
register: true,
skipWaiting: true,

278
web/package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
/* eslint-disable no-restricted-globals */
self.addEventListener('activate', event => {
console.log('Owncast service worker activated', event);
});
self.addEventListener('install', event => {
console.log('installing Owncast service worker...', event);
});
self.addEventListener('push', event => {
const data = JSON.parse(event.data.text());
const { title, body, icon, tag } = data;
const options = {
title: title || 'Live!',
body: body || 'This live stream has started.',
icon: icon || '/logo/external',
tag,
};
event.waitUntil(self.registration.showNotification(options.title, options));
});
self.addEventListener('notificationclick', event => {
clients.openWindow('/');
});

View File

@@ -27,7 +27,25 @@ function urlBase64ToUint8Array(base64String: string) {
return outputArray;
}
function pausePrecaching(): Promise<void> {
return navigator.serviceWorker.ready.then(registration => {
if (!registration.active) {
return Promise.resolve();
}
return new Promise<void>(resolve => {
const messageChannel = new MessageChannel();
messageChannel.port1.onmessage = () => resolve();
// Timeout fallback in case SW doesn't respond
setTimeout(resolve, 100);
registration.active.postMessage({ type: 'PAUSE_PRECACHING' }, [messageChannel.port2]);
});
});
}
export async function registerWebPushNotifications(vapidPublicKey) {
// Pause precaching to free up the service worker event loop
await pausePrecaching();
const registration = await navigator.serviceWorker.ready;
let subscription = await registration.pushManager.getSubscription();

46
web/worker/index.js Normal file
View File

@@ -0,0 +1,46 @@
/* eslint-disable no-restricted-globals */
/* eslint-disable no-underscore-dangle */
/* eslint-disable no-undef */
// Custom service worker code that next-pwa will bundle into sw.js.
// This handles push notifications and provides a way to pause precaching
// when notification registration needs priority.
// Handle messages from main thread
self.addEventListener('message', event => {
if (event.data?.type === 'PAUSE_PRECACHING') {
console.log('Pausing precaching for notification registration');
// Signal to Workbox to stop precaching by clearing the queue
// This works because Workbox checks this before each fetch
if (self.__WB_MANIFEST) {
// Clear the manifest to prevent further precaching
self.__WB_MANIFEST.length = 0;
}
// Respond that we're ready
if (event.ports[0]) {
event.ports[0].postMessage({ paused: true });
}
}
});
// Push notification handler
self.addEventListener('push', event => {
const data = JSON.parse(event.data.text());
const { title, body, icon, tag } = data;
const options = {
title: title || 'Live!',
body: body || 'This live stream has started.',
icon: icon || '/logo/external',
tag,
};
event.waitUntil(self.registration.showNotification(options.title, options));
});
// Handle notification click
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(clients.openWindow('/'));
});