Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: config loading on subdomains waits for updated config #94

Merged
merged 6 commits into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions src/components/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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 <subdomain>.<namespace>.BASE_URL service worker
await postFromIframeToParentSw()
setConfigExpanded(false)
Expand Down
1 change: 1 addition & 0 deletions src/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export enum COLORS {

export enum ChannelActions {
RELOAD_CONFIG = 'RELOAD_CONFIG',
RELOAD_CONFIG_SUCCESS = 'RELOAD_CONFIG_SUCCESS'
}
5 changes: 3 additions & 2 deletions src/lib/config-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ export async function loadConfigFromLocalStorage (): Promise<void> {
}

export async function setConfig (config: ConfigDb): Promise<void> {
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)
Comment on lines +74 to +75
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set debug level before calling log method so debug level is taken into consideration immediately.


const db = await openDatabase()
await setInDatabase(db, 'gateways', config.gateways)
await setInDatabase(db, 'routers', config.routers)
Expand Down
20 changes: 7 additions & 13 deletions src/redirectPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -27,20 +27,13 @@ export default function RedirectPage (): JSX.Element {
async function doWork (config: ConfigDb): Promise<void> {
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
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

things happen pretty fast so i don't think we want a spinner or other things, but maybe?

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) {
Expand All @@ -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)
Expand Down
33 changes: 24 additions & 9 deletions src/sw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> => {
verifiedFetch = await getVerifiedFetch()
}

/**
******************************************************
Expand All @@ -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<ChannelMessage<'WINDOW', null>>) => {
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:
Expand All @@ -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)
Expand Down Expand Up @@ -138,6 +147,12 @@ function getVerifiedFetchUrl ({ protocol, id, path }: GetVerifiedFetchUrlOptions
}

async function fetchHandler ({ path, request }: FetchHandlerArg): Promise<Response> {
/**
* > 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) {
Expand Down
Loading