fix(pwa): rewrite and update service worker related to fit recent refactors

This commit is contained in:
Julien Oculi 2025-04-22 16:50:42 +02:00
parent 09e4f401e9
commit 125e645ffd
9 changed files with 61 additions and 90 deletions

View file

@ -1,5 +0,0 @@
import RegisterServiceWorker from ':islands/RegisterServiceWorker.tsx'
export function ProgressiveWebApp() {
return <RegisterServiceWorker />
}

View file

@ -26,6 +26,7 @@
"packages/" "packages/"
], ],
"imports": { "imports": {
"@deno/emit": "jsr:@deno/emit@^0.46.0",
"@deno/gfm": "jsr:@deno/gfm@^0.10.0", "@deno/gfm": "jsr:@deno/gfm@^0.10.0",
"@std/fs": "jsr:@std/fs@^1.0.6", "@std/fs": "jsr:@std/fs@^1.0.6",
"@std/path": "jsr:@std/path@^1.0.8", "@std/path": "jsr:@std/path@^1.0.8",

View file

@ -1,10 +1,8 @@
import { requestApi } from ':src/utils.ts' import { requestApi } from ':src/utils.ts'
import { useEffect } from 'preact/hooks'
export default function RegisterServiceWorker() { async function register() {
if ('serviceWorker' in navigator) { const registration = await navigator.serviceWorker.register('/sw', {
import(':islands/StartServiceWorker.tsx').then(async (mod) => {
const href = mod.default()
const registration = await navigator.serviceWorker.register(href, {
scope: '/', scope: '/',
type: 'module', type: 'module',
}) })
@ -40,8 +38,12 @@ export default function RegisterServiceWorker() {
} catch (cause) { } catch (cause) {
console.error('Push subscription is not available', { cause }) console.error('Push subscription is not available', { cause })
} }
})
} }
return <></> export default function RegisterServiceWorker() {
useEffect(() => {
if ('serviceWorker' in navigator) register()
}, [])
return null
} }

View file

@ -1,10 +0,0 @@
import { main } from ':src/serviceworker/mod.ts'
const IS_SW = 'onpushsubscriptionchange' in self
export default function StartServiceWorker() {
if (IS_SW) {
main()
}
return new URL(import.meta.url).pathname
}

View file

@ -2,8 +2,8 @@ import { asset, Partial } from 'fresh/runtime'
import { type PageProps } from 'fresh' import { type PageProps } from 'fresh'
import { Footer } from ':components/Footer.tsx' import { Footer } from ':components/Footer.tsx'
import { Header } from ':components/Header.tsx' import { Header } from ':components/Header.tsx'
import { ProgressiveWebApp } from ':components/ProgressiveWebApp.tsx'
import IsOnline from ':islands/IsOnline.tsx' import IsOnline from ':islands/IsOnline.tsx'
import RegisterServiceWorker from ':islands/RegisterServiceWorker.tsx'
export default function App( export default function App(
{ Component, data }: PageProps<{ title?: string } | undefined>, { Component, data }: PageProps<{ title?: string } | undefined>,
@ -54,7 +54,7 @@ export default function App(
<Component /> <Component />
</Partial> </Partial>
</main> </main>
<ProgressiveWebApp /> <RegisterServiceWorker />
<Footer /> <Footer />
</body> </body>
</html> </html>

View file

@ -1,7 +1,6 @@
import { useCache } from ':src/cache/middleware.ts' import { useCache } from ':src/cache/middleware.ts'
import { useCsp } from ':src/csp/middleware.ts' import { useCsp } from ':src/csp/middleware.ts'
import { useSecurityHeaders } from ':src/security_headers/middleware.ts' import { useSecurityHeaders } from ':src/security_headers/middleware.ts'
import { useServiceworker } from ':src/serviceworker/middleware.ts'
import { useSession } from ':src/session/middleware.ts' import { useSession } from ':src/session/middleware.ts'
import { SessionStore } from ':src/session/mod.ts' import { SessionStore } from ':src/session/mod.ts'
import { define } from '../utils.ts' import { define } from '../utils.ts'
@ -23,7 +22,6 @@ export default define.middleware(async (ctx) => {
useSecurityHeaders(request, response, ctx) useSecurityHeaders(request, response, ctx)
await useCsp(request, response, ctx) await useCsp(request, response, ctx)
useSession(request, response, ctx) useSession(request, response, ctx)
useServiceworker(request, response, ctx)
useCache(request, response, ctx) useCache(request, response, ctx)
return response return response

View file

@ -1,37 +1,28 @@
import { expandGlob } from '@std/fs' import { expandGlob } from '@std/fs'
import { SessionHandlers } from ':src/session/mod.ts' import { SessionHandlers } from ':src/session/mod.ts'
import { respondApi } from ':src/utils.ts' import { respondApi } from ':src/utils.ts'
import { BUILD_ID } from '$fresh/src/server/build_id.ts' import { BUILD_ID } from '../../../utils.ts'
import { encodeBase64 } from '@std/encoding'
export type PrecacheResponse = { version: string; preCachedUrls: string[] } export type PrecacheResponse = { version: string; preCachedUrls: string[] }
async function getVersion() {
const versionBytes = new TextEncoder().encode(BUILD_ID)
const versionHash = await crypto.subtle.digest('SHA-256', versionBytes)
return encodeBase64(versionHash)
}
export const handler: SessionHandlers = { export const handler: SessionHandlers = {
async GET() { async GET() {
try { try {
const preCachedUrls: string[] = ['/', '/imports/markdown_css'] const preCachedUrls: string[] = ['/', '/imports/markdown_css']
const paths = ['/static/**', '/_fresh/static/**'] const paths = ['/static/**', '/_fresh/static/**']
const routes = '/routes/*/index.tsx' const routes = '/routes/*/index.tsx'
const version = await getVersion() const version = BUILD_ID
//Pre-cache routes //Pre-cache routes
for await (const route of expandGlob(routes, { root: '.' })) { for await (const route of expandGlob(routes, { root: '.' })) {
if (!route.isFile) continue if ('isFile' in route && !route.isFile) continue
//@ts-expect-error parentPath is missing from type definition preCachedUrls.push(strip('/routes/**', route.path))
const path = route.parentPath as string
preCachedUrls.push(path.replace('routes', '').replace('\\', '/'))
} }
// Pre-cache files // Pre-cache files
for (const path of paths) { for (const path of paths) {
for await (const entry of expandGlob(path, { root: '.' })) { for await (const entry of expandGlob(path, { root: '.' })) {
if (!entry.isFile) continue if ('isFile' in entry && !entry.isFile) continue
preCachedUrls.push(strip(path, entry.path)) preCachedUrls.push(strip(path, entry.path))
} }
} }
@ -47,12 +38,10 @@ export const handler: SessionHandlers = {
} }
function strip(root: string, path: string) { function strip(root: string, path: string) {
return path
// Force unix/web separator
.replaceAll('\\', '/')
.replace(
// Remove root slash and glob * // Remove root slash and glob *
root.slice(1).replaceAll('*', ''), const base = root.slice(1).replaceAll('*', '').replaceAll('\\', '/')
'/', // Force unix/web separator
) const pathname = path.replaceAll('\\', '/').replace('/index.tsx', '')
return `/${pathname.slice(base.length)}`
} }

View file

@ -1,12 +0,0 @@
import { FreshContext } from 'fresh'
export function useServiceworker(
_request: Request,
response: Response,
ctx: FreshContext,
) {
// Allow service worker to serve root scope
if (ctx.url.pathname.endsWith('island-startserviceworker.js')) {
response.headers.set('Service-Worker-Allowed', '/')
}
}

View file

@ -4,7 +4,7 @@
import type { JsonValue } from '@std/json' import type { JsonValue } from '@std/json'
import { ApiPayload } from ':src/utils.ts' import { ApiPayload } from ':src/utils.ts'
import type { PrecacheResponse } from '../../routes/api/serviceworker/precache.tsx' import type { PrecacheResponse } from '../../routes/api/serviceworker/precache.tsx'
import { FetchStrategy } from './src/fetch_strategy.ts' import { FetchStrategy } from ':src/serviceworker/src/fetch_strategy.ts'
// Force load service worker types // Force load service worker types
const self = globalThis as unknown as ServiceWorkerGlobalScope const self = globalThis as unknown as ServiceWorkerGlobalScope
@ -120,6 +120,14 @@ if (IS_SW) {
) )
} }
}) })
self.addEventListener('notificationclick', (event) => {
console.log('SW - NOT_CLICK', event)
})
self.addEventListener('notificationclose', (event) => {
console.log('SW - NOT_CLOSE', event)
})
} }
async function fetchHandler(event: FetchEvent) { async function fetchHandler(event: FetchEvent) {