eb83238ae7
Client-side, offline decoder layer (ported from simtrace-analyser, verified against the pinned specs and pySim test vectors): - file manager Decoded pill: decoded field table instead of the raw pySim JSON dump (per-record fields for record files); the server's read_*_decoded JSON stays behind a 'pySim JSON (server)' disclosure; switching Raw/Decoded re-reads the open file - snapshot view: decoded field block for transparent files and a per-record decoded summary for record files - profile check / snapshot comparison report: content mismatches of files with a decoder render as a per-field decoded diff (only the differing fields, expected | actual); the raw expected/actual pair is kept when the decoded values match - coverage: ICCID, IMSI/IST, UST/SST, LI/PL, SPN, PLMNsel/PLMNwAcT/ OPLMNwAcT/HPLMNwAcT, OPL, SPDI, PNN, LOCI/PSLOCI/EPSLOCI/EPSNSC/LOCIGPRS, Kc/KcGPRS, ACC, PHASE, CBMI/CBMID/CBMIR, ECC, AD, ACL, SMSP, SMS, ARR, DIR, IMPI/DOMAIN/IMPU, ADN/FDN/SDN/MSISDN/LND/BDN, EXT1-5, SUME (TS 51.011 §10.3.34); unknown files fall back to raw - new frontend/tests/ef_decode.test.js (14 tests); help EN/RU + READMEs document the decoded views; version 2.4.0, SW cache simple-v191 - also fix the README/RUS snapshot-comparison text: comparison is always exact (no content masking)
70 lines
1.8 KiB
JavaScript
70 lines
1.8 KiB
JavaScript
const CACHE = 'simple-v191';
|
|
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',
|
|
];
|
|
|
|
self.addEventListener('install', e => {
|
|
e.waitUntil(
|
|
caches.open(CACHE).then(c => c.addAll(URLS))
|
|
);
|
|
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;
|
|
}))
|
|
);
|
|
}
|
|
}); |