67 lines
3.6 KiB
Org Mode
67 lines
3.6 KiB
Org Mode
:PROPERTIES:
|
|
:ID: 41466dc4-91a5-4578-bce3-7b4a0ba813d2
|
|
:END:
|
|
|
|
#+title: homey/emulatorjs
|
|
#+filetags: :project: :knowledge: :emulatorjs:
|
|
|
|
* homey/emulatorjs
|
|
|
|
Self-hosted game emulator running on games.zakobar.com. Python sidecar at
|
|
[[file:~/projects/selfhosted/homey/modules/services/emulatorjs-server.py][modules/services/emulatorjs-server.py]], NixOS module at
|
|
[[file:~/projects/selfhosted/homey/modules/services/emulatorjs.nix][modules/services/emulatorjs.nix]].
|
|
|
|
** What works (stable baseline)
|
|
|
|
- Game library launcher with search, favourites, thumbnails
|
|
- Per-user server-side saves (GET/PUT /saves/<name>, keyed by Authelia Remote-User header)
|
|
- Manual Save button on player page (Save → Saving... → Saved! feedback)
|
|
- Auto-save every 30 seconds via setInterval
|
|
- sendBeacon save on pagehide (iOS-reliable exit save)
|
|
- pullSave on EJS_onGameStart loads server save into EmulatorJS FS
|
|
|
|
** Offline/PWA mode — designed but not yet working
|
|
|
|
Intended design (revert this when debugging is done):
|
|
|
|
*** Service Worker (SW_JS constant, served at /sw.js)
|
|
- Cache name versioned: emulatorjs-vN. Bump N to wipe stale cache on deploy.
|
|
- /static/ and /roms/<name>: cache-first, arrayBuffer() for caching (NOT clone() — iOS Safari bug: cloned response body is empty)
|
|
- /play/<name>: network-first (so deployments take effect). Cache as offline fallback.
|
|
- /roms/, /favorites, /: network-first, cache fallback
|
|
- /saves/: pass-through to network only (page handles offline)
|
|
- /sw.js: never cached
|
|
- Install: cache.addAll(['/static/loader.js', '/']); skipWaiting() outside waitUntil
|
|
- Activate: delete old cache names, clients.claim()
|
|
|
|
*** IndexedDB offline save queue (_IDB_JS shared constant)
|
|
- DB name: 'emulatorjs', version 1, store: 'pending-saves' (keyPath: 'name')
|
|
- openIDB() MUST handle onblocked or Promise hangs indefinitely
|
|
- idbPut/idbGet/idbGetAll/idbDelete helpers
|
|
- syncPendingSaves(): flush IDB queue to server when online
|
|
|
|
*** Player page flow
|
|
- preflightCheck(): check cache for ROM, block if offline + ROM not cached, else return true
|
|
- syncPendingSaves() runs inside EJS_onGameStart (NOT in startup path — if in startup, IDB blocks loader.js)
|
|
- pushSave(): PUT to server if online, else idbPut to IDB queue
|
|
- pullSave(): fetch from server if online, else idbGet from IDB
|
|
|
|
*** Launcher additions
|
|
- updateCacheStatus(): green dots on cached game cards (checks cache for /roms/<name>)
|
|
- syncPendingSaves() on 'online' event
|
|
|
|
** Known bugs / gotchas
|
|
|
|
- Python string.Template: \n inside triple-quoted template string is a LITERAL NEWLINE in generated JS → SyntaxError (unterminated string literal). Never use \n inside JS string literals embedded in the Python template. Use \\n or restructure.
|
|
- iOS Safari response.clone() bug: cloned response body is empty (0 bytes). Always use response.arrayBuffer() + new Response(body, ...) for SW caching.
|
|
- SW cache-first for /play/ pages means deployments don't take effect until cache is cleared. Use network-first for HTML pages.
|
|
- Cache version bump alone is not enough if the browser serves the old page (from SW cache) before the new SW activates. User must clear site data OR the new SW must activate and claim before any navigation.
|
|
- Python BaseHTTPRequestHandler is single-threaded. Heavy ROM downloads can block other requests.
|
|
- preflightCheck() must call its first setDbg equivalent BEFORE any await, or the UI appears frozen.
|
|
|
|
** Ports / URLs
|
|
- Port 8085, Caddy vhost games.zakobar.com, Authelia one_factor priority 57
|
|
- Saves: /mnt/data/emulatorjs/saves/<user>/<name>.sav
|
|
- ROMs: /mnt/data/emulatorjs/roms/
|
|
- Static assets: Nix store (emulatorjsAssets = EmulatorJS v4.2.3 from GitHub)
|