jarvis-cloud / webar /service-worker.js
Jarvis2345's picture
Squash history — remove all prior commits (secret hygiene, S4)
a31f556
Raw
History Blame Contribute Delete
4.26 kB
const CACHE_NAME = 'jarvis-real-ar-v5'
const BASE_PATH = new URL(self.registration.scope).pathname.replace(/\/?$/, '/')
const shellURL = path => new URL(path, self.registration.scope).href
const START_URL = shellURL('./?device=POCO-M5&mode=vision_pro&source=manifest')
const APP_SHELL = [
shellURL('./'),
shellURL('./index.html'),
shellURL('./manifest.json'),
START_URL,
shellURL('./ui/panels.css'),
shellURL('./ui/mobile.css'),
shellURL('./ui/hud.css'),
shellURL('./ui/animations.css'),
]
function isSameOriginAsset(url) {
return url.origin === self.location.origin
}
function discoverAppAssets(html) {
if (!html) return []
const urls = new Set()
const re = /<(?:script|link)\b[^>]*(?:\ssrc|\shref)=["']([^"']+)["'][^>]*>/gi
for (const match of html.matchAll(re)) {
try {
const url = new URL(match[1], shellURL('./index.html'))
if (isSameOriginAsset(url)) urls.add(url.href)
} catch {
// Ignore malformed asset URLs.
}
}
return [...urls]
}
function discoverDynamicImports(js, baseUrl) {
if (!js) return []
const urls = new Set()
const re = /import\(\s*["']([^"']+)["']\s*\)/g
for (const match of js.matchAll(re)) {
try {
const url = new URL(match[1], baseUrl)
if (isSameOriginAsset(url)) urls.add(url.href)
} catch {
// Ignore malformed dynamic import specifiers.
}
}
return [...urls]
}
async function cacheAppShell() {
const cache = await caches.open(CACHE_NAME)
const urls = new Set(APP_SHELL)
const seen = new Set()
const queue = [...APP_SHELL]
try {
const response = await fetch(shellURL('./index.html'), { cache: 'reload' })
if (response.ok) {
const html = await response.text()
discoverAppAssets(html).forEach(url => {
urls.add(url)
queue.push(url)
})
await cache.put(shellURL('./index.html'), new Response(html, {
status: response.status,
statusText: response.statusText,
headers: response.headers,
}))
}
} catch {
// Shell HTML is best-effort during install.
}
while (queue.length) {
const url = queue.shift()
if (!url || seen.has(url)) continue
seen.add(url)
try {
const response = await fetch(url, { cache: 'reload' })
const contentType = response.headers.get('content-type') || ''
const expectedCSS = url.endsWith('.css')
const expectedJS = url.endsWith('.js')
if (response.ok && (!expectedCSS || contentType.includes('text/css'))) {
await cache.put(url, response.clone())
if (expectedJS) {
const js = await response.text()
discoverDynamicImports(js, url).forEach(nextUrl => {
if (!seen.has(nextUrl)) queue.push(nextUrl)
})
}
}
} catch {
await Promise.resolve()
}
}
}
self.addEventListener('install', event => {
event.waitUntil(cacheAppShell().then(() => self.skipWaiting()))
})
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys()
.then(keys => Promise.all(keys.filter(key => key !== CACHE_NAME).map(key => caches.delete(key))))
.then(() => self.clients.claim())
)
})
self.addEventListener('fetch', event => {
const request = event.request
if (request.method !== 'GET') return
const url = new URL(request.url)
if (url.origin !== self.location.origin) return
const relativePath = url.pathname.startsWith(BASE_PATH)
? url.pathname.slice(BASE_PATH.length - 1)
: url.pathname
if (relativePath.startsWith('/api/') || relativePath.startsWith('/scene-ws')) return
event.respondWith(
fetch(request)
.then(response => {
if (response.ok) {
const copy = response.clone()
caches.open(CACHE_NAME).then(cache => cache.put(request, copy)).catch(() => undefined)
}
return response
})
.catch(() => caches.match(request).then(cached => {
if (cached) return cached
if (request.mode === 'navigate') {
return caches.match(shellURL('./')).then(shell => shell || caches.match(shellURL('./index.html')))
}
return new Response('Offline: this AR asset is not cached.', { status: 503, headers: { 'content-type': 'text/plain' } })
}))
)
})