201b0df278
New 'Profiler' sub-tab in card-reader view: - Named rulesets (profiles) persisted in localStorage; each has an ordered list of filesystem rules (type 'file', extensible to OTA/TAR checks later) - List page: New profile / Profile from card / Import profile + per-profile Edit / Check / Export / Delete - Editor page: inline rule fields (path, file type, size, record length, record count, contents with exact/mask/'?'-wildcard modes) - Results page: sequential rule checks with live progress and a pass/fail report (existence + FCI attributes + content match) - 'Profile from card' scans the equipped card; creates a rule only for files that exist (FCI present). A scan-options dialog lets the user skip contents of frequently-overwritten dynamic files (LOCI/PSLOCI/EPSLOCI/5GS3GPPLOCI/ Keys/KeysPS/SMS/Kc/KcGPRS/LOCIGPRS/CBMID/SMSS), checked by default - ADF-rooted paths use the AID; paths resolve via a new server _select_path Server: - _select_path() resolves MF/ADF-AID-rooted paths (pySim can't select ADF by AID) - /api/select and /api/read accept 'path'; /api/select returns file_size, record_len, num_of_rec Also fix: silent SELECT (P2=0x0C) no longer emits a trailing Le byte (chain builder) - matches ETSI TS 102 221 silent-select behavior. Tests: +profiler.test.js; frontend 117 pass, Python 61 pass. SW cache v37.
56 lines
1.4 KiB
JavaScript
56 lines
1.4 KiB
JavaScript
const CACHE = 'otaman-v37';
|
|
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())
|
|
);
|
|
});
|
|
|
|
self.addEventListener('fetch', e => {
|
|
if (!e.request.url.startsWith('http')) return;
|
|
if (new URL(e.request.url).pathname.startsWith('/api/')) return; // live data, never cache
|
|
if (e.request.method !== 'GET') return;
|
|
const isNavigate = e.request.mode === 'navigate' || e.request.url.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))
|
|
);
|
|
} 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;
|
|
}))
|
|
);
|
|
}
|
|
}); |