mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-06-15 23:43:06 +03:00
* 🛟 fix: Auto-Recover from Stale Service Worker Assets After Deploys - 404 missing static assets in the SPA fallback instead of serving index.html - inline recovery script unregisters stale SWs and reloads once on chunk failure - route vite:preloadError into the same recovery path for stale lazy chunks * 🛟 fix: Address Review — SW-Side Recovery, Scoped Unregister, Shared Fallback - importScripts'd sw-heal.js pings window clients on activation and reloads ones that can't pong: stale pages carry no recovery code of their own - scope SW unregistration to the deployment base for subpath installs - preventDefault vite:preloadError only when a recovery reload was initiated - extract createSpaFallback and apply the asset 404 guard to experimental.js
61 lines
1.7 KiB
JavaScript
61 lines
1.7 KiB
JavaScript
/* Runs inside the generated service worker via workbox `importScripts`.
|
|
* When a new build's worker activates, pages served from a previous build
|
|
* can no longer load their hashed chunks (the old precache is purged) and
|
|
* carry no recovery code of their own — the worker is the only code path
|
|
* stale clients fetch fresh. Ping every window client; reload the ones
|
|
* that cannot answer. */
|
|
const PING_TYPE = 'LC_SW_PING';
|
|
const PONG_TYPE = 'LC_SW_PONG';
|
|
const PONG_TIMEOUT_MS = 1500;
|
|
|
|
const pendingPongs = new Map();
|
|
|
|
self.addEventListener('message', (event) => {
|
|
if (!event.data || event.data.type !== PONG_TYPE || !event.source) {
|
|
return;
|
|
}
|
|
const resolvePong = pendingPongs.get(event.source.id);
|
|
if (resolvePong) {
|
|
pendingPongs.delete(event.source.id);
|
|
resolvePong(true);
|
|
}
|
|
});
|
|
|
|
function pingClient(client) {
|
|
return new Promise((resolve) => {
|
|
pendingPongs.set(client.id, resolve);
|
|
setTimeout(() => {
|
|
if (pendingPongs.delete(client.id)) {
|
|
resolve(false);
|
|
}
|
|
}, PONG_TIMEOUT_MS);
|
|
client.postMessage({ type: PING_TYPE });
|
|
});
|
|
}
|
|
|
|
async function reloadUnresponsiveClients() {
|
|
await self.clients.claim();
|
|
const windowClients = await self.clients.matchAll({
|
|
type: 'window',
|
|
includeUncontrolled: true,
|
|
});
|
|
const topLevelClients = windowClients.filter((client) => client.frameType !== 'nested');
|
|
await Promise.all(
|
|
topLevelClients.map(async (client) => {
|
|
const responsive = await pingClient(client);
|
|
if (responsive) {
|
|
return;
|
|
}
|
|
try {
|
|
await client.navigate(client.url);
|
|
} catch {
|
|
/* client closed or no longer controllable */
|
|
}
|
|
}),
|
|
);
|
|
}
|
|
|
|
self.addEventListener('activate', (event) => {
|
|
event.waitUntil(reloadUnresponsiveClients());
|
|
});
|