mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Debug: forward embed logs to parent when embed_debug=1
This commit instruments the embed runtime to forward structured logs and lifecycle events (Nostr, relay, pointer resolution, playback decision, WebTorrent) to the parent window via `postMessage` when the `embed_debug=1` query parameter is present. It also adds a defensive guard to `resetTorrentStats` in `js/app.js` to prevent TypeErrors. Changes: - Added `js/embedDiagnostics.js` module. - Modified `js/embed.js` to initialize diagnostics and instrument startup. - Modified `js/app.js` to instrument playback fallback and patch `resetTorrentStats`. - Modified `js/webtorrent.js` to instrument peer probing and streaming.
This commit is contained in:
1 parent
868d69cef8
commit
439265d9c0
4 files changed
+270
-45
No files matched your search
@@ -3,6 +3,7 @@
|
||||
import { nostrClient } from "./nostrClientFacade.js";
|
||||
import { recordVideoView } from "./nostrViewEventsFacade.js";
|
||||
import { torrentClient } from "./webtorrent.js";
|
||||
import { emit } from "./embedDiagnostics.js";
|
||||
import {
|
||||
isDevMode,
|
||||
ADMIN_SUPER_NPUB,
|
||||
@@ -4901,14 +4902,16 @@ class Application {
|
||||
}
|
||||
|
||||
resetTorrentStats() {
|
||||
if (this.videoModal) {
|
||||
if (typeof this.videoModal.resetStats === "function") {
|
||||
try {
|
||||
if (this.videoModal && typeof this.videoModal.resetStats === "function") {
|
||||
this.videoModal.resetStats();
|
||||
} else {
|
||||
devLogger.info(
|
||||
"[Application] resetTorrentStats: videoModal.resetStats not available — skipping."
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
devLogger.warn("[Application] resetTorrentStats failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8192,6 +8195,16 @@ class Application {
|
||||
*/
|
||||
async playVideoWithFallback(options = {}) {
|
||||
const { url = "", magnet = "", trigger, forcedSource } = options || {};
|
||||
|
||||
emit("playback-decision", {
|
||||
method: forcedSource || (magnet ? "webtorrent" : "url"), // heuristic
|
||||
details: {
|
||||
url: Boolean(url),
|
||||
magnet: Boolean(magnet),
|
||||
forcedSource,
|
||||
},
|
||||
});
|
||||
|
||||
const hasTrigger = Object.prototype.hasOwnProperty.call(
|
||||
options || {},
|
||||
"trigger"
|
||||
@@ -8459,6 +8472,11 @@ class Application {
|
||||
return result;
|
||||
}
|
||||
|
||||
emit("playback-started", {
|
||||
method: result.source,
|
||||
details: { startedAt: Date.now() },
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+65
-41
@@ -5,45 +5,38 @@ import { resolveVideoPointer } from "./utils/videoPointer.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import { nostrToolsReady } from "./nostrToolsBootstrap.js";
|
||||
|
||||
// Diagnostic forwarder for embed debugging
|
||||
function postToParent(type, payload) {
|
||||
try {
|
||||
if (window.parent !== window) {
|
||||
window.parent.postMessage({ __bitvid_debug: true, type, payload }, "*");
|
||||
}
|
||||
} catch (e) {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("error", (ev) => {
|
||||
postToParent("error", {
|
||||
message: ev.message,
|
||||
filename: ev.filename,
|
||||
lineno: ev.lineno,
|
||||
colno: ev.colno,
|
||||
stack: ev.error && ev.error.stack,
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener("unhandledrejection", (ev) => {
|
||||
postToParent("unhandledrejection", {
|
||||
reason: (ev.reason && ev.reason.stack) || String(ev.reason),
|
||||
});
|
||||
});
|
||||
|
||||
["log", "info", "warn", "error", "debug"].forEach((level) => {
|
||||
if (console[level]) {
|
||||
const orig = console[level].bind(console);
|
||||
console[level] = (...args) => {
|
||||
postToParent("console", {
|
||||
level,
|
||||
args: args.map((a) => (typeof a === "string" ? a : String(a))),
|
||||
});
|
||||
orig(...args);
|
||||
};
|
||||
}
|
||||
});
|
||||
// early in embed bootstrap (insert before any heavy init)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const embedDebugEnabled =
|
||||
urlParams.get("embed_debug") === "1" || urlParams.get("debug") === "embed";
|
||||
let diag = null;
|
||||
const diagPromise = embedDebugEnabled
|
||||
? import("./embedDiagnostics.js")
|
||||
.then((m) => {
|
||||
diag = m.initEmbedDiagnostics({ enabled: true });
|
||||
// immediately signal startup
|
||||
diag.emit("embed-start", {
|
||||
url: location.href,
|
||||
userAgent: navigator.userAgent,
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
__bitvid_debug: true,
|
||||
type: "embed-diag-failed",
|
||||
payload: { error: String(e) },
|
||||
},
|
||||
"*"
|
||||
);
|
||||
}
|
||||
} catch (e2) {
|
||||
/* no-op */
|
||||
}
|
||||
})
|
||||
: Promise.resolve();
|
||||
|
||||
const POINTER_PARAM = "pointer";
|
||||
const PLAYBACK_PARAM = "playback";
|
||||
@@ -110,9 +103,11 @@ const resolvePlaybackPreference = (value) => {
|
||||
|
||||
const ensureNostrReady = async () => {
|
||||
try {
|
||||
await nostrToolsReady;
|
||||
const toolkitResult = await nostrToolsReady;
|
||||
diag?.emit("nostr-tools-ready", { ok: Boolean(toolkitResult) });
|
||||
} catch (error) {
|
||||
userLogger.warn("[embed] Failed to initialize NostrTools:", error);
|
||||
diag?.emit("nostr-tools-ready", { ok: false, error: String(error) });
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -175,13 +170,15 @@ const buildRelayList = (relays) => {
|
||||
...(Array.isArray(nostrClient.relays) ? nostrClient.relays : []),
|
||||
];
|
||||
|
||||
return Array.from(
|
||||
const result = Array.from(
|
||||
new Set(
|
||||
combined
|
||||
.map((relay) => (typeof relay === "string" ? relay.trim() : ""))
|
||||
.filter(Boolean)
|
||||
)
|
||||
);
|
||||
diag?.emit("relay-list", { relays: result });
|
||||
return result;
|
||||
};
|
||||
|
||||
const resolveEventFromPointer = async (pointer) => {
|
||||
@@ -196,6 +193,7 @@ const resolveEventFromPointer = async (pointer) => {
|
||||
let video = null;
|
||||
|
||||
if (eventId) {
|
||||
diag?.emit("event-request", { type: "start", id: eventId });
|
||||
if (relays.length) {
|
||||
try {
|
||||
const raw = await nostrClient.fetchRawEventById(eventId, {
|
||||
@@ -212,6 +210,12 @@ const resolveEventFromPointer = async (pointer) => {
|
||||
if (!video) {
|
||||
video = await nostrClient.getEventById(eventId);
|
||||
}
|
||||
diag?.emit("event-request", {
|
||||
type: "done",
|
||||
id: eventId,
|
||||
found: Boolean(video),
|
||||
videoRootId: video?.videoRootId,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -235,6 +239,10 @@ const resolveEventFromPointer = async (pointer) => {
|
||||
return { video: null, pointerHint: null };
|
||||
}
|
||||
|
||||
diag?.emit("event-request", {
|
||||
type: "start",
|
||||
naddr: { kind, pubkey, identifier },
|
||||
});
|
||||
await nostrClient.ensurePool();
|
||||
const pool = nostrClient.pool;
|
||||
const relayList = buildRelayList(relays);
|
||||
@@ -268,6 +276,12 @@ const resolveEventFromPointer = async (pointer) => {
|
||||
}
|
||||
|
||||
const video = rawEvent ? convertEventToVideo(rawEvent) : null;
|
||||
diag?.emit("event-request", {
|
||||
type: "done",
|
||||
naddr: { kind, pubkey, identifier },
|
||||
found: Boolean(video),
|
||||
videoRootId: video?.videoRootId,
|
||||
});
|
||||
|
||||
return {
|
||||
video,
|
||||
@@ -301,6 +315,10 @@ const setPointerState = ({ video, hint } = {}) => {
|
||||
app.currentVideoPointer = pointerInfo?.pointer || null;
|
||||
app.currentVideoPointerKey = pointerInfo?.key || null;
|
||||
|
||||
if (pointerInfo) {
|
||||
diag?.emit("pointer-resolved", pointerInfo);
|
||||
}
|
||||
|
||||
if (app.currentVideo) {
|
||||
app.currentVideo.pointer = app.currentVideoPointer;
|
||||
app.currentVideo.pointerKey = app.currentVideoPointerKey;
|
||||
@@ -313,6 +331,12 @@ const startEmbed = async () => {
|
||||
}
|
||||
window.__bitvidEmbedStarted = true;
|
||||
|
||||
try {
|
||||
await diagPromise;
|
||||
} catch (error) {
|
||||
// Should be caught above, but just in case
|
||||
}
|
||||
|
||||
if (!(videoEl instanceof HTMLVideoElement)) {
|
||||
setStatus("Embed video element is missing.", "error");
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
// 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 */
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -22,6 +22,7 @@ import WebTorrent from "./webtorrent.min.js";
|
||||
import { WSS_TRACKERS } from "./constants.js";
|
||||
import { safeDecodeURIComponent } from "./utils/safeDecode.js";
|
||||
import { devLogger, userLogger } from "./utils/logger.js";
|
||||
import { emit } from "./embedDiagnostics.js";
|
||||
|
||||
const DEFAULT_PROBE_TRACKERS = Object.freeze([...WSS_TRACKERS]);
|
||||
|
||||
@@ -224,6 +225,8 @@ export class TorrentClient {
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
|
||||
emit("torrent-probe-start", { magnet: augmentedMagnet });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
let torrent = null;
|
||||
@@ -259,7 +262,7 @@ export class TorrentClient {
|
||||
const isHealthy = Boolean(overrides.healthy);
|
||||
const peersCount = Number.isFinite(overrides.peers) ? overrides.peers : 0;
|
||||
|
||||
resolve({
|
||||
const result = {
|
||||
healthy: isHealthy,
|
||||
peers: peersCount,
|
||||
reason: isHealthy ? "peer" : "timeout",
|
||||
@@ -268,7 +271,9 @@ export class TorrentClient {
|
||||
usedTrackers: trackers,
|
||||
durationMs: Math.max(0, endedAt - startedAt),
|
||||
...overrides,
|
||||
});
|
||||
};
|
||||
emit("torrent-probe-result", result);
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -859,6 +864,7 @@ export class TorrentClient {
|
||||
*/
|
||||
async streamVideo(magnetURI, videoElement, opts = {}) {
|
||||
try {
|
||||
emit("torrent-stream-start", { magnet: magnetURI });
|
||||
// 1) Make sure we have a WebTorrent client and a valid SW registration.
|
||||
const initResult = await this.init();
|
||||
const serviceWorkerReady =
|
||||
|
||||
Reference in new issue
Block a user