diff --git a/test/api-client.test.js b/test/api-client.test.js new file mode 100644 index 0000000..aa8d426 --- /dev/null +++ b/test/api-client.test.js @@ -0,0 +1,28 @@ +const test = require("node:test"); +const assert = require("node:assert/strict"); +const { createApiClient } = require("../web/api-client"); + +test("API client surfaces a backend error message", async () => { + const api = createApiClient(async () => ({ + ok: false, + status: 400, + json: async () => ({ error: "TikTok login is already in progress" }), + })); + + await assert.rejects( + api.post("/api/tiktok/login"), + /TikTok login is already in progress/ + ); +}); + +test("API client falls back to the HTTP status for non-JSON errors", async () => { + const api = createApiClient(async () => ({ + ok: false, + status: 502, + json: async () => { + throw new SyntaxError("Unexpected token"); + }, + })); + + await assert.rejects(api.get("/api/status"), /API Error: 502/); +}); diff --git a/web/api-client.js b/web/api-client.js new file mode 100644 index 0000000..1962e78 --- /dev/null +++ b/web/api-client.js @@ -0,0 +1,35 @@ +(function exposeApiClient(root, factory) { + const createApiClient = factory(); + + if (typeof module === "object" && module.exports) { + module.exports = { createApiClient }; + } else { + root.createApiClient = createApiClient; + } +})(typeof globalThis !== "undefined" ? globalThis : this, function apiClientFactory() { + return function createApiClient(fetchImpl = globalThis.fetch) { + async function parse(response) { + const data = await response.json().catch(() => null); + if (!response.ok) { + throw new Error(data?.error || `API Error: ${response.status}`); + } + return data; + } + + return { + async get(endpoint) { + return parse(await fetchImpl(endpoint)); + }, + async post(endpoint, body) { + const options = { + method: "POST", + headers: { "Content-Type": "application/json" }, + }; + if (body !== undefined) { + options.body = JSON.stringify(body); + } + return parse(await fetchImpl(endpoint, options)); + }, + }; + }; +}); diff --git a/web/app.js b/web/app.js index 6a91ee2..9384cd8 100644 --- a/web/app.js +++ b/web/app.js @@ -1,22 +1,4 @@ -const API = { - async get(endpoint) { - const res = await fetch(endpoint); - if (!res.ok) throw new Error(`API Error: ${res.status}`); - return res.json(); - }, - async post(endpoint, body) { - const options = { - method: "POST", - headers: { "Content-Type": "application/json" }, - }; - if (body !== undefined) { - options.body = JSON.stringify(body); - } - const res = await fetch(endpoint, options); - if (!res.ok) throw new Error(`API Error: ${res.status}`); - return res.json(); - }, -}; +const API = createApiClient(); function escapeHtml(value) { return String(value ?? "") diff --git a/web/index.html b/web/index.html index 23d27f0..91f00d7 100644 --- a/web/index.html +++ b/web/index.html @@ -828,6 +828,7 @@