diff --git a/src/components/config.tsx b/src/components/config.tsx index e3f2cc8d..9775863c 100644 --- a/src/components/config.tsx +++ b/src/components/config.tsx @@ -3,7 +3,8 @@ import { ConfigContext } from '../context/config-context.tsx' import { HeliaServiceWorkerCommsChannel } from '../lib/channel.ts' import { getConfig, loadConfigFromLocalStorage } from '../lib/config-db.ts' import { LOCAL_STORAGE_KEYS } from '../lib/local-storage.ts' -import { Collapsible } from './collapsible' +import { trace } from '../lib/logger.ts' +import { Collapsible } from './collapsible.tsx' import LocalStorageInput from './local-storage-input.tsx' import { LocalStorageToggle } from './local-storage-toggle' import { ServiceWorkerReadyButton } from './sw-ready-button.tsx' @@ -47,14 +48,14 @@ export default (): JSX.Element | null => { // TODO: why we need this origin here? where is targetOrigin used? const targetOrigin = decodeURIComponent(window.location.hash.split('@origin=')[1]) const config = await getConfig() - // eslint-disable-next-line no-console - console.log('config-page: postMessage config to origin ', config, origin) + trace('config-page: postMessage config to origin ', config, origin) /** * The reload page in the parent window is listening for this message, and then it passes a RELOAD_CONFIG message to the service worker */ window.parent?.postMessage({ source: 'helia-sw-config-iframe', target: 'PARENT', action: 'RELOAD_CONFIG', config }, { targetOrigin }) + trace('config-page: RELOAD_CONFIG sent to parent window') }, []) useEffect(() => { @@ -67,9 +68,11 @@ export default (): JSX.Element | null => { const saveConfig = useCallback(async () => { try { await loadConfigFromLocalStorage() + trace('config-page: sending RELOAD_CONFIG to service worker') // update the BASE_URL service worker - // TODO: use channel.messageAndWaitForResponse to ensure that the config is loaded before proceeding. - channel.postMessage({ target: 'SW', action: 'RELOAD_CONFIG' }) + await channel.messageAndWaitForResponse('SW', { target: 'SW', action: 'RELOAD_CONFIG' }) + // base_domain service worker is updated + trace('config-page: RELOAD_CONFIG_SUCCESS for %s', window.location.origin) // update the ..BASE_URL service worker await postFromIframeToParentSw() setConfigExpanded(false) diff --git a/src/lib/common.ts b/src/lib/common.ts index 61c338db..0c3486cc 100644 --- a/src/lib/common.ts +++ b/src/lib/common.ts @@ -7,4 +7,5 @@ export enum COLORS { export enum ChannelActions { RELOAD_CONFIG = 'RELOAD_CONFIG', + RELOAD_CONFIG_SUCCESS = 'RELOAD_CONFIG_SUCCESS' } diff --git a/src/lib/config-db.ts b/src/lib/config-db.ts index 8b7bc685..9c526fdd 100644 --- a/src/lib/config-db.ts +++ b/src/lib/config-db.ts @@ -71,8 +71,9 @@ export async function loadConfigFromLocalStorage (): Promise { } export async function setConfig (config: ConfigDb): Promise { - log('config-debug: setting config', config) - debugLib.enable(config.debug ?? '') + debugLib.enable(config.debug ?? '') // set debug level first. + log('config-debug: setting config %O for domain %s', config, window.location.origin) + const db = await openDatabase() await setInDatabase(db, 'gateways', config.gateways) await setInDatabase(db, 'routers', config.routers) diff --git a/src/redirectPage.tsx b/src/redirectPage.tsx index dd8adc40..4970a7ac 100644 --- a/src/redirectPage.tsx +++ b/src/redirectPage.tsx @@ -4,7 +4,7 @@ import { ServiceWorkerContext } from './context/service-worker-context.tsx' import { HeliaServiceWorkerCommsChannel } from './lib/channel.ts' import { setConfig, type ConfigDb } from './lib/config-db.ts' import { getSubdomainParts } from './lib/get-subdomain-parts' -import { error } from './lib/logger.ts' +import { error, trace } from './lib/logger.ts' const ConfigIframe = (): JSX.Element => { const { parentDomain } = getSubdomainParts(window.location.href) @@ -27,20 +27,13 @@ export default function RedirectPage (): JSX.Element { async function doWork (config: ConfigDb): Promise { try { await setConfig(config) - // TODO: use channel.messageAndWaitForResponse to ensure that the config is loaded before proceeding. - channel.postMessage({ target: 'SW', action: 'RELOAD_CONFIG' }) + // TODO: show spinner / disable buttons while waiting for response + await channel.messageAndWaitForResponse('SW', { target: 'SW', action: 'RELOAD_CONFIG' }) + trace('redirect-page: RELOAD_CONFIG_SUCCESS on %s', window.location.origin) // try to preload the content - setTimeout(() => { - fetch(window.location.href, { method: 'GET' }).then((response) => { - // eslint-disable-next-line no-console - console.log('response', response) - }).catch((err) => { - // eslint-disable-next-line no-console - console.error('error fetching', err) - }) - }, 500) + await fetch(window.location.href, { method: 'GET' }) } catch (err) { - error('config-debug: error setting config on subdomain', err) + error('redirect-page: error setting config on subdomain', err) } if (config.autoReload) { @@ -49,6 +42,7 @@ export default function RedirectPage (): JSX.Element { } const listener = (event: MessageEvent): void => { if (event.data?.source === 'helia-sw-config-iframe') { + trace('redirect-page: received RELOAD_CONFIG message from iframe', event.data) const config = event.data?.config if (config != null) { void doWork(config as ConfigDb) diff --git a/src/sw.ts b/src/sw.ts index f727e2f5..a997bd7f 100644 --- a/src/sw.ts +++ b/src/sw.ts @@ -38,6 +38,9 @@ declare let self: ServiceWorkerGlobalScope let verifiedFetch: VerifiedFetch const channel = new HeliaServiceWorkerCommsChannel('SW') const urlInterceptRegex = [new RegExp(`${self.location.origin}/ip(n|f)s/`)] +const updateVerifiedFetch = async (): Promise => { + verifiedFetch = await getVerifiedFetch() +} /** ****************************************************** @@ -47,20 +50,26 @@ const urlInterceptRegex = [new RegExp(`${self.location.origin}/ip(n|f)s/`)] self.addEventListener('install', (event) => { // 👇 When a new version of the SW is installed, activate immediately void self.skipWaiting() + // ensure verifiedFetch is ready for use + event.waitUntil(updateVerifiedFetch()) }) -self.addEventListener('activate', () => { - // Set verified fetch initially - void getVerifiedFetch().then((newVerifiedFetch) => { - verifiedFetch = newVerifiedFetch - }) - +self.addEventListener('activate', (event) => { + /** + * 👇 Claim all clients immediately. This handles the case when subdomain is + * loaded for the first time, and config is updated and then a pre-fetch is + * sent (await fetch(window.location.href, { method: 'GET' })) to start + * loading the content prior the user reloading or clicking the "load content" + * button. + */ + event.waitUntil(self.clients.claim()) channel.onmessagefrom('WINDOW', async (message: MessageEvent>) => { const { action } = message.data switch (action) { case 'RELOAD_CONFIG': - void getVerifiedFetch().then((newVerifiedFetch) => { - verifiedFetch = newVerifiedFetch + void updateVerifiedFetch().then(() => { + channel.postMessage({ action: 'RELOAD_CONFIG_SUCCESS' }) + trace('sw: RELOAD_CONFIG_SUCCESS for %s', self.location.origin) }) break default: @@ -69,7 +78,7 @@ self.addEventListener('activate', () => { }) }) -self.addEventListener('fetch', event => { +self.addEventListener('fetch', (event) => { const request = event.request const urlString = request.url const url = new URL(urlString) @@ -138,6 +147,12 @@ function getVerifiedFetchUrl ({ protocol, id, path }: GetVerifiedFetchUrlOptions } async function fetchHandler ({ path, request }: FetchHandlerArg): Promise { + /** + * > Any global variables you set will be lost if the service worker shuts down. + * + * @see https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle + */ + verifiedFetch = verifiedFetch ?? await getVerifiedFetch() // test and enforce origin isolation before anything else is executed const originLocation = await findOriginIsolationRedirect(new URL(request.url)) if (originLocation !== null) {