Skip to content

Commit

Permalink
Fixing SW: first time and hard refreshing (#110)
Browse files Browse the repository at this point in the history
Two cases here:

* app first load: the script try to fetch on 'swfs/init' to get the
clientId but without waiting to be sure that the SW is fully activated
(ie. `serviceWorker.state = 'activated'`).
* hard refresh: when calling for a hard refresh the browser behavior is
to remove the page from the SW control, so
`navigator.serviceWorker.controller` is `null`. The page has to call the
SW and ask him to claim the page.

Both issue are fixed by this issue.
  • Loading branch information
othelarian authored Jul 11, 2024
1 parent 79b8cda commit 5f49bb0
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 13 deletions.
2 changes: 1 addition & 1 deletion apps/jscad-web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async function initFs() {
const lastReload = localStorage.getItem('lastReload')
if (!lastReload || Date.now() - lastReload > 3000) {
localStorage.setItem('lastReload', Date.now())
location.reload()
//location.reload()
}
}
sw.defProjectName = 'jscad'
Expand Down
30 changes: 23 additions & 7 deletions packages/fs-provider/fs-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,34 @@ export const registerServiceWorker = async (
) => {
if ('serviceWorker' in navigator) {
try {
let registration = await navigator.serviceWorker.register(workerScript, {
scope,
})
for (let i = 1; i <= 10; i++) {
if (registration.active) break
registration = await navigator.serviceWorker.getRegistration()
let reg = await navigator.serviceWorker.getRegistration()
if (!reg) { // no sw register yet, go for it
await navigator.serviceWorker.register(workerScript, { scope, })
} else if (!navigator.serviceWorker.controller) {
// handling hard refresh here
await new Promise((resolve) => {
const messageChannel = new MessageChannel()
messageChannel.port1.onmessage = (event) => resolve(event.data)
reg.active.postMessage({type: 'CLAIM_CLIENTS'}, [messageChannel.port2])
})
}
} catch (error) {
console.error(`service worker registration failed with ${error}`)
}

// this code handle app first load, when the serviceWorker is still activating
// here we force it to wait until it switch to 'ativated' state
const rdy = await navigator.serviceWorker.ready
if (rdy.active.state != 'activated') {
await new Promise((resolve) => {
const listener = () => {
rdy.active.removeEventListener('statechange', listener)
resolve()
}
rdy.active.addEventListener('statechange', listener)
})
}

/** @type {SwHandler} */
const sw = {roots:[], libRoots:[]}
sw.api = messageProxy(navigator.serviceWorker, {
Expand All @@ -122,7 +139,6 @@ export const registerServiceWorker = async (
}
},
})


// id is important as we use it to name the temporary cache instance
// for now we use fetch to extract our id, but a better way could be found later
Expand Down
12 changes: 8 additions & 4 deletions packages/fs-serviceworker/fs-serviceworker.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ self.addEventListener('install', event => {
self.skipWaiting()
})


/** Create a client wrapper, or return one from cache. It is important to know
* that cache can disappear (likely due to browser suspending the worker when idle).
* page calling init will create a cached instance, but if dev tools in chrome
Expand Down Expand Up @@ -74,7 +73,12 @@ self.addEventListener('fetch', async event => {
}
})

self.addEventListener('message', event => {
const client = clientMap[event.source.id]
if (client) client.api.onmessage(event)
self.addEventListener('message', async event => {
if (event.data?.type == 'CLAIM_CLIENTS') { // handling hard refresh
await self.clients.claim();
event.ports[0].postMessage(true)
} else {
const client = clientMap[event.source.id]
if (client) client.api.onmessage(event)
}
})
2 changes: 1 addition & 1 deletion packages/postmessage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const initMessaging = (_self, handlers, { onJobCount, debug } = {}) => {
*/
export const messageProxy = (_self, handlers, { onJobCount, debug } = {}) => {
const { sendCmd, sendNotify, getRpcJobCount, listener } = initMessaging(_self, handlers, { onJobCount, debug })
// creating error is not too expensive in our context as tehre will not be millions
// creating error is not too expensive in our context as there will not be millions
// methods produced, and info on how the proxy is created an when called is indispensible for debug
let crated = new Error('proxy')

Expand Down

0 comments on commit 5f49bb0

Please sign in to comment.