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:
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
File diff suppressed because it is too large.
Load diff
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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 |
@@ -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))
|
||||
);
|
||||
});
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user