commit f275d39dfbdc29ab7d06a2f055dd2d86cc129d0b
parent 0d21f3581ec73a68ebf34da01e83b0e1fc07d67d
Author: Hunter
Date: Wed, 29 Apr 2026 23:54:47 -0400
freeze cache after first install
Diffstat:
| M | build.py | | | 94 | ++++++++++++++++++++----------------------------------------------------------- |
1 file changed, 24 insertions(+), 70 deletions(-)
diff --git a/build.py b/build.py
@@ -76,33 +76,6 @@ def get_background_color():
return "#080a0c"
-def _next_cache_name(app_name, manifest_path):
- """Determine the next cache name by incrementing the version in an existing manifest.
-
- If manifest.json doesn't exist or has no versioned cache_name, returns "{app_name}-v1".
- If it already has e.g. "heaven-v1", returns "heaven-v1.1".
- If it already has e.g. "heaven-v1.3", returns "heaven-v1.4".
- """
- if manifest_path.exists():
- try:
- with open(manifest_path, 'r', encoding='utf-8') as f:
- existing = json.load(f)
- old_cache = existing.get("cache_name", "")
- # Match pattern like "appname-v1" or "appname-v1.3"
- match = re.match(r'^(.+)-v(\d+)(?:\.(\d+))?$', old_cache)
- if match:
- prefix = match.group(1)
- major = int(match.group(2))
- minor = int(match.group(3)) if match.group(3) is not None else 0
- # If app was renamed, start fresh with new name
- if prefix != app_name:
- return f"{app_name}-v1"
- return f"{app_name}-v{major}.{minor + 1}"
- except (json.JSONDecodeError, KeyError):
- pass
- return f"{app_name}-v1"
-
-
def build_pwa(app_name=None, base_path=None):
"""Generate manifest.json and service-worker.js based on tracks.json
@@ -116,7 +89,7 @@ def build_pwa(app_name=None, base_path=None):
# Derived values
short_name = app_name
- cache_name = _next_cache_name(app_name, SCRIPT_DIR / "manifest.json")
+ cache_name = app_name
app_description = f"{app_name}"
print("Building PWA files...")
@@ -203,8 +176,10 @@ const basePath = getBasePath();
// Static files to cache on install
const STATIC_FILES = {static_files_js};
-// Install event - cache static resources
-// Audio files will be cached by the main app's blob preloading system
+// Install event - cache static resources only if not already cached.
+// This makes installs immutable: once a file is in the cache, redeploys
+// will not overwrite it, so the app stays frozen at its first-installed version.
+// Audio files will be cached by the main app's blob preloading system.
self.addEventListener('install', (event) => {{
console.log('Service Worker installing...', 'Base path:', basePath);
event.waitUntil(
@@ -213,59 +188,38 @@ self.addEventListener('install', (event) => {{
if (url === './') return new URL(basePath, self.location.href).href;
return new URL(url, new URL(basePath, self.location.href)).href;
}});
- console.log('Caching', absoluteUrls.length, 'static resources');
- // Cache files individually with better error handling
- // Using Promise.allSettled to continue even if some fail
return Promise.allSettled(
absoluteUrls.map(url =>
- fetch(url, {{ cache: 'no-cache' }})
- .then(response => {{
- if (!response.ok) {{
- throw new Error(`HTTP error! status: ${{response.status}}`);
- }}
- return cache.put(url, response);
- }})
- .then(() => console.log('✓ Cached:', url))
- .catch(err => {{
- console.error('✗ Failed to cache:', url, err);
- throw err;
- }})
+ cache.match(url).then(existing => {{
+ if (existing) {{
+ console.log('• Already cached, skipping:', url);
+ return;
+ }}
+ return fetch(url, {{ cache: 'no-cache' }})
+ .then(response => {{
+ if (!response.ok) {{
+ throw new Error(`HTTP error! status: ${{response.status}}`);
+ }}
+ return cache.put(url, response);
+ }})
+ .then(() => console.log('✓ Cached:', url))
+ .catch(err => {{
+ console.error('✗ Failed to cache:', url, err);
+ throw err;
+ }});
+ }})
)
).then(results => {{
const failed = results.filter(r => r.status === 'rejected');
- const succeeded = results.filter(r => r.status === 'fulfilled');
- console.log(`Cached ${{succeeded.length}}/${{results.length}} static resources`);
- if (failed.length > 0) {{
- console.warn(`Failed to cache ${{failed.length}} resources`);
- }}
+ console.log(`Install complete: ${{results.length - failed.length}}/${{results.length}} ok`);
}});
- }}).then(() => {{
- console.log('Service Worker installation complete');
- return self.skipWaiting();
}}).catch(error => {{
console.error('Service Worker installation failed:', error);
}})
);
}});
-// Activate event - clean up old caches
-self.addEventListener('activate', (event) => {{
- console.log('Service Worker activating...');
- event.waitUntil(
- caches.keys().then((cacheNames) => {{
- return Promise.all(
- cacheNames.map((cacheName) => {{
- if (cacheName !== CACHE_NAME) {{
- console.log('Deleting old cache:', cacheName);
- return caches.delete(cacheName);
- }}
- }})
- );
- }}).then(() => self.clients.claim())
- );
-}});
-
// Fetch event - cache first, network fallback
self.addEventListener('fetch', (event) => {{
// Ignore non-http(s) requests like blob: URLs, data: URLs, chrome-extension:, etc.