diff --git a/frontend/index.html b/frontend/index.html
index dc6ea9e..1fd1453 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1713,7 +1713,7 @@
// ===== Version =====
// Single source of truth for the PWA version: shown in the header and used
// by the server version check in pysimConnect().
-const SIMPLE_VERSION = '3.6.30';
+const SIMPLE_VERSION = '3.6.31';
document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION;
// ===== Tab switching =====
diff --git a/frontend/sw.js b/frontend/sw.js
index 028b3f4..faef1a0 100644
--- a/frontend/sw.js
+++ b/frontend/sw.js
@@ -1,4 +1,4 @@
-const CACHE = 'simple-v303';
+const CACHE = 'simple-v304';
const URLS = [
'index.html',
'help.html',
@@ -18,8 +18,15 @@ const URLS = [
];
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 => c.addAll(URLS))
+ 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();
});
diff --git a/frontend/tests/sw.test.js b/frontend/tests/sw.test.js
index 3b8e7c5..975702e 100644
--- a/frontend/tests/sw.test.js
+++ b/frontend/tests/sw.test.js
@@ -55,6 +55,23 @@ function navigateEvent(url) {
return event;
}
+test('install precaches every URL with cache: reload (no stale HTTP copies)', async () => {
+ const calls = [];
+ const { listeners, puts } = loadSW({
+ fetchImpl: async (url, init) => {
+ calls.push([String(url), init && init.cache]);
+ return { ok: true, status: 200 };
+ },
+ cacheMatch: async () => undefined,
+ });
+ const event = { waitUntil: p => { event.promise = p; } };
+ listeners.install(event);
+ await event.promise;
+ assert.ok(calls.length >= 10, String(calls.length));
+ assert.ok(calls.every(c => c[1] === 'reload'), JSON.stringify(calls[0]));
+ assert.strictEqual(puts.length, calls.length);
+});
+
test('offline navigation falls back to the cached index.html', async () => {
const index = new FakeResponse('html');
const { listeners } = loadSW({
diff --git a/pyproject.toml b/pyproject.toml
index 44eab9b..e65f739 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "pysim-simple-server"
-version = "3.6.30"
+version = "3.6.31"
description = "HTTP REST server wrapping pysim for the SIMple PWA"
requires-python = ">=3.8"
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py
index 4c28ecc..294f026 100644
--- a/pysim_simple_server/server.py
+++ b/pysim_simple_server/server.py
@@ -31,7 +31,7 @@ from osmocom.tlv import BER_TLV_IE
from osmocom.utils import rpad
-VERSION = '3.6.30'
+VERSION = '3.6.31'
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
@@ -5050,11 +5050,28 @@ class PysimHandler(BaseHTTPRequestHandler):
with open(fs_path, 'rb') as f:
data = f.read()
content_type = _STATIC_MIME.get(os.path.splitext(rel)[1].lower(), 'application/octet-stream')
+ try:
+ mtime = int(os.path.getmtime(fs_path))
+ except OSError:
+ mtime = 0
+ last_modified = (time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(mtime))
+ if mtime else '')
+ ims = self.headers.get('If-Modified-Since') if self.headers else None
+ if last_modified and ims == last_modified:
+ self.send_response(304)
+ self.send_header('Cache-Control', 'no-cache')
+ self.send_header('Last-Modified', last_modified)
+ self.end_headers()
+ return
self.send_response(200)
self.send_header('Content-Type', content_type)
self.send_header('Content-Length', str(len(data)))
- if rel in ('index.html', 'sw.js'):
- self.send_header('Cache-Control', 'no-cache')
+ # Every shell file revalidates - index.html/sw.js always did, but a
+ # stale style.css silently drops newly added styles (v3.6.31). The
+ # revalidation is cheap thanks to Last-Modified/304.
+ self.send_header('Cache-Control', 'no-cache')
+ if last_modified:
+ self.send_header('Last-Modified', last_modified)
self.end_headers()
self.wfile.write(data)