From 14418cfe9c83e693ddd91f2014147b142fcd1fc3 Mon Sep 17 00:00:00 2001 From: Tokisaki-Galaxy Date: Mon, 5 Jan 2026 09:26:57 +0800 Subject: [PATCH] feat: chrome. implement offscreen document to keep service worker alive --- src/background/main.ts | 49 +++++++++++++++++++++++------------------- src/manifest.json | 1 + src/offscreen.html | 9 ++++++++ src/offscreen.ts | 11 ++++++++++ vite.config.ts | 6 +++++- 5 files changed, 53 insertions(+), 23 deletions(-) create mode 100644 src/offscreen.html create mode 100644 src/offscreen.ts diff --git a/src/background/main.ts b/src/background/main.ts index a1b092b..a63e8d8 100644 --- a/src/background/main.ts +++ b/src/background/main.ts @@ -77,29 +77,34 @@ setBaseUrl(client.baseURL) .then(() => console.info('Started successfully')) /** - * Keep the service worker alive to prevent Chrome's 5-minute inactivity termination - * This is a workaround for Chrome's behavior of terminating inactive service workers - * https://stackoverflow.com/questions/66618136 + * Keep the service worker alive using Offscreen API to prevent Chrome's termination. */ -if (import.meta.env.VITE_TARGET_BROWSER === 'chrome') { - function setupKeepAlive(): void { - console.debug( - 'Setting up keep-alive ping to prevent service worker termination', - ) +async function setupOffscreen() { + const _chrome = (globalThis as any).chrome + if (typeof _chrome === 'undefined' || !_chrome.offscreen) return - setInterval( - () => { - console.debug('Keep-alive ping') - // Force some minimal activity - browser.alarms - .get(config.heartbeat.alarmName) - .then(() => console.debug('Keep-alive ping completed')) - .catch((err) => console.error('Keep-alive ping failed:', err)) - }, - 4 * 60 * 1000, - ) // 4 minutes (less than Chrome's ~5 minute timeout) - } + if (await _chrome.offscreen.hasDocument()) return - // Start the keep-alive mechanism - setupKeepAlive() + try { + await _chrome.offscreen.createDocument({ + url: 'src/offscreen.html', + reasons: ['BLOBS'], + justification: 'Keep service worker alive for heartbeat packets', + }) + } catch (e) { + console.error('Failed to create offscreen document:', e) + } } + +browser.runtime.onMessage.addListener((message: any) => { + if (message.type === 'KEEP_ALIVE') { + return Promise.resolve({ status: 'ok' }) + } + return undefined +}) + +// Initialize on startup and installation +browser.runtime.onStartup.addListener(setupOffscreen) +browser.runtime.onInstalled.addListener(setupOffscreen) + +setupOffscreen() diff --git a/src/manifest.json b/src/manifest.json index 477e7df..6ddd21c 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -48,6 +48,7 @@ "alarms", "notifications", "activeTab", + "offscreen", "storage" ], "{{safari}}.permissions": [ diff --git a/src/offscreen.html b/src/offscreen.html new file mode 100644 index 0000000..f262fa3 --- /dev/null +++ b/src/offscreen.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/offscreen.ts b/src/offscreen.ts new file mode 100644 index 0000000..1c7ab86 --- /dev/null +++ b/src/offscreen.ts @@ -0,0 +1,11 @@ +import browser from 'webextension-polyfill' + +// Send a message to the background every 20 seconds to keep the Service Worker alive +function keepAlive() { + browser.runtime.sendMessage({ type: 'KEEP_ALIVE' }).catch(() => { + // Expected during reload + }) +} + +setInterval(keepAlive, 20 * 1000) +keepAlive() diff --git a/vite.config.ts b/vite.config.ts index c7dde5b..94fdb24 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,7 +20,11 @@ export default defineConfig({ plugins: [ webExtension({ manifest: generateManifest, - additionalInputs: ['src/consent/index.html', 'src/consent/main.ts'], + additionalInputs: [ + 'src/consent/index.html', + 'src/consent/main.ts', + 'src/offscreen.html', + ], browser: process.env.VITE_TARGET_BROWSER, }), ],