Files
simple/frontend/sw.js
T
catarrh ef949e6a17 fix: cached GETs bypass the card lock; compact install step lines (v3.6.47)
The install modal's progress bar froze around 70% and jumped to 100% at
the end: not a math bug - the UI's other polls (/api/stk-status,
/api/poll-status, /api/proactive-log, /api/menu, ...) were card-locked in
do_GET even though they only read cached state, queued behind the install
and exhausted the browser's 6-connections-per-host budget, starving
/api/status.  _CARD_FREE_GET lists every card-free GET (status, version,
stk-status, poll-status, proactive-log, menu, events, terminal-profile,
pli-*, commands, net-state, mcc-mnc, scp81/bip status|script|log);
/api/cardinfo and /api/esim/* stay locked (they touch the card).

tests/test_fetch_routes.py holds _CARD_LOCK and asserts the cached
endpoints still answer - verified to time out on the pre-fix list.

The install modal is wider (max-w-3xl) and successful step lines are
compact: '✅ Step 3: LOAD (1/240) - 6101 · 274 B / 3 SMS' (no verbose SW
name, B instead of bytes, the SMS count only when >1); failed steps keep
the full detail and the hint.

sw cache simple-v320; tests: 533 Python / 684 Node.
2026-09-30 00:02:52 +03:00

78 lines
2.1 KiB
JavaScript

const CACHE = 'simple-v320';
const URLS = [
'index.html',
'help.html',
'help-ru.html',
'style.css',
'sim.png',
'favicon.svg',
'icon-192.png',
'icon-512.png',
'manifest.json',
'des-bundle.js',
'aes-bundle.js',
'sim.svg',
'sim_anim.svg',
'nosim.svg',
'uicc_files.json',
];
self.addEventListener('install', e => {
// Fetch the shell with `cache: 'reload'`: a plain addAll() would re-use
// HTTP-cached copies, so a new SW version could precache a stale
// style.css/index.html (the stale-style trap, v3.6.31).
e.waitUntil(
caches.open(CACHE).then(c =>
Promise.all(URLS.map(u => fetch(u, { cache: 'reload' }).then(r => {
if (r && r.ok) return c.put(u, r);
})))
)
);
self.skipWaiting();
});
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
).then(() => clients.claim())
);
});
const OFFLINE_RESPONSE = new Response('Offline: page not cached', {
status: 503,
statusText: 'Offline',
headers: { 'Content-Type': 'text/plain' },
});
self.addEventListener('fetch', e => {
if (!e.request.url.startsWith('http')) return;
const path = new URL(e.request.url).pathname;
if (path.startsWith('/api/')) return; // live data, never cache
if (e.request.method !== 'GET') return;
const isNavigate = e.request.mode === 'navigate';
const isSwScript = path.endsWith('/sw.js');
if (isNavigate) {
e.respondWith(
fetch(e.request).then(res => {
const clone = res.clone();
caches.open(CACHE).then(c => c.put(e.request, clone));
return res;
}).catch(() =>
caches.match(e.request)
.then(r => r || caches.match('index.html'))
.then(r => r || OFFLINE_RESPONSE)
)
);
} else if (isSwScript) {
e.respondWith(fetch(e.request).catch(() => OFFLINE_RESPONSE));
} else {
e.respondWith(
caches.match(e.request).then(r => r || fetch(e.request).then(res => {
const clone = res.clone();
caches.open(CACHE).then(c => c.put(e.request, clone));
return res;
}))
);
}
});