Add event-driven state sync with ETag optimization

This commit is contained in:
2026-02-18 19:58:57 +01:00
parent 5b921063ec
commit 3c7f3d2114
17 changed files with 493 additions and 30 deletions

View File

@@ -18,24 +18,53 @@ async function request(path, { method = "GET", body } = {}) {
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
let msg = `${res.status}`;
try {
const data = await res.json();
msg =
data.error || data.detail || data.title || JSON.stringify(data);
} catch {
/* ignore */
}
const err = new Error(msg);
err.status = res.status;
throw err;
}
if (!res.ok) throw await toApiError(res);
return res.status === 204 ? null : res.json();
}
async function requestState(ifNoneMatch) {
const headers = { ...defaultHeaders };
if (ifNoneMatch) headers["If-None-Match"] = ifNoneMatch;
const res = await fetch(withBase("/api/state"), {
method: "GET",
credentials: "same-origin",
headers,
});
if (res.status === 304) {
return {
notModified: true,
etag: res.headers.get("ETag"),
data: null,
};
}
if (!res.ok) throw await toApiError(res);
return {
notModified: false,
etag: res.headers.get("ETag"),
data: await res.json(),
};
}
async function toApiError(res) {
let msg = `${res.status}`;
try {
const data = await res.json();
msg = data.error || data.detail || data.title || JSON.stringify(data);
} catch {
/* ignore */
}
const err = new Error(msg);
err.status = res.status;
return err;
}
export const api = {
state: () => request("/api/state"),
state: (ifNoneMatch) => requestState(ifNoneMatch),
stateEventsUrl: () => withBase("/api/events/state"),
me: () => request("/api/me"),
authOptions: () => request("/api/auth/options"),
register: (payload) =>