Files
bitvid/dist/js/embedDiagnostics.js
T
2026-01-30 17:09:51 -05:00

178 lines
4.3 KiB
JavaScript

// js/embedDiagnostics.js
let activeEmitter = null;
export function initEmbedDiagnostics({ enabled = true } = {}) {
if (!enabled) {
return { emit: () => {} };
}
function postToParent(type, payload) {
try {
// NOTE: using "*" so tester receives messages; in a production build you may restrict origin.
if (window.parent && window.parent !== window) {
window.parent.postMessage({ __bitvid_debug: true, type, payload }, "*");
}
} catch (e) {
// ignore
}
}
function safeStringify(obj, max = 2000) {
try {
const s = JSON.stringify(
obj,
(k, v) => {
if (typeof v === "string" && v.length > 1000)
return v.slice(0, 1000) + "…";
// redact likely sensitive fields
if (k && /key|secret|sk|priv|pass|token/i.test(k)) return "[REDACTED]";
return v;
},
2
);
return s.length > max ? s.slice(0, max) + "…" : s;
} catch (e) {
try {
return String(obj);
} catch (e2) {
return "[unserializable]";
}
}
}
// console wrappers
["log", "info", "warn", "error", "debug"].forEach((lvl) => {
// eslint-disable-next-line no-console
const orig = console[lvl] ? console[lvl].bind(console) : null;
if (orig) {
// eslint-disable-next-line no-console
console[lvl] = (...args) => {
try {
postToParent("console", {
level: lvl,
args: args.map((a) =>
typeof a === "string" ? a : safeStringify(a, 1000)
),
});
} catch (e) {
/* no-op */
}
try {
orig(...args);
} catch (e) {
/* no-op */
}
};
}
});
// global errors
window.addEventListener(
"error",
(ev) => {
try {
const payload = {
message: ev.message,
filename: ev.filename,
lineno: ev.lineno,
colno: ev.colno,
stack: ev.error && ev.error.stack ? ev.error.stack : null,
};
postToParent("error", payload);
} catch (e) {
/* no-op */
}
},
true
);
window.addEventListener("unhandledrejection", (ev) => {
try {
const reason =
ev && ev.reason
? ev.reason && ev.reason.stack
? ev.reason.stack
: safeStringify(ev.reason)
: "unknown";
postToParent("unhandledrejection", { reason });
} catch (e) {
/* no-op */
}
});
// light fetch/XHR monitoring (failures only)
(function wrapFetch() {
if (!window.fetch) return;
const orig = window.fetch.bind(window);
window.fetch = async (...args) => {
try {
const res = await orig(...args);
if (!res.ok) {
postToParent("fetch-error", {
url: args[0],
status: res.status,
statusText: res.statusText,
});
}
return res;
} catch (err) {
postToParent("fetch-exception", { url: args[0], error: String(err) });
throw err;
}
};
})();
(function wrapXhr() {
try {
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (...args) {
this.__embed_diag_url = args[1];
return origOpen.apply(this, args);
};
XMLHttpRequest.prototype.send = function (...args) {
try {
this.addEventListener("error", () =>
postToParent("xhr-error", { url: this.__embed_diag_url })
);
this.addEventListener("load", () => {
if (this.status >= 400)
postToParent("xhr-status", {
url: this.__embed_diag_url,
status: this.status,
});
});
} catch (e) {
/* no-op */
}
return origSend.apply(this, args);
};
} catch (e) {
/* no-op */
}
})();
function emitEmbedEvent(name, payload = {}) {
try {
postToParent("embed-event", { name, payload });
} catch (e) {
/* no-op */
}
}
activeEmitter = emitEmbedEvent;
return { emit: emitEmbedEvent };
}
export function emit(name, payload) {
if (activeEmitter) {
try {
activeEmitter(name, payload);
} catch (e) {
/* no-op */
}
}
}