105 lines
9.7 KiB
JavaScript
105 lines
9.7 KiB
JavaScript
import { addonsJs, bootGuardJs, i18nJs, localeEn, localeZh, localeEnJs, localeZhJs, localeScripts, localeData, tasksJs, calendarJs, notificationsJs, i18nRenderersJs, systemNavigationJs, browserAppJs, dataActionsJs, terminalAppJs, dbAdminJs, indexHtml, manifest, serviceWorker, icon } from "./assets.js";
|
|
import { cloudApi } from "./cloud-state.js";
|
|
import { dbAdminApi } from "./db-admin.js";
|
|
|
|
const BING_ARCHIVE = "https://www.bing.com/HPImageArchive.aspx?format=js&idx=0&n=1&mkt=en-US";
|
|
|
|
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"); }
|
|
|
|
async function holidayFeed(url) {
|
|
const country = url.searchParams.get('country') || '';
|
|
const year = Number(url.searchParams.get('year'));
|
|
if (!/^[A-Z]{2}$/.test(country) || !Number.isInteger(year) || year < 2024 || year > 2030) return Response.json({ error: 'Unsupported holiday country or year' }, { status: 400 });
|
|
try {
|
|
const response = await fetch(`https://openworkdays.com/api/v1/${country.toLowerCase()}/${year}.json`);
|
|
if (!response.ok) throw new Error(`Holiday source returned ${response.status}`);
|
|
const data = await response.json();
|
|
if (data.code !== country || data.year !== year || !Array.isArray(data.holidays)) throw new Error('Unexpected holiday data');
|
|
const validDate = value => typeof value === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(value) && value.startsWith(`${year}-`);
|
|
const holidays = data.holidays.filter(item => validDate(item.date) && typeof item.name === 'string').map(item => ({ date: item.date, name: item.name.slice(0, 160), observed: item.observed === true, estimated: item.estimated === true }));
|
|
const workingDays = (data.working_weekend_days || []).filter(validDate);
|
|
return Response.json({ country, year, holidays, workingDays, source: 'OpenWorkdays' }, { headers: { 'cache-control': 'public, max-age=43200' } });
|
|
} catch { return Response.json({ error: 'Holiday source unavailable' }, { status: 502, headers: { 'cache-control': 'no-store' } }); }
|
|
}
|
|
|
|
async function holidayCountries() {
|
|
try {
|
|
const response = await fetch('https://openworkdays.com/api/v1/countries.json');
|
|
if (!response.ok) throw new Error(`Holiday source returned ${response.status}`);
|
|
const data = await response.json();
|
|
const countries = (data.countries || []).filter(item => /^[A-Z]{2}$/.test(item.code) && typeof item.name === 'string').map(item => ({ code: item.code, name: item.name.slice(0, 100) }));
|
|
if (!countries.length) throw new Error('No countries available');
|
|
return Response.json({ countries }, { headers: { 'cache-control': 'public, max-age=86400' } });
|
|
} catch { return Response.json({ error: 'Holiday country list unavailable' }, { status: 502, headers: { 'cache-control': 'no-store' } }); }
|
|
}
|
|
|
|
function loginPage(request) {
|
|
const preferred = (request.headers.get("accept-language") || "en").split(",").map(part => part.split(";")[0].trim().toLowerCase());
|
|
const code = preferred.map(value => {
|
|
if (value.startsWith("zh-hk")) return "zh-HK";
|
|
if (value.startsWith("zh-mo")) return "zh-MO";
|
|
if (value.startsWith("zh-tw") || value.startsWith("zh-hant")) return "zh-TW";
|
|
if (value.startsWith("zh")) return "zh-CN";
|
|
return ["ar", "fr", "ru", "es", "ja", "en"].find(item => value === item || value.startsWith(item + "-"));
|
|
}).find(Boolean) || "en";
|
|
const language = code === "zh-CN" ? localeZh : code === "en" ? localeEn : localeData[code];
|
|
const text = language.login;
|
|
const escape = (value) => String(value).replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", "\"": """, "'": "'" }[char]));
|
|
return `<!doctype html><html lang="${code}" dir="${code === "ar" ? "rtl" : "ltr"}"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Life OS</title><style>html,body{margin:0;min-height:100%;font-family:ui-sans-serif,system-ui,sans-serif;background:#121513;color:#f4f6f1}body{display:grid;place-items:center;padding:24px;box-sizing:border-box}.panel{width:min(430px,100%);border:1px solid #414942;border-radius:22px;padding:34px;background:#1a201b;box-shadow:0 28px 70px #0008}.mark{font:600 11px ui-monospace,monospace;letter-spacing:.2em;color:#b8ff3d}.title{font-size:36px;letter-spacing:-.06em;margin:18px 0 10px}.copy{color:#afb9b1;line-height:1.65;margin:0 0 26px}.sign{display:flex;align-items:center;justify-content:center;text-decoration:none;background:#b8ff3d;color:#11170d;padding:14px 18px;border-radius:12px;font-size:14px;font-weight:700}.fine{margin:18px 0 0;color:#7e8a81;font:11px ui-monospace,monospace;letter-spacing:.04em;text-align:center}</style></head><body><main class="panel"><div class="mark">LIFE OS</div><h1 class="title">${text.title}</h1><p class="copy">${escape(text.description)}</p><a class="sign" href="/signin-with-chatgpt?return_to=/" target="_top">${escape(text.continue)}</a><p class="fine">${escape(text.footnote)}</p></main></body></html>`;
|
|
}
|
|
|
|
export default {
|
|
async fetch(request, env) {
|
|
const url = new URL(request.url);
|
|
const id = userId(request);
|
|
if (url.pathname === '/api/holidays') return holidayFeed(url);
|
|
if (url.pathname === '/api/holiday-countries') return holidayCountries();
|
|
if (["/api/lifeos-state", "/api/lifeos-initialize", "/api/lifeos-sync"].includes(url.pathname)) return cloudApi(request, env, id, url.pathname);
|
|
if (url.pathname.startsWith("/api/db-admin/")) return dbAdminApi(request, env, id, url.pathname);
|
|
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") });
|
|
const extraLocale = /^\/locales\/(zh-HK|zh-MO|zh-TW|ar|fr|ru|es|ja)\.js$/.exec(url.pathname)?.[1];
|
|
if (extraLocale) return new Response(localeScripts[extraLocale], { 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 === "/data-actions.js") return new Response(dataActionsJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
|
|
if (url.pathname === "/terminal-app.js") return new Response(terminalAppJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
|
|
if (url.pathname === "/db-admin.js") return new Response(dbAdminJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
|
|
if (url.pathname === "/notifications.js") return new Response(notificationsJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
|
|
if (url.pathname === "/system-navigation.js") return new Response(systemNavigationJs, { headers: headers("application/javascript; charset=utf-8", "no-cache") });
|
|
if (url.pathname === "/browser-app.js") return new Response(browserAppJs, { 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 });
|
|
},
|
|
};
|