feat(web): I5 callback inbox and integration catalog UI

Made-with: Cursor
This commit is contained in:
2026-04-06 22:40:28 +08:00
parent e34b420168
commit 841bd3e0bd
8 changed files with 784 additions and 0 deletions
@@ -0,0 +1,72 @@
const SENSITIVE_KEY_RE = /(authorization|bearer|token|secret|password|api[_-]?key|access[_-]?token|refresh[_-]?token|idempotency)/i;
/**
* Recursively redact object values for safe display (tokens / auth-like keys).
* @param {unknown} value
* @param {string} [keyHint]
* @returns {unknown}
*/
function redactValue(value, keyHint = "") {
if (value === null || value === undefined) return value;
if (typeof value === "string") {
const k = keyHint;
if (SENSITIVE_KEY_RE.test(k)) return "[REDACTED]";
if (value.length > 48) return `${value.slice(0, 8)}…[${value.length} chars]`;
return value;
}
if (typeof value === "number" || typeof value === "boolean") return value;
if (Array.isArray(value)) return value.map((item, i) => redactValue(item, `${keyHint}[${i}]`));
if (typeof value === "object") {
/** @type {Record<string, unknown>} */
const out = {};
for (const [k, v] of Object.entries(value)) {
out[k] = redactValue(v, k);
}
return out;
}
return value;
}
/**
* Pretty JSON string with redaction; falls back to regex pass on non-JSON text.
* @param {unknown} raw — object or JSON string from API
* @returns {string}
*/
export function formatRedactedPayloadJson(raw) {
if (raw == null) return "";
let obj = raw;
if (typeof raw === "string") {
const trimmed = raw.trim();
try {
obj = JSON.parse(trimmed);
} catch {
return redactRawJsonString(trimmed);
}
}
const redacted = redactValue(obj, "");
try {
return JSON.stringify(redacted, null, 2);
} catch {
return String(raw);
}
}
/**
* Best-effort redaction when the body is not valid JSON.
* @param {string} s
*/
function redactRawJsonString(s) {
let out = s;
out = out.replace(/"((?:[^"\\]|\\.)*)"\s*:\s*"((?:[^"\\]|\\.)*)"/g, (match, key, val) => {
const keyStr = String(key).replace(/\\"/g, '"');
if (SENSITIVE_KEY_RE.test(keyStr)) {
return `"${key}":"[REDACTED]"`;
}
const unescaped = val.replace(/\\"/g, '"').replace(/\\\\/g, "\\");
if (unescaped.length > 48) {
return `"${key}":"${unescaped.slice(0, 8)}…[${unescaped.length} chars]"`;
}
return match;
});
return out;
}