UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

96 lines (95 loc) • 4.12 kB
import { t as pruneMapToMaxSize } from "./map-size-CNcWiFKu.mjs"; import { d as normalizeMimeType } from "./mime-CGDYrRzL.mjs"; import { d as sendMethodNotAllowed, x as respondNotFound } from "./http-common-Cl0x-bzn.mjs"; import { t as buildAssistantMediaContentDisposition } from "./assistant-media-content-disposition-B3NSqIRL.mjs"; import { i as writeByteHeaders } from "./http-byte-range-Bev-58hq.mjs"; import { t as ARTIFACT_DOWNLOAD_PATH } from "./artifact-download-CXaDlLGk.mjs"; import { randomBytes } from "node:crypto"; //#region src/gateway/artifact-downloads.ts const DOWNLOAD_TTL_MS = 3e5; const downloads = /* @__PURE__ */ new WeakMap(); function canCreateArtifactDownload(client) { return Boolean(client?.connId && client.connectionSignal?.aborted === false && !client.invalidated); } function createArtifactDownload(params) { const { client } = params; if (!canCreateArtifactDownload(client)) throw new Error("Artifact download connection is no longer available"); params.assertCurrent(); let grants = downloads.get(client); if (!grants) { grants = /* @__PURE__ */ new Map(); downloads.set(client, grants); } const now = Date.now(); for (const [ticket, grant] of grants) if (grant.expiresAt <= now) grants.delete(ticket); pruneMapToMaxSize(grants, 127); const ticket = randomBytes(32).toString("base64url"); const expiresAt = now + DOWNLOAD_TTL_MS; grants.set(ticket, { expiresAt, digest: params.prepared.digest, assertCurrent: params.assertCurrent, read: params.read }); return { url: `${ARTIFACT_DOWNLOAD_PATH}${encodeURIComponent(client.connId)}/${ticket}`, expiresAt: new Date(expiresAt).toISOString() }; } /** The RPC chooses and authorizes the resource; HTTP never accepts a replacement query. */ async function handleArtifactDownloadHttpRequest(req, res, opts) { const url = new URL(req.url ?? "/", "http://localhost"); const pathname = opts.basePath && url.pathname.startsWith(`${opts.basePath}/api/artifacts/download/`) ? url.pathname.slice(opts.basePath.length) : url.pathname; if (!pathname.startsWith("/api/artifacts/download/")) return false; if (req.method !== "GET" && req.method !== "HEAD") { sendMethodNotAllowed(res, "GET, HEAD"); return true; } const [connection, ticket, extra] = pathname.slice(ARTIFACT_DOWNLOAD_PATH.length).split("/"); const client = [...opts.clients].find((candidate) => candidate.connId && encodeURIComponent(candidate.connId) === connection); const grants = client ? downloads.get(client) : void 0; const grant = ticket && extra === void 0 ? grants?.get(ticket) : void 0; if (!client || !grant || !ticket) { respondNotFound(res); return true; } const assertCurrent = () => { if (!opts.clients.has(client) || client.connectionSignal?.aborted !== false || client.invalidated || grant.expiresAt <= Date.now() || grants?.get(ticket) !== grant) throw new Error("Artifact download expired"); grant.assertCurrent(); }; let prepared; try { assertCurrent(); prepared = await grant.read({ expectedDigest: grant.digest, method: req.method, headers: { range: req.headers.range, "if-range": req.headers["if-range"], "if-none-match": req.headers["if-none-match"] } }); assertCurrent(); } catch { respondNotFound(res); return true; } if (!prepared) { respondNotFound(res); return true; } const { artifact, response, body } = prepared; const mime = normalizeMimeType(artifact.mimeType); const contentType = mime && /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/i.test(mime) ? mime : "application/octet-stream"; res.setHeader("content-type", contentType); res.setHeader("content-disposition", buildAssistantMediaContentDisposition(artifact.title)); res.setHeader("content-security-policy", "default-src 'none'; sandbox"); res.setHeader("x-content-type-options", "nosniff"); res.setHeader("referrer-policy", "no-referrer"); res.setHeader("cache-control", "private, no-store"); writeByteHeaders(res, response); res.end(body); return true; } //#endregion export { createArtifactDownload as n, handleArtifactDownloadHttpRequest as r, canCreateArtifactDownload as t };