Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
*.pyc
web/game.zip
web/game_version.txt
screenshots/*.png
saves/*/*.json
saves/*/rooms/*.json
Expand Down
6 changes: 5 additions & 1 deletion src/mapimage/mapImageGenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ def mapImageExists(self):
def getExistingMapImage(self):
mapImagePath = self.getMapImagePath()
_logger.debug("loading existing map image", path=mapImagePath)
return Image.open(mapImagePath)
try:
return Image.open(mapImagePath)
except Exception:
_logger.warning("map image unreadable, recreating", path=mapImagePath)
return self.createNewMapImage()

def createNewMapImage(self):
_logger.debug("creating new map image")
Expand Down
12 changes: 11 additions & 1 deletion web/build_zip.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

Bundles the Python source tree, schemas, and config into a single zip that
the browser's Pyodide Worker downloads and unpacks into its virtual filesystem.

Also writes web/game_version.txt containing the SHA-256 of the zip so the
Worker can request game.zip?v=<hash> — a content-addressed URL that forces
the browser to fetch a new zip whenever the source changes.
"""
import hashlib
import os
import zipfile

Expand All @@ -26,4 +31,9 @@
if os.path.exists(_web_file):
z.write(_web_file, _web_file)

print("Built web/game.zip")
with open("web/game.zip", "rb") as _f:
_digest = hashlib.sha256(_f.read()).hexdigest()
with open("web/game_version.txt", "w") as _f:
_f.write(_digest)

print(f"Built web/game.zip ({_digest[:12]})")
11 changes: 10 additions & 1 deletion web/game-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,16 @@ self.onmessage = async (e) => {

self.postMessage({ type: 'status', msg: 'Downloading game…' });

const resp = await fetch('/web/game.zip');
// Fetch the content-hash written by build_zip.py so the zip URL is
// unique per build. The browser may cache game.zip?v=<hash> freely;
// a new deploy changes the hash, forcing a fresh download.
let zipUrl = '/web/game.zip';
try {
const verResp = await fetch('/web/game_version.txt', { cache: 'no-store' });
if (verResp.ok) zipUrl += '?v=' + (await verResp.text()).trim();
} catch (_) { /* no version file — fall back to unversioned URL */ }

const resp = await fetch(zipUrl);
if (!resp.ok) throw new Error(`game.zip fetch failed: ${resp.status}`);
const buf = await resp.arrayBuffer();
pyodide.FS.mkdir('/game');
Expand Down
8 changes: 8 additions & 0 deletions web/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ def end_headers(self):
self.send_header("Cross-Origin-Opener-Policy", "same-origin")
self.send_header("Cross-Origin-Embedder-Policy", "require-corp")
self.send_header("Cross-Origin-Resource-Policy", "same-origin")
path = getattr(self, "path", "") or ""
bare = path.split("?")[0]
if bare.endswith("game_version.txt"):
# Never cache — the Worker fetches this to get the current zip hash.
self.send_header("Cache-Control", "no-store")
elif any(bare.endswith(ext) for ext in (".zip", ".js")):
# Revalidate on every request so a new deploy is always picked up.
self.send_header("Cache-Control", "no-cache")
super().end_headers()

def log_message(self, *args):
Expand Down
Loading