Files
wyndham-Condon/api-client.js
2026-08-02 13:33:13 +08:00

143 lines
5.2 KiB
JavaScript

(function initializeCondoApi(windowObject) {
"use strict";
const config = windowObject.CONDO_RUNTIME_CONFIG || {};
const requestedMode = new URLSearchParams(windowObject.location.search).get("mode");
const mode = requestedMode === "api"
? "api"
: requestedMode === "demo"
? "demo"
: config.defaultMode === "api"
? "api"
: "demo";
const periodYear = Number.isInteger(config.periodYear) ? config.periodYear : new Date().getUTCFullYear();
const baseUrl = new URL(config.apiBaseUrl || "http://127.0.0.1:3000", windowObject.location.origin);
if (!["http:", "https:"].includes(baseUrl.protocol)) {
throw new Error("CONDO API base URL must use HTTP or HTTPS.");
}
class CondoApiError extends Error {
constructor(message, { status = 0, code = "NETWORK_ERROR" } = {}) {
super(message);
this.name = "CondoApiError";
this.status = status;
this.code = code;
}
}
function apiUrl(path, query = {}) {
const url = new URL(path, `${baseUrl.href.replace(/\/$/, "")}/`);
Object.entries(query).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== "") {
url.searchParams.set(key, String(value));
}
});
return url;
}
async function request(path, { method = "GET", query, body, signal, timeoutMs = 12_000 } = {}) {
const controller = new AbortController();
const abortFromCaller = () => controller.abort(signal.reason);
if (signal?.aborted) abortFromCaller();
else signal?.addEventListener("abort", abortFromCaller, { once: true });
const timeout = windowObject.setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await windowObject.fetch(apiUrl(path, query), {
method,
credentials: "include",
headers: body === undefined ? { Accept: "application/json" } : {
Accept: "application/json",
"Content-Type": "application/json"
},
body: body === undefined ? undefined : JSON.stringify(body),
signal: controller.signal
});
const payload = await response.json().catch(() => null);
if (!response.ok) {
throw new CondoApiError(
payload?.error?.message || `API request failed with status ${response.status}`,
{ status: response.status, code: payload?.error?.code || "API_ERROR" }
);
}
return payload;
} catch (error) {
if (error instanceof CondoApiError) throw error;
const aborted = controller.signal.aborted;
throw new CondoApiError(
aborted ? "The API request timed out or was cancelled." : "The API is unavailable.",
{ code: aborted ? "REQUEST_ABORTED" : "NETWORK_ERROR" }
);
} finally {
windowObject.clearTimeout(timeout);
signal?.removeEventListener("abort", abortFromCaller);
}
}
async function collectPages(loader, options = {}) {
const first = await loader({ ...options, page: 1, pageSize: 100 });
const items = [...first.items];
const pageCount = Math.ceil(first.total / first.pageSize);
for (let page = 2; page <= pageCount; page += 1) {
const next = await loader({ ...options, page, pageSize: 100 });
items.push(...next.items);
}
return { items, total: first.total };
}
const client = Object.freeze({
getSession: options => request("auth/session", options),
login: (credentials, options = {}) => request("auth/login", {
...options,
method: "POST",
body: credentials
}),
logout: options => request("auth/logout", {
...options,
method: "POST"
}),
health: options => request("health", options),
listRoomTypes: options => request("room-types", options),
listOwnerAccounts: ({ q, roomType, year = periodYear, page = 1, pageSize = 100, ...options } = {}) => request("owner-accounts", {
...options,
query: { q, roomType, year, page, pageSize }
}),
listAllOwnerAccounts: ({ q, roomType, year = periodYear, ...options } = {}) => collectPages(
pageOptions => client.listOwnerAccounts(pageOptions),
{ q, roomType, year, ...options }
),
getOwnerAccount: (id, { year = periodYear, ...options } = {}) => request(`owner-accounts/${encodeURIComponent(id)}`, {
...options,
query: { year }
}),
listUsageRecords: ({ ownerAccountId, confirmationNo, usedRoomType, page = 1, pageSize = 100, ...options } = {}) => request("usage-records", {
...options,
query: { ownerAccountId, confirmationNo, usedRoomType, page, pageSize }
}),
listAllUsageRecords: ({ ownerAccountId, confirmationNo, usedRoomType, ...options } = {}) => collectPages(
pageOptions => client.listUsageRecords(pageOptions),
{ ownerAccountId, confirmationNo, usedRoomType, ...options }
),
getDashboard: ({ year = periodYear, ...options } = {}) => request("dashboard", {
...options,
query: { year }
}),
createUsageRecord: (input, options = {}) => request("usage-records", {
...options,
method: "POST",
body: input
})
});
windowObject.CONDO_API = Object.freeze({
runtime: Object.freeze({
mode,
apiBaseUrl: baseUrl.href.replace(/\/$/, ""),
periodYear
}),
client,
CondoApiError
});
})(window);