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.
41 lines
1.5 KiB
TypeScript
41 lines
1.5 KiB
TypeScript
import { redactSecrets } from "@rakazo/core";
|
|
|
|
export function combineSignals(...signals: Array<AbortSignal | undefined>): AbortSignal {
|
|
return AbortSignal.any(signals.filter((signal): signal is AbortSignal => Boolean(signal)));
|
|
}
|
|
|
|
export function sanitizeConnectorError(error: unknown, secrets: string[] = []): string {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
return redactSecrets(
|
|
message
|
|
.replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
|
|
.replace(/trg_(?:live|test)_[A-Za-z0-9_-]+/g, "[redacted]"),
|
|
secrets,
|
|
).slice(0, 2_000);
|
|
}
|
|
|
|
export function redactConnectorPayload(value: unknown, secrets: string[]): unknown {
|
|
if (secrets.length === 0) return value;
|
|
try {
|
|
return redactPayloadValue(JSON.parse(JSON.stringify(value)), secrets);
|
|
} catch {
|
|
return { ok: true };
|
|
}
|
|
}
|
|
|
|
function redactPayloadValue(value: unknown, secrets: string[]): unknown {
|
|
if (typeof value === "string") return redactSecrets(value, secrets);
|
|
if (Array.isArray(value)) return value.map((item) => redactPayloadValue(item, secrets));
|
|
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
const serialized = String(value);
|
|
return redactSecrets(serialized, secrets) === serialized ? value : "[redacted]";
|
|
}
|
|
if (typeof value !== "object") return value;
|
|
return Object.fromEntries(
|
|
Object.entries(value).map(([key, item]) => [
|
|
redactSecrets(key, secrets),
|
|
redactPayloadValue(item, secrets),
|
|
]),
|
|
);
|
|
}
|