UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

354 lines (353 loc) 12.9 kB
import { n as readResponseWithLimit$1 } from "./read-response-with-limit-MDCSJrlg.js"; import { n as buildTimeoutAbortSignal } from "./fetch-timeout-CDaw0X8V.js"; import { _ as resolvePinnedHostnameWithPolicy, a as createPinnedDispatcher, i as closeDispatcher } from "./ssrf-CvPEXMGn.js"; import { n as fetchWithRuntimeDispatcherOrMockedGlobal } from "./runtime-fetch-BwqSVSNK.js"; import { t as parseMediaContentLength } from "./content-length-DZY9SBS5.js"; import "./media-runtime-fgpmX6eL.js"; import "./runtime-fetch-CaFTO7GG.js"; import "./response-limit-runtime-B0ZHF0eR.js"; import "./ssrf-dispatcher-BI3-s6fI.js"; import "./timeout-abort-signal-DLoJS-P9.js"; //#region extensions/matrix/src/matrix/sdk/event-helpers.ts function matrixEventToRaw(event, opts = {}) { const unsigned = event.getUnsigned?.() ?? {}; const eventWithOriginalContent = event; const normalizedContent = preserveMatrixRelation(event, (opts.contentMode === "original" ? eventWithOriginalContent.getOriginalContent?.() ?? event.getContent?.() ?? {} : event.getContent?.() ?? eventWithOriginalContent.getOriginalContent?.() ?? {}) || {}); const raw = { event_id: event.getId() ?? "", sender: event.getSender() ?? "", type: event.getType() ?? "", origin_server_ts: event.getTs() ?? 0, content: normalizedContent, unsigned }; const stateKey = resolveMatrixStateKey(event); if (typeof stateKey === "string") raw.state_key = stateKey; return raw; } function preserveMatrixRelation(event, content) { if (Object.hasOwn(content, "m.relates_to")) return content; const relation = resolveMatrixRelation(event); return relation ? { ...content, "m.relates_to": relation } : content; } function resolveMatrixRelation(event) { const originalRelation = (event.getOriginalContent?.())?.["m.relates_to"]; if (originalRelation) return originalRelation; const wireRelation = (event.getWireContent?.())?.["m.relates_to"]; if (wireRelation) return wireRelation; const rawContent = event.event?.content; if (rawContent && typeof rawContent === "object") return rawContent["m.relates_to"]; } function parseMxc(url) { const match = /^mxc:\/\/([^/]+)\/(.+)$/.exec(url.trim()); if (!match) return null; return { server: match[1], mediaId: match[2] }; } function buildHttpError(statusCode, bodyText) { let message = `Matrix HTTP ${statusCode}`; if (bodyText.trim()) try { const parsed = JSON.parse(bodyText); if (typeof parsed.error === "string" && parsed.error.trim()) message = parsed.error.trim(); else message = bodyText.slice(0, 500); } catch { message = bodyText.slice(0, 500); } return Object.assign(new Error(message), { statusCode }); } function resolveMatrixStateKey(event) { const direct = event.getStateKey?.(); if (typeof direct === "string") return direct; const wireContent = event.getWireContent?.(); if (wireContent && typeof wireContent.state_key === "string") return wireContent.state_key; const rawEvent = event.event; if (rawEvent && typeof rawEvent.state_key === "string") return rawEvent.state_key; } //#endregion //#region extensions/matrix/src/matrix/media-errors.ts const MATRIX_MEDIA_SIZE_LIMIT_ERROR_MESSAGE = "Matrix media exceeds configured size limit"; var MatrixMediaSizeLimitError = class extends Error { constructor(message = MATRIX_MEDIA_SIZE_LIMIT_ERROR_MESSAGE, options) { super(message, options); this.code = "MATRIX_MEDIA_SIZE_LIMIT"; this.name = "MatrixMediaSizeLimitError"; } }; function isMatrixMediaSizeLimitError(err) { if (err instanceof MatrixMediaSizeLimitError) return true; if (!(err instanceof Error) || err.cause === void 0) return false; return isMatrixMediaSizeLimitError(err.cause); } //#endregion //#region extensions/matrix/src/matrix/sdk/read-response-with-limit.ts async function readResponseWithLimit(res, maxBytes, opts) { return await readResponseWithLimit$1(res, maxBytes, { ...opts, onIdleTimeout: opts?.onIdleTimeout ?? (({ chunkTimeoutMs }) => /* @__PURE__ */ new Error(`Matrix media download stalled: no data received for ${chunkTimeoutMs}ms`)) }); } //#endregion //#region extensions/matrix/src/matrix/sdk/transport.ts function normalizeEndpoint(endpoint) { if (!endpoint) return "/"; return endpoint.startsWith("/") ? endpoint : `/${endpoint}`; } function applyQuery(url, qs) { if (!qs) return; for (const [key, rawValue] of Object.entries(qs)) { if (rawValue === void 0 || rawValue === null) continue; if (Array.isArray(rawValue)) { for (const item of rawValue) { if (item === void 0 || item === null) continue; url.searchParams.append(key, String(item)); } continue; } url.searchParams.set(key, String(rawValue)); } } function isRedirectStatus(statusCode) { return statusCode >= 300 && statusCode < 400; } function toFetchUrl(resource) { if (resource instanceof URL) return resource.toString(); if (typeof resource === "string") return resource; return resource.url; } const MATRIX_STATE_AFTER_SYNC_PARAM = "org.matrix.msc4222.use_state_after"; function withoutMatrixStateAfterSyncParam(rawUrl) { let url; try { url = new URL(rawUrl); } catch { return rawUrl; } if (!url.pathname.endsWith("/sync") || !url.searchParams.has(MATRIX_STATE_AFTER_SYNC_PARAM)) return rawUrl; url.searchParams.delete(MATRIX_STATE_AFTER_SYNC_PARAM); return url.toString(); } function buildBufferedResponse(params) { const response = new Response(params.body, { status: params.source.status, statusText: params.source.statusText, headers: new Headers(params.source.headers) }); try { Object.defineProperty(response, "url", { value: params.source.url || params.url, configurable: true }); } catch {} return response; } async function fetchWithMatrixDispatcher(params) { return await fetchWithRuntimeDispatcherOrMockedGlobal(params.url, params.init); } async function fetchWithMatrixGuardedRedirects(params) { let currentUrl = new URL(params.url); let method = (params.init?.method ?? "GET").toUpperCase(); let body = params.init?.body; let headers = new Headers(params.init?.headers ?? {}); const maxRedirects = 5; const visited = /* @__PURE__ */ new Set(); const { signal, cleanup } = buildTimeoutAbortSignal({ timeoutMs: params.timeoutMs, signal: params.signal, operation: "matrix.guarded-redirect-fetch", url: params.url }); for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount += 1) { let dispatcher; try { dispatcher = createPinnedDispatcher(await resolvePinnedHostnameWithPolicy(currentUrl.hostname, { policy: params.ssrfPolicy }), params.dispatcherPolicy, params.ssrfPolicy); const response = await fetchWithMatrixDispatcher({ url: currentUrl.toString(), init: { ...params.init, method, body, headers, redirect: "manual", signal, dispatcher } }); if (!isRedirectStatus(response.status)) return { response, release: async () => { cleanup(); await closeDispatcher(dispatcher); }, finalUrl: currentUrl.toString() }; const location = response.headers.get("location"); if (!location) { cleanup(); await closeDispatcher(dispatcher); throw new Error(`Matrix redirect missing location header (${currentUrl.toString()})`); } const nextUrl = new URL(location, currentUrl); if (nextUrl.protocol !== currentUrl.protocol) { cleanup(); await closeDispatcher(dispatcher); throw new Error(`Blocked cross-protocol redirect (${currentUrl.protocol} -> ${nextUrl.protocol})`); } const nextUrlString = nextUrl.toString(); if (visited.has(nextUrlString)) { cleanup(); await closeDispatcher(dispatcher); throw new Error("Redirect loop detected"); } visited.add(nextUrlString); if (nextUrl.origin !== currentUrl.origin) { headers = new Headers(headers); headers.delete("authorization"); } if (response.status === 303 || (response.status === 301 || response.status === 302) && method !== "GET" && method !== "HEAD") { method = "GET"; body = void 0; headers = new Headers(headers); headers.delete("content-type"); headers.delete("content-length"); } response.body?.cancel(); await closeDispatcher(dispatcher); currentUrl = nextUrl; } catch (error) { cleanup(); await closeDispatcher(dispatcher); throw error; } } cleanup(); throw new Error(`Too many redirects while requesting ${params.url}`); } function createMatrixGuardedFetch(params) { return (async (resource, init) => { const url = withoutMatrixStateAfterSyncParam(toFetchUrl(resource)); const { signal, ...requestInit } = init ?? {}; const { response, release } = await fetchWithMatrixGuardedRedirects({ url, init: requestInit, signal: signal ?? void 0, ssrfPolicy: params.ssrfPolicy, dispatcherPolicy: params.dispatcherPolicy }); try { return buildBufferedResponse({ source: response, body: await response.arrayBuffer(), url }); } finally { await release(); } }); } async function performMatrixRequest(params) { const isAbsoluteEndpoint = params.endpoint.startsWith("http://") || params.endpoint.startsWith("https://"); if (isAbsoluteEndpoint && params.allowAbsoluteEndpoint !== true) throw new Error(`Absolute Matrix endpoint is blocked by default: ${params.endpoint}. Set allowAbsoluteEndpoint=true to opt in.`); const baseUrl = isAbsoluteEndpoint ? new URL(params.endpoint) : new URL(normalizeEndpoint(params.endpoint), params.homeserver); applyQuery(baseUrl, params.qs); const headers = new Headers(); headers.set("Accept", params.raw ? "*/*" : "application/json"); if (params.accessToken) headers.set("Authorization", `Bearer ${params.accessToken}`); let body; if (params.body !== void 0) if (params.body instanceof Uint8Array || params.body instanceof ArrayBuffer || typeof params.body === "string") body = params.body; else { headers.set("Content-Type", "application/json"); body = JSON.stringify(params.body); } const { response, release } = await fetchWithMatrixGuardedRedirects({ url: baseUrl.toString(), init: { method: params.method, headers, body }, timeoutMs: params.timeoutMs, ssrfPolicy: params.ssrfPolicy, dispatcherPolicy: params.dispatcherPolicy }); try { if (params.raw) { const contentLength = response.headers.get("content-length"); if (params.maxBytes && contentLength) { const length = parseMediaContentLength(contentLength); if (length !== null && length > params.maxBytes) throw new MatrixMediaSizeLimitError(`Matrix media exceeds configured size limit (${length} bytes > ${params.maxBytes} bytes)`); } const bytes = params.maxBytes ? await readResponseWithLimit(response, params.maxBytes, { onOverflow: ({ maxBytes, size }) => new MatrixMediaSizeLimitError(`Matrix media exceeds configured size limit (${size} bytes > ${maxBytes} bytes)`), chunkTimeoutMs: params.readIdleTimeoutMs }) : Buffer.from(await response.arrayBuffer()); return { response, text: bytes.toString("utf8"), buffer: bytes }; } const text = await response.text(); return { response, text, buffer: Buffer.from(text, "utf8") }; } finally { await release(); } } //#endregion //#region extensions/matrix/src/matrix/sdk/http-client.ts var MatrixAuthedHttpClient = class { constructor(params) { this.homeserver = params.homeserver; this.accessToken = params.accessToken; this.ssrfPolicy = params.ssrfPolicy; this.dispatcherPolicy = params.dispatcherPolicy; } async requestJson(params) { const { response, text } = await performMatrixRequest({ homeserver: this.homeserver, accessToken: this.accessToken, method: params.method, endpoint: params.endpoint, qs: params.qs, body: params.body, timeoutMs: params.timeoutMs, ssrfPolicy: this.ssrfPolicy, dispatcherPolicy: this.dispatcherPolicy, allowAbsoluteEndpoint: params.allowAbsoluteEndpoint }); if (!response.ok) throw buildHttpError(response.status, text); if ((response.headers.get("content-type") ?? "").includes("application/json")) { if (!text.trim()) return {}; return JSON.parse(text); } return text; } async requestRaw(params) { const { response, buffer } = await performMatrixRequest({ homeserver: this.homeserver, accessToken: this.accessToken, method: params.method, endpoint: params.endpoint, qs: params.qs, timeoutMs: params.timeoutMs, raw: true, maxBytes: params.maxBytes, readIdleTimeoutMs: params.readIdleTimeoutMs, ssrfPolicy: this.ssrfPolicy, dispatcherPolicy: this.dispatcherPolicy, allowAbsoluteEndpoint: params.allowAbsoluteEndpoint }); if (!response.ok) throw buildHttpError(response.status, buffer.toString("utf8")); return buffer; } }; //#endregion export { matrixEventToRaw as a, isMatrixMediaSizeLimitError as i, createMatrixGuardedFetch as n, parseMxc as o, MatrixMediaSizeLimitError as r, MatrixAuthedHttpClient as t };