Add optional ServerAPI hooks + API stubs for server-side autosave#1306
Add optional ServerAPI hooks + API stubs for server-side autosave#1306SUNsung wants to merge 4 commits intoAzgaar:masterfrom
Conversation
✅ Deploy Preview for afmg ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Adds an optional “server integration” layer that can be enabled via /api/info.json, allowing the app to report autosaves/saves and client errors to a backend while preserving default client-only behavior when disabled.
Changes:
- Introduces
window.ServerAPIwithgetJson/postDatahelpers and an enablement probe against/api/info.json - Hooks save/autosave flows to optionally POST map data and error reports to
/api/* - Adds
/public/api/*.jsonplaceholder endpoints and wiresserver-api.jsintoindex.html
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/index.html | Loads the new ServerAPI module before existing IO modules |
| public/modules/io/server-api.js | Implements the optional server API wrapper and enablement probe |
| public/modules/io/save.js | Adds optional server POST hooks for saves and error paths |
| public/api/info.json | Placeholder probe response indicating server integration disabled by default |
| public/api/load-map.json | Placeholder “load/save map” endpoint response |
| public/api/catch-error.json | Placeholder “catch error” endpoint response |
| "use strict"; | ||
|
|
||
| window.ServerAPI = (function () { | ||
| const base = "./"; | ||
| let ok = false; | ||
|
|
There was a problem hiding this comment.
The module mixes 4-space indentation and spacing that differs from other public/modules/io/*.js files (e.g. public/modules/io/load.js:1+ uses 2-space indentation). Please reformat server-api.js to match the established style to reduce diff noise and keep consistency.
| if (!res.ok) { | ||
| const payload = await readJsonSafe(res); | ||
| console.error("ServerAPI GET failed:", { url, status: res.status, payload }); | ||
| return null; | ||
| } |
There was a problem hiding this comment.
getJson logs console.error on any non-2xx response. For the initial /api/info.json probe, a missing endpoint (404) is an expected “server disabled” state per PR description, so this will create noisy errors in console (and can break setups that treat console errors as failures). Consider treating 404 / network failures for the probe as a silent disabled state (or downgrade to console.info/debug).
| if (looksJson) { | ||
| try { | ||
| const data = await res.json(); | ||
| ok = checkData(data); | ||
| return data; | ||
|
|
||
| } catch (e) { | ||
| console.error("ServerAPI JSON-parse failed:", e); | ||
| ok = false; | ||
| return null; | ||
| } |
There was a problem hiding this comment.
ok is mutated on every response parse (ok = checkData(data)), meaning a single endpoint returning {ok:false,...} (or a transient parse failure) will globally disable all future postData calls. This makes server enablement fragile and can be flipped by unrelated requests; consider separating “server enabled” (set once from info.json) from per-request success, and avoid toggling the global flag based on arbitrary response bodies.
| async function postData(path, mapData, params) { | ||
| if (!ok) {return null;} | ||
|
|
||
| const url = buildUrl(path, params); | ||
| const body = toTextBlob(mapData); | ||
|
|
There was a problem hiding this comment.
postData hard-gates on the module-level ok flag. Because ok is only set asynchronously after the startup getJson("api/info.json") completes, any early calls to postData (e.g. user saving immediately on load) will be dropped even if the server is actually enabled. Consider exposing/awaiting a readiness promise or queueing calls until the enablement probe finishes.
public/modules/io/save.js
Outdated
| if (method === "dropbox") saveToDropbox(mapData, filename); | ||
| } catch (error) { | ||
| ERROR && console.error(error); | ||
| await window.ServerAPI.postData("api/catch-error.json", error, {point: "saveMap", timestamp: Date.now()}); |
There was a problem hiding this comment.
error is passed directly into ServerAPI.postData. In server-api.js, non-Blob inputs are turned into a text Blob via new Blob([mapData]), so an Error object will typically serialize to "[object Error]" and lose message/stack. Serialize the error explicitly (e.g., {message, stack, name} or parseError(error)) before sending.
| await window.ServerAPI.postData("api/catch-error.json", error, {point: "saveMap", timestamp: Date.now()}); | |
| const serializedError = { | |
| name: error && error.name, | |
| message: error && error.message, | |
| stack: error && error.stack, | |
| toString: String(error) | |
| }; | |
| await window.ServerAPI.postData( | |
| "api/catch-error.json", | |
| JSON.stringify(serializedError), | |
| {point: "saveMap", timestamp: Date.now()} | |
| ); |
| { | ||
| "ok": false, | ||
| "info": "server not enabled", | ||
| "data": {} | ||
| } No newline at end of file |
There was a problem hiding this comment.
These /public/api/*.json files are static assets. They can be fetched via GET, but they will not act as stub handlers for POST requests to api/load-map.json / api/catch-error.json on most static/dev servers (typically 404/405), so the “stub endpoint” behavior described won’t be testable without an actual backend/proxy or dev middleware. Consider adding a Vite dev-server middleware/mock for /api/* POSTs or clarifying the intended usage.
| { | ||
| "ok": false, | ||
| "info": "server not enabled", | ||
| "data": {} | ||
| } No newline at end of file |
There was a problem hiding this comment.
Like load-map.json, this is a static JSON asset and won’t behave as a stub POST endpoint on most servers, so ServerAPI.postData("api/catch-error.json", ...) can’t be exercised without a backend/proxy or dev middleware. Consider adding a dev mock handler for POST /api/* or documenting that these files are GET-only placeholders.
public/modules/io/save.js
Outdated
| async function saveToStorage(mapData, showTip = false) { | ||
| const blob = new Blob([mapData], { type: "text/plain" }); | ||
| await ldb.set("lastMap", blob); | ||
| await window.ServerAPI.postData("api/load-map.json", blob, {showTip: showTip}); |
There was a problem hiding this comment.
saveToStorage now awaits a network call (ServerAPI.postData). When server mode is enabled, this makes local saves/autosaves block on network latency/failures, which can noticeably slow autosave and introduce UI timing issues. Consider making the server hook fire-and-forget (no await) or adding a short timeout so local persistence isn’t delayed by the server integration.
| await window.ServerAPI.postData("api/load-map.json", blob, {showTip: showTip}); | |
| if (window.ServerAPI && typeof window.ServerAPI.postData === "function") { | |
| // fire-and-forget server sync so local saves/autosaves are not blocked by network latency | |
| window.ServerAPI | |
| .postData("api/load-map.json", blob, {showTip: showTip}) | |
| .catch(error => { | |
| ERROR && console.error(error); | |
| }); | |
| } |
public/modules/io/save.js
Outdated
| lastSavedAt = Date.now(); | ||
| } catch (error) { | ||
| ERROR && console.error(error); | ||
| await window.ServerAPI.postData("api/catch-error.json", error, {point: "initiateAutosave", timestamp: Date.now()}); |
There was a problem hiding this comment.
Same issue as the saveMap catch: posting the raw error object will not reliably include useful details (often becomes "[object Error]"). Serialize the error fields you need (message/stack/name + context) before sending to the server.
| await window.ServerAPI.postData("api/catch-error.json", error, {point: "initiateAutosave", timestamp: Date.now()}); | |
| const errorPayload = { | |
| name: error && error.name, | |
| message: error && error.message, | |
| stack: error && error.stack, | |
| stringified: String(error) | |
| }; | |
| await window.ServerAPI.postData( | |
| "api/catch-error.json", | |
| errorPayload, | |
| {point: "initiateAutosave", timestamp: Date.now()} | |
| ); |
| <script defer src="modules/coa-renderer.js?v=1.99.00"></script> | ||
| <script defer src="libs/rgbquant.min.js"></script> | ||
| <script defer src="libs/jquery.ui.touch-punch.min.js"></script> | ||
| <script defer src="modules/io/server-api.js"></script> |
There was a problem hiding this comment.
server-api.js is included without the cache-busting ?v=... query used for other internal modules in this script block, which can leave clients with a stale cached copy after deploys. Add a version/hash parameter consistent with the surrounding modules/* scripts (and update it when changing the file).
Description
This PR introduces an optional server integration layer without changing the default (fully client-side) behavior.
public/modules/io/server-api.jsthat exposes a small, vanilla API viawindow.ServerAPI(getJson,postData,checkData)./api(e.g.info.json,load-map.json,catch-error.json) to enable seamless local/proxy setups.saveToStorage→ POSTapi/load-map.json)initiateAutosavecatch → POSTapi/catch-error.json)saveMapcatch → POSTapi/catch-error.json)server-api.jsinindex.html.By default nothing changes: if
/api/info.jsonis not available (or reports disabled),ServerAPIbecomes a no-op and the app works exactly as before.Motivation: make it easy to add server support later by simply routing
/api/*to a backend. For now it allows catching autosaves and error reports; in future it can be extended to fetch server-provided presets/configs for City Generator / Village Generator to enable a fully local ecosystem.Type of change
Versioning