diff --git a/public/api/catch-error.json b/public/api/catch-error.json
new file mode 100644
index 00000000..2c37fa2b
--- /dev/null
+++ b/public/api/catch-error.json
@@ -0,0 +1,5 @@
+{
+ "ok": false,
+ "info": "server not enabled",
+ "data": {}
+}
\ No newline at end of file
diff --git a/public/api/info.json b/public/api/info.json
new file mode 100644
index 00000000..2c37fa2b
--- /dev/null
+++ b/public/api/info.json
@@ -0,0 +1,5 @@
+{
+ "ok": false,
+ "info": "server not enabled",
+ "data": {}
+}
\ No newline at end of file
diff --git a/public/api/load-map.json b/public/api/load-map.json
new file mode 100644
index 00000000..2c37fa2b
--- /dev/null
+++ b/public/api/load-map.json
@@ -0,0 +1,5 @@
+{
+ "ok": false,
+ "info": "server not enabled",
+ "data": {}
+}
\ No newline at end of file
diff --git a/public/modules/io/save.js b/public/modules/io/save.js
index 25cd7493..ae4d28bd 100644
--- a/public/modules/io/save.js
+++ b/public/modules/io/save.js
@@ -14,6 +14,7 @@ async function saveMap(method) {
if (method === "dropbox") saveToDropbox(mapData, filename);
} catch (error) {
ERROR && console.error(error);
+ window.ServerAPI.postData("api/catch-error.json", error.toString(), {point: "saveMap", timestamp: Date.now()});
alertMessage.innerHTML = /* html */ `An error is occured on map saving. If the issue persists, please copy the message below and report it on ${link(
"https://github.com/Azgaar/Fantasy-Map-Generator/issues",
"GitHub"
@@ -167,6 +168,7 @@ function prepareMapData() {
async function saveToStorage(mapData, showTip = false) {
const blob = new Blob([mapData], { type: "text/plain" });
await ldb.set("lastMap", blob);
+ window.ServerAPI.postData("api/load-map.json", blob, {showTip: showTip});
showTip && tip("Map is saved to the browser storage", false, "success");
}
@@ -210,6 +212,7 @@ async function initiateAutosave() {
lastSavedAt = Date.now();
} catch (error) {
ERROR && console.error(error);
+ window.ServerAPI.postData("api/catch-error.json", error.toString(), {point: "initiateAutosave", timestamp: Date.now()});
}
}
diff --git a/public/modules/io/server-api.js b/public/modules/io/server-api.js
new file mode 100644
index 00000000..a1986735
--- /dev/null
+++ b/public/modules/io/server-api.js
@@ -0,0 +1,151 @@
+"use strict";
+
+window.ServerAPI = (function () {
+ const base = "./";
+ let ok = false;
+
+ function checkData(data){
+ if (data === null) {
+ return false;
+ }
+ if (typeof data.ok != "boolean" || typeof data.info != "string") {
+ return false;
+ }
+
+ return data.ok;
+ }
+
+
+ function buildUrl(path, params) {
+ const p = String(path || "");
+ const normalizedPath = p.startsWith("/") ? p : `/${p}`;
+
+ let url = `${base}${normalizedPath}`;
+
+ if (params && typeof params === "object") {
+ const qs = new URLSearchParams();
+
+ Object.keys(params).forEach((key) => {
+ const val = params[key];
+ if (val === undefined || val === null) return;
+
+ if (Array.isArray(val)) val.forEach((item) => qs.append(key, String(item)));
+ else qs.append(key, String(val));
+ });
+
+ const query = qs.toString();
+ if (query) url += (url.includes("?") ? "&" : "?") + query;
+ }
+
+ return url;
+ }
+
+ function toTextBlob(mapData) {
+ if (mapData instanceof Blob) return mapData;
+ return new Blob([mapData], { type: "text/plain" });
+ }
+
+ async function readJsonSafe(res) {
+ if (!res) return null;
+ if (res.status === 204) return null;
+
+ const headers = res.headers;
+ const ct = headers && headers.get ? (headers.get("content-type") || "") : "";
+ const looksJson = ct.includes("application/json") || ct.includes("+json");
+
+ if (looksJson) {
+ try {
+ const data = await res.json();
+ ok = checkData(data);
+ return data;
+
+ } catch (e) {
+ console.debug("ServerAPI JSON-parse failed:", e);
+ ok = false;
+ return null;
+ }
+ }
+
+ try {
+ const text = await res.text();
+ if (!text) return null;
+
+ try {
+ const data = JSON.parse(text);
+ ok = checkData(data);
+ return data;
+
+ } catch (e) {
+ ok = false;
+ return { ok: false, info: text };
+ }
+ } catch (e) {
+ console.debug("ServerAPI plain-parse failed:", e);
+ ok = false;
+ return null;
+ }
+ }
+
+ async function getJson(path, params) {
+ const url = buildUrl(path, params);
+
+ try {
+ const res = await fetch(url, {
+ method: "GET",
+ headers: { Accept: "application/json" }
+ });
+
+ if (!res.ok) {
+ const payload = await readJsonSafe(res);
+ console.debug("ServerAPI GET failed:", { url, status: res.status, payload });
+ return null;
+ }
+
+ return await readJsonSafe(res);
+ } catch (error) {
+ console.debug("ServerAPI GET error:", { url, error });
+ return null;
+ }
+ }
+
+ async function postData(path, mapData, params) {
+ if (!ok) {return null;}
+
+ const url = buildUrl(path, params);
+ const body = toTextBlob(mapData);
+
+ try {
+ const res = await fetch(url, {
+ method: "POST",
+ headers: { Accept: "application/json" },
+ body
+ });
+
+ if (!res.ok) {
+ const payload = await readJsonSafe(res);
+ console.debug("ServerAPI POST failed:", { url, status: res.status, payload });
+ return null;
+ }
+
+ return await readJsonSafe(res);
+ } catch (error) {
+ console.debug("ServerAPI POST error:", { url, error });
+ return null;
+ }
+ }
+
+ return {
+ checkData,
+ getJson,
+ postData
+ };
+})();
+
+(async () => {
+ const data = await window.ServerAPI.getJson("api/info.json", null);
+ if (!window.ServerAPI.checkData(data)) {
+ return;
+ }
+
+ console.log("[ServerAPI][enabled]: ", data.info);
+})();
\ No newline at end of file
diff --git a/src/index.html b/src/index.html
index 6173e519..ca8e7136 100644
--- a/src/index.html
+++ b/src/index.html
@@ -8555,6 +8555,7 @@
+