import { addonsJs, bootGuardJs, i18nJs, localeEn, localeZh, localeEnJs, localeZhJs, tasksJs, calendarJs, i18nRenderersJs, indexHtml, manifest, serviceWorker, icon } from "./assets.js";
const BING_ARCHIVE = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US";
const STATE_FIELDS = ["profile", "memories", "museum", "settings", "system"];
async function bingMetadata() {
const response = await fetch(BING_ARCHIVE, { headers: { accept: "application/json" } });
if (!response.ok) throw new Error(`Bing metadata request failed: ${response.status}`);
const image = (await response.json()).images?.[0];
if (!image?.url) throw new Error("Bing did not return today's image");
return { title: image.copyright || "Bing Daily Image", url: new URL(image.url, "https://www.bing.com").toString() };
}
function headers(type, cache = "public, max-age=3600") { return { "content-type": type, "cache-control": cache }; }
function userId(request) { return request.headers.get("oai-authenticated-user-id"); }
function loginPage(request) {
const language = request.headers.get("accept-language")?.toLowerCase().startsWith("zh") ? localeZh : localeEn;
const text = language.login;
const escape = (value) => String(value).replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[char]));
return `
Life OSLIFE OS
${text.title}
${escape(text.description)}
${escape(text.continue)}${escape(text.footnote)}
`;
}
function validState(value) { return value && typeof value === "object" && !Array.isArray(value) && STATE_FIELDS.every((field) => Object.hasOwn(value, field)); }
function stateFromRow(row) { return row ? Object.fromEntries(STATE_FIELDS.map((field) => [field, JSON.parse(row[`${field}_json`])])) : null; }
async function stateApi(request, env, id) {
if (!id) return Response.json({ error: "Authentication required" }, { status: 401 });
if (!env.DB) return Response.json({ error: "Cloud storage is unavailable" }, { status: 503 });
try {
if (request.method === "GET") {
const row = await env.DB.prepare("SELECT profile_json, memories_json, museum_json, settings_json, system_json, updated_at FROM lifeos_states WHERE user_id = ?").bind(id).first();
return Response.json({ state: stateFromRow(row), updatedAt: row?.updated_at ?? null }, { headers: { "cache-control": "no-store" } });
}
if (request.method === "PUT") {
const payload = await request.json();
if (!validState(payload?.state)) return Response.json({ error: "Invalid Life OS state" }, { status: 400 });
const state = payload.state;
const serialized = STATE_FIELDS.map((field) => JSON.stringify(state[field]));
if (serialized.some((value) => value.length > 1_000_000)) return Response.json({ error: "Life OS state is too large" }, { status: 413 });
const updatedAt = new Date().toISOString();
await env.DB.prepare("INSERT INTO lifeos_states (user_id, profile_json, memories_json, museum_json, settings_json, system_json, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET profile_json = excluded.profile_json, memories_json = excluded.memories_json, museum_json = excluded.museum_json, settings_json = excluded.settings_json, system_json = excluded.system_json, updated_at = excluded.updated_at").bind(id, ...serialized, updatedAt).run();
return Response.json({ ok: true, updatedAt }, { headers: { "cache-control": "no-store" } });
}
return Response.json({ error: "Method not allowed" }, { status: 405, headers: { allow: "GET, PUT" } });
} catch (error) {
return Response.json({ error: "Cloud storage request failed", detail: String(error) }, { status: 500 });
}
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
const id = userId(request);
if (url.pathname === "/api/lifeos-state") return stateApi(request, env, id);
try {
if (url.pathname === "/api/bing-wallpaper") return Response.json(await bingMetadata(), { headers: { "cache-control": "public, max-age=3600" } });
if (url.pathname === "/api/bing-wallpaper-image") {
const image = await bingMetadata();
const response = await fetch(image.url);
if (!response.ok) throw new Error(`Bing image request failed: ${response.status}`);
return new Response(response.body, { headers: headers(response.headers.get("content-type") || "image/jpeg", "public, max-age=21600") });
}
} catch (error) {
return Response.json({ error: "Bing wallpaper is temporarily unavailable", detail: String(error) }, { status: 502 });
}
if (url.pathname === "/addons.js") return new Response(addonsJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/service-worker.js") return new Response(serviceWorker, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/manifest.webmanifest") return new Response(manifest, { headers: headers("application/manifest+json; charset=utf-8", "no-cache") });
if (url.pathname === "/life-os-icon.svg") return new Response(icon, { headers: headers("image/svg+xml", "public, max-age=86400") });
if (url.pathname === "/boot-guard.js") return new Response(bootGuardJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/i18n.js") return new Response(i18nJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/locales/en.js") return new Response(localeEnJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/locales/zh-CN.js") return new Response(localeZhJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/tasks.js") return new Response(tasksJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/calendar.js") return new Response(calendarJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/i18n-renderers.js") return new Response(i18nRenderersJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
if (url.pathname === "/" || url.pathname === "/index.html") {
if (!id) return new Response(loginPage(request), { headers: headers("text/html; charset=utf-8", "no-cache") });
return new Response(indexHtml, { headers: headers("text/html; charset=utf-8", "no-cache") });
}
return new Response("Not found", { status: 404 });
},
};