UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

119 lines (118 loc) • 5.08 kB
import { t as resolveGatewayPublicOrigin } from "./gateway-public-origin-BcHLka2A.mjs"; import { f as resolveSessionPublicShare } from "./session-accessor.sqlite-entry-store-CIM2yoGL.mjs"; import { i as withReadySessionRows } from "./session-row-prepared-read-BEDhe5gP.mjs"; import { t as isSecurePublicSessionIngress } from "./control-ui-public-session-ingress-DNMYqZVL.mjs"; import { n as PUBLIC_SESSION_ENTRY_SCRIPT, r as renderPublicSessionDocument, t as servePublicSessionRepresentation } from "./control-ui-public-session-response-BJtz_UjS.mjs"; import { r as parseControlUiSessionReturnPath, t as buildControlUiSessionEntryUrl } from "./control-ui-session-entry-path-DtBV3od1.mjs"; import { t as resolveControlUiSessionPath } from "./control-ui-session-path-resolve-BB2CQ4ec.mjs"; import { n as resolveControlUiShareOrigin } from "./control-ui-share-DjoTgUR8.mjs"; import { createHash } from "node:crypto"; //#region src/gateway/control-ui-public-chat.ts const PROBE_HASH = createHash("sha256").update(PUBLIC_SESSION_ENTRY_SCRIPT).digest("base64"); async function serveControlUiPublicChat(params) { const { req, res, basePath, config, projection, gate } = params; const url = new URL(req.url ?? "/", "http://localhost"); const entryQuery = new URLSearchParams(url.searchParams); entryQuery.delete("offset"); const entryPath = url.pathname + (entryQuery.size ? `?${entryQuery}` : ""); const target = parseControlUiSessionReturnPath(entryPath, basePath); const publicOrigin = resolveGatewayPublicOrigin(config); const origin = resolveControlUiShareOrigin(req, publicOrigin); res.setHeader("Cache-Control", "no-store"); res.setHeader("X-Robots-Tag", "noindex, nofollow"); res.setHeader("Content-Security-Policy", `default-src 'none'; img-src 'self'; style-src 'unsafe-inline'; script-src 'sha256-${PROBE_HASH}'; connect-src 'self'; base-uri 'none'; frame-ancestors 'none'; form-action 'none'`); const end = (status, body) => { res.statusCode = status; res.setHeader("Content-Type", "text/html; charset=utf-8"); res.setHeader("Content-Length", Buffer.byteLength(body)); res.end(req.method === "HEAD" ? void 0 : body); return true; }; const offsetText = url.searchParams.get("offset") ?? "0"; if (!target || !origin || req.method !== "GET" && req.method !== "HEAD" || url.searchParams.getAll("offset").length > 1 || !/^(?:0|[1-9][0-9]{0,9})$/u.test(offsetText)) return end(404, "Not found"); if (req.method === "HEAD") { res.setHeader("Allow", "GET"); return end(405, ""); } const entryUrl = buildControlUiSessionEntryUrl(entryPath, basePath); const clientAuthBasePath = params.auth.mode === "token" || params.auth.mode === "password" ? basePath : void 0; const secureIngress = isSecurePublicSessionIngress(req, params.ingress, publicOrigin); if (!secureIngress && clientAuthBasePath !== void 0) { await params.serveApp(entryPath); return true; } const unavailable = (status = 404, retryAfterSeconds = 1) => { if (status !== 404) { res.setHeader("Retry-After", status === 429 ? retryAfterSeconds : "1"); return end(status, status === 429 ? "Too many public session requests. Please retry later." : "This conversation is temporarily unavailable. Please retry."); } return end(404, renderPublicSessionDocument({ title: "Conversation unavailable", messages: [], truncated: false, latestUrl: url.pathname, entryUrl, clientAuthBasePath, cardUrl: `${origin}${basePath}/share/card.png`, unavailable: true })); }; const admitted = gate.admitClient(params.ingress.rateLimit.subject.key); if (admitted.kind === "rate-limited") return unavailable(429, admitted.retryAfterSeconds); if (!secureIngress) return unavailable(); if (!projection) return unavailable(503); try { const selected = await resolveControlUiSessionPath({ target, projection, client: null, publicOnly: true }); if (!selected) return unavailable(); const query = { key: selected.key, agentId: selected.agentId }; const queries = () => [query]; const share = await withReadySessionRows(projection, queries, () => { const current = projection.sharingTargetState(query); return current.status === "ready" ? resolveSessionPublicShare(current.target.entry) : void 0; }); if (!share) return unavailable(); const locator = { agentId: selected.agentId, sessionKey: selected.key, sessionId: share.sessionId, shareId: share.id }; const offset = Number(offsetText); return await servePublicSessionRepresentation({ ...params, locator, projection, offset, requestKey: JSON.stringify([ "canonical", share.id, url.pathname, offset, origin, entryUrl, clientAuthBasePath ]), document: { latestUrl: url.pathname, entryUrl, clientAuthBasePath, canonicalUrl: `${origin}${url.pathname}`, cardUrl: `${origin}${basePath}/share/card.png` }, olderUrl: (olderOffset) => `${url.pathname}?offset=${olderOffset}`, unavailable }); } catch { return unavailable(503); } } //#endregion export { serveControlUiPublicChat };