cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

71 lines
2.4 KiB
TypeScript

import { ResponseError } from "@asciidev/box-sdk";
import { redactSecrets } from "@rakazo/core";
import { readBodyCapped } from "./web-ssrf.js";
const MAX_BOX_ERROR_RESPONSE_BYTES = 16 * 1024;
const BOX_ERROR_RESPONSE_TIMEOUT_MS = 2_000;
/** Keep HTTP failures actionable without changing the SDK's status-based recovery contract. */
export function wrapBoxCall<Args extends unknown[], Result>(
call: (...args: Args) => Promise<Result>,
apiKey: string,
): (...args: Args) => Promise<Result> {
return async (...args) => {
try {
return await call(...args);
} catch (error) {
if (error instanceof ResponseError) throw await boxResponseError(error.response, apiKey);
throw error;
}
};
}
export async function boxResponseError(
response: Response,
apiKey: string,
signal?: AbortSignal,
): Promise<ResponseError> {
const body = await readErrorBody(response, signal);
const code = safeIdentifier(body?.code);
const requestId = safeIdentifier(body?.requestId);
const prefix = `Box API request failed (HTTP ${response.status}${code ? `, ${code}` : ""})`;
const detail =
typeof body?.message === "string"
? redactSecrets(body.message, [apiKey])
.replace(/https?:\/\/[^\s<>"']+/gi, "[redacted URL]")
.replace(/Bearer\s+[^\s,;]+/gi, "Bearer [redacted]")
.replace(/\s+/g, " ")
.trim()
.slice(0, 512)
: "";
return new ResponseError(
response,
redactSecrets(
`${prefix}${detail ? `: ${detail}` : ""}${requestId ? ` (request ${requestId})` : ""}`,
[apiKey],
),
);
}
function safeIdentifier(value: unknown): string | undefined {
return typeof value === "string" && /^[a-zA-Z0-9_.-]{1,128}$/.test(value) ? value : undefined;
}
async function readErrorBody(
response: Response,
signal?: AbortSignal,
): Promise<Record<string, unknown> | undefined> {
const readSignal = signal
? AbortSignal.any([signal, AbortSignal.timeout(BOX_ERROR_RESPONSE_TIMEOUT_MS)])
: AbortSignal.timeout(BOX_ERROR_RESPONSE_TIMEOUT_MS);
try {
const bytes = await readBodyCapped(response, MAX_BOX_ERROR_RESPONSE_BYTES, readSignal);
const body: unknown = JSON.parse(new TextDecoder().decode(bytes));
return body !== null && typeof body === "object" && !Array.isArray(body)
? (body as Record<string, unknown>)
: undefined;
} catch {
return undefined;
}
}