Merge simtrace-analyser PWA into frontend/ (monorepo); serve it same-origin

- frontend/: simtrace-analyser PWA (index.html, sw.js, manifest.json,
  icons, tests/index.test.html) copied in fresh
- server.py: static file serving (_STATIC_MIME, _serve_static) with
  path-traversal guard; injects window.SIMTRACE_EMBEDDED='1' into
  index.html so the PWA defaults to a relative API base
- server __main__: --web-dir (default <repo>/frontend)
- frontend: embedded same-origin mode; sw.js relative precache paths +
  cache v4; GitHub link + version-warn point at simtrace2-pysniff
- version 1.1.0 everywhere (__version__, pyproject, PWA_VERSION,
  manifest); MIN_SERVER_VERSION stays 1.0.0
- README/scripts/.gitignore updated

Tests: 107.
This commit is contained in:
catarrh committed 2026-08-14 16:26:28 +03:00
1 parent 8b7b10b8fa
commit b900a8a3e1
16 files changed
+1707 -18

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+1451
View File
File diff suppressed because it is too large. Load diff
+14
View File
@@ -0,0 +1,14 @@
{
"name": "SIMtrace Analyser",
"short_name": "SIMtrace",
"description": "PWA frontend for browsing SIMtrace2 APDU traces",
"version": "1.1.0",
"start_url": "index.html",
"display": "standalone",
"background_color": "#0f172a",
"theme_color": "#0f172a",
"icons": [
{ "src": "icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "icon-512.png", "sizes": "512x512", "type": "image/png" }
]
}
+45
View File
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
shape-rendering="geometricPrecision"
text-rendering="geometricPrecision"
image-rendering="optimizeQuality"
fill-rule="evenodd"
clip-rule="evenodd"
viewBox="0 0 507 511.73"
version="1.1"
id="svg1"
sodipodi:docname="sim-analyser.svg"
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#3b82f6"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="1.0446382"
inkscape:cx="206.29151"
inkscape:cy="241.23184"
inkscape:window-width="1707"
inkscape:window-height="1007"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M224.639 0c59.668 0 116.663 23.625 158.855 65.816 58.277 58.264 79.952 144.187 56.478 223.076-5.44 18.308-13.169 35.634-22.82 51.652l87.013 94.825c4.008 4.376 3.736 11.192-.627 15.2l-63.894 58.345c-4.362 4.008-11.15 3.694-15.145-.668l-83.237-91.553c-35.253 21.471-75.345 32.676-116.623 32.676-59.681 0-116.678-23.624-158.869-65.816a224.953 224.953 0 01-48.762-72.959C-17.808 226.73 1.985 129.628 65.798 65.843 107.907 23.611 165.012 0 224.639 0zM351.39 97.92C186.06-67.451-70.51 183.202 97.833 351.436c51.257 51.134 128.646 66.579 195.362 38.974 67.083-27.864 110.692-93.107 110.692-165.739 0-24.32-4.812-47.508-13.523-68.542A180.032 180.032 0 00351.39 97.92z"
fill="#3b82f6" id="path1" />
<path
style="fill:#3b82f6;stroke-width:4.8596"
d="m 128.04356,356.87391 c -13.07564,-2.62909 -24.733,-12.68507 -29.511929,-25.45795 -2.596909,-6.94081 -2.6389,-8.86274 -2.29299,-104.97903 l 0.35231,-97.90875 3.788399,-6.8388 c 4.61261,-8.3267 10.50929,-13.68042 19.93961,-18.10361 l 7.13036,-3.34441 h 97.07534 97.0753 l 7.13037,3.34441 c 9.43036,4.42319 15.32699,9.77691 19.93961,18.10361 l 3.78839,6.8388 0.35231,97.90875 c 0.34602,96.11629 0.30373,98.03822 -2.29294,104.97903 -3.50512,9.36825 -10.87912,17.4984 -20.10814,22.17001 l -7.52385,3.80852 -95.14666,0.19674 c -52.33067,0.10843 -97.19362,-0.2148 -99.69555,-0.71758 z m 46.33625,-48.98143 v -28.28685 h -28.28682 -28.28687 v 22.40224 22.40224 l 3.47213,4.12639 c 5.66491,6.73236 9.07721,7.53679 32.20788,7.59246 l 20.89368,0.0505 z m 78.43168,0.0486 v -28.23905 l -28.28799,-0.36934 -28.288,-0.36933 0.001,28.60829 0.001,28.60828 h 28.28682 28.28683 z m 67.88799,26.4325 c 1.90962,-0.99356 4.91852,-3.35297 6.68645,-5.24315 3.14502,-3.3625 3.22311,-3.93424 3.61699,-26.48038 l 0.40286,-23.04367 -24.5107,-0.16089 c -13.48091,-0.0884 -25.3786,-0.16089 -26.43935,-0.16089 -6.0649,0 -5.78596,-1.37936 -5.78596,28.60828 v 28.28683 h 21.27901 c 15.77042,0 22.17779,-0.46749 24.75093,-1.80651 z M 331.24322,228.81866 V 199.88895 H 255.9675 180.69175 l -3.15598,-3.15595 -3.15596,-3.15602 v -36.05986 -36.05988 h -20.61354 c -22.40511,0 -26.44906,0.88289 -32.10286,7.0087 l -3.21436,3.4828 -0.35671,62.89983 -0.3567,62.89984 h 106.7539 106.75388 z m -78.43173,-78.43245 v -28.92976 h -28.28683 -28.28682 v 28.07258 c 0,15.43993 0.38586,28.45827 0.85719,28.92974 0.47138,0.4714 13.20053,0.85713 28.28682,0.85713 h 27.42964 z m 78.43173,6.61896 c 0,-21.05484 -0.16425,-22.55671 -2.91862,-26.67962 -5.20034,-7.78442 -9.192,-8.86481 -32.76137,-8.86705 l -20.89371,-0.002 v 28.07259 c 0,15.43993 0.38585,28.45827 0.85718,28.92974 0.47138,0.47139 13.20053,0.85712 28.28683,0.85712 h 27.42969 z"
id="path1-5"
sodipodi:nodetypes="csscsscccsscccsccccccccccscccccccccccccscccsscsccccccccccssccccccccccscsccscccscscs" />
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

+45
View File
@@ -0,0 +1,45 @@
const CACHE = 'simtrace-analyser-v4';
const PRECACHE = [
'index.html',
'manifest.json',
'icon-192.png',
'icon-512.png',
'favicon.png',
'sim-analyser.svg',
];
self.addEventListener('install', e => {
self.skipWaiting();
e.waitUntil(
caches.open(CACHE)
.then(cache => cache.addAll(PRECACHE))
.then(() => console.log('SW: precache done'))
.catch(err => console.error('SW: precache failed', err))
);
});
self.addEventListener('activate', e => {
e.waitUntil(
caches.keys()
.then(keys => Promise.all(
keys.filter(k => k !== CACHE).map(k => caches.delete(k))
))
.then(() => self.clients.claim())
.then(() => console.log('SW: activated, controlling clients'))
);
});
self.addEventListener('fetch', e => {
// Network-first: the local server is always up, so always serve fresh
// content and fall back to cache only when offline.
e.respondWith(
fetch(e.request)
.then(r => {
const clone = r.clone();
caches.open(CACHE).then(c => c.put(e.request, clone));
return r;
})
.catch(() => caches.match(e.request))
);
});
+59
View File
@@ -0,0 +1,59 @@
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<title>Tests — SIMtrace Analyser</title>
<body>
<h1>HTML Validator Test</h1>
<p>Page loads cleanly with no JS errors — check browser console.</p>
<script>
const tests = [];
// Test INS name mapping
function testInsNames() {
const n = (h) => ({'a4':'SELECT','b0':'READ BINARY','c2':'ENVELOPE','14':'TERMINAL RESPONSE',
'88':'AUTHENTICATE','f2':'STATUS','12':'FETCH','20':'VERIFY'}[h] || '?' + h);
console.assert(n('a4') === 'SELECT', 'INS a4 -> SELECT');
console.assert(n('c2') === 'ENVELOPE', 'INS c2 -> ENVELOPE');
console.assert(n('14') === 'TERMINAL RESPONSE', 'INS 14 -> TERMINAL RESPONSE');
console.assert(n('ff') !== '?', 'Unknown INS returns placeholder');
tests.push('testInsNames: OK');
}
// Test elapsed formatting
function testFmtElapsed() {
const fmt = (e) => {
const s = Math.floor(e);
const ms = Math.round((e - s) * 1000);
return String(s).padStart(4, '0') + '.' + String(ms).padStart(3, '0');
};
console.assert(fmt(0) === '0000.000', '0 -> 0000.000');
console.assert(fmt(1.5) === '0001.500', '1.5 -> 0001.500');
console.assert(fmt(12.034) === '0012.034', '12.034 -> 0012.034');
tests.push('testFmtElapsed: OK');
}
// Test data formatter
function testFmtData() {
const fmt = (hex, limit) => {
const pairs = (hex.match(/.{1,2}/g) || []);
const shown = pairs.slice(0, limit || 32);
let s = shown.join(' ');
if (pairs.length > (limit || 32)) s += ' ...';
return s;
};
const r = fmt('a0a40000023f00', 16);
console.assert(r === 'a0 a4 00 00 02 3f 00', 'Short data');
const r2 = fmt('00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff', 4);
console.assert(r2.startsWith('00 11 22 33 ...'), 'Long data truncated');
tests.push('testFmtData: OK');
}
testInsNames();
testFmtElapsed();
testFmtData();
document.body.innerHTML += `<pre>${tests.join('\n')}</pre>
<p style="color:green; font-weight:bold;">All ${tests.length} tests passed.</p>`;
</script>
</body>
</html>