143 lines
5.2 KiB
JavaScript
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);
|