From 27bafdd3de14162da0e07d97d73a2e7b5538bcea Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:36:00 -0400 Subject: [PATCH 1/7] Add verbose dev mode toggle for COUNT warnings --- config/instance-config.js | 9 +++++++++ js/config.js | 5 +++++ js/nostr/client.js | 16 ++++++++++------ js/nostr/publishHelpers.js | 5 ++++- js/nostr/viewEvents.js | 5 ++++- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/config/instance-config.js b/config/instance-config.js index dfa246ec..493e6931 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -23,6 +23,15 @@ */ export const IS_DEV_MODE = true; +/** + * Whether development builds should surface extra verbose diagnostics. + * + * Set this to `true` to keep high-volume development warnings (like COUNT + * request failures) visible in the console. Flip it to `false` when you want + * a quieter dev mode without disabling other development helpers entirely. + */ +export const IS_VERBOSE_DEV_MODE = true; + /** * Whether the current deployment should operate in lockdown mode. * diff --git a/js/config.js b/js/config.js index 7a07c770..2864a36d 100644 --- a/js/config.js +++ b/js/config.js @@ -3,6 +3,7 @@ import { userLogger } from "./utils/logger.js"; import { IS_DEV_MODE, + IS_VERBOSE_DEV_MODE, IS_LOCKDOWN_MODE, ADMIN_SUPER_NPUB, ADMIN_DM_IMAGE_URL, @@ -43,11 +44,15 @@ import { export const isDevMode = Boolean(IS_DEV_MODE); export { IS_DEV_MODE }; +export const isVerboseDevMode = Boolean(IS_VERBOSE_DEV_MODE); +export { IS_VERBOSE_DEV_MODE }; + export const isLockdownMode = Boolean(IS_LOCKDOWN_MODE); export { IS_LOCKDOWN_MODE }; if (typeof window !== "undefined") { window.__BITVID_DEV_MODE__ = isDevMode; + window.__BITVID_VERBOSE_DEV_MODE__ = isVerboseDevMode; window.__BITVID_LOCKDOWN_MODE__ = isLockdownMode; } diff --git a/js/nostr/client.js b/js/nostr/client.js index 27249fc3..822de47f 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -1,6 +1,6 @@ // js/nostr/client.js -import { isDevMode } from "../config.js"; +import { isDevMode, isVerboseDevMode } from "../config.js"; import { FEATURE_PUBLISH_NIP71 } from "../constants.js"; import { accessControl } from "../accessControl.js"; // 🔧 merged conflicting changes from codex/update-video-publishing-and-parsing-logic vs unstable @@ -345,7 +345,9 @@ function withRequestTimeout(promise, timeoutMs, onTimeout, message = "Request ti try { onTimeout(); } catch (cleanupError) { - devLogger.warn("[nostr] COUNT timeout cleanup failed:", cleanupError); + if (isVerboseDevMode) { + devLogger.warn("[nostr] COUNT timeout cleanup failed:", cleanupError); + } } } reject(new Error(message)); @@ -5744,10 +5746,12 @@ export class NostrClient { if (isUnsupported) { this.countUnsupportedRelays.add(url); } else { - devLogger.warn( - `[nostr] COUNT request failed on ${url}:`, - error, - ); + if (isVerboseDevMode) { + devLogger.warn( + `[nostr] COUNT request failed on ${url}:`, + error, + ); + } } return { url, ok: false, error, unsupported: isUnsupported }; } diff --git a/js/nostr/publishHelpers.js b/js/nostr/publishHelpers.js index 077dde60..38eea286 100644 --- a/js/nostr/publishHelpers.js +++ b/js/nostr/publishHelpers.js @@ -1,5 +1,6 @@ import { ENSURE_PRESENCE_REBROADCAST_COOLDOWN_SECONDS, + isVerboseDevMode, } from "../config.js"; import { publishEventToRelays, @@ -1023,7 +1024,9 @@ export async function rebroadcastEvent({ client, eventId, options = {} }) { }) : null; } catch (error) { - devLogger.warn("[nostr] COUNT request for rebroadcast failed:", error); + if (isVerboseDevMode) { + devLogger.warn("[nostr] COUNT request for rebroadcast failed:", error); + } } if (countResult?.total && Number(countResult.total) > 0) { diff --git a/js/nostr/viewEvents.js b/js/nostr/viewEvents.js index 723b93b0..e0e50563 100644 --- a/js/nostr/viewEvents.js +++ b/js/nostr/viewEvents.js @@ -1,6 +1,7 @@ import { VIEW_COUNT_DEDUPE_WINDOW_SECONDS, VIEW_COUNT_BACKFILL_MAX_DAYS, + isVerboseDevMode, } from "../config.js"; import { VIEW_FILTER_INCLUDE_LEGACY_VIDEO } from "../constants.js"; import { @@ -590,7 +591,9 @@ export async function countVideoViewEvents(client, pointer, options = {}) { } } catch (error) { if (error?.code !== "count-unsupported") { - devLogger.warn("[nostr] COUNT view request failed:", error); + if (isVerboseDevMode) { + devLogger.warn("[nostr] COUNT view request failed:", error); + } } } From 8e2f21846a597402167023ff8f39f42ff422b394 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:38:22 -0400 Subject: [PATCH 2/7] Disable verbose dev mode by default --- config/instance-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/instance-config.js b/config/instance-config.js index 493e6931..f2c341d5 100644 --- a/config/instance-config.js +++ b/config/instance-config.js @@ -30,7 +30,7 @@ export const IS_DEV_MODE = true; * request failures) visible in the console. Flip it to `false` when you want * a quieter dev mode without disabling other development helpers entirely. */ -export const IS_VERBOSE_DEV_MODE = true; +export const IS_VERBOSE_DEV_MODE = false; /** * Whether the current deployment should operate in lockdown mode. From 7de8eddcbe25583eedd478b814c882ab946e0eb2 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:48:29 -0400 Subject: [PATCH 3/7] Throttle COUNT diagnostics unless verbose dev mode --- js/nostr/client.js | 31 ++++++++--------- js/nostr/countDiagnostics.js | 65 ++++++++++++++++++++++++++++++++++++ js/nostr/publishHelpers.js | 10 ++---- js/nostr/viewEvents.js | 6 ++-- 4 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 js/nostr/countDiagnostics.js diff --git a/js/nostr/client.js b/js/nostr/client.js index 822de47f..2b5ff8d6 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -1,6 +1,6 @@ // js/nostr/client.js -import { isDevMode, isVerboseDevMode } from "../config.js"; +import { isDevMode } from "../config.js"; import { FEATURE_PUBLISH_NIP71 } from "../constants.js"; import { accessControl } from "../accessControl.js"; // 🔧 merged conflicting changes from codex/update-video-publishing-and-parsing-logic vs unstable @@ -58,6 +58,10 @@ import { listVideoComments as listVideoCommentsForClient, subscribeVideoComments as subscribeVideoCommentsForClient, } from "./commentEvents.js"; +import { + logCountTimeoutCleanupFailure, + logRelayCountFailure, +} from "./countDiagnostics.js"; import { publishEventToRelay, publishEventToRelays, @@ -345,9 +349,7 @@ function withRequestTimeout(promise, timeoutMs, onTimeout, message = "Request ti try { onTimeout(); } catch (cleanupError) { - if (isVerboseDevMode) { - devLogger.warn("[nostr] COUNT timeout cleanup failed:", cleanupError); - } + logCountTimeoutCleanupFailure(cleanupError); } } reject(new Error(message)); @@ -5741,20 +5743,15 @@ export class NostrClient { }); const count = this.extractCountValue(frame?.[2]); return { url, ok: true, frame, count }; - } catch (error) { - const isUnsupported = error?.code === "count-unsupported"; - if (isUnsupported) { - this.countUnsupportedRelays.add(url); - } else { - if (isVerboseDevMode) { - devLogger.warn( - `[nostr] COUNT request failed on ${url}:`, - error, - ); - } - } - return { url, ok: false, error, unsupported: isUnsupported }; + } catch (error) { + const isUnsupported = error?.code === "count-unsupported"; + if (isUnsupported) { + this.countUnsupportedRelays.add(url); + } else { + logRelayCountFailure(url, error); } + return { url, ok: false, error, unsupported: isUnsupported }; + } }) ); diff --git a/js/nostr/countDiagnostics.js b/js/nostr/countDiagnostics.js new file mode 100644 index 00000000..c731cf0f --- /dev/null +++ b/js/nostr/countDiagnostics.js @@ -0,0 +1,65 @@ +// js/nostr/countDiagnostics.js +// Centralized helpers for gating high-volume COUNT diagnostics behind +// verbose dev mode and deduplicating repeated warnings so the console stays +// readable during normal development sessions. + +import { devLogger } from "../utils/logger.js"; +import { isVerboseDevMode } from "../config.js"; + +const seenWarningKeys = new Set(); + +function isVerboseCountLoggingEnabled() { + if (typeof window !== "undefined" && window) { + const runtimeFlag = window.__BITVID_VERBOSE_DEV_MODE__; + if (typeof runtimeFlag === "boolean") { + return runtimeFlag; + } + } + return Boolean(isVerboseDevMode); +} + +function logCountWarning(message, args = [], { key, throttle = true } = {}) { + if (!isVerboseCountLoggingEnabled()) { + return; + } + + if (throttle && key) { + const normalizedKey = String(key); + if (seenWarningKeys.has(normalizedKey)) { + return; + } + seenWarningKeys.add(normalizedKey); + } + + devLogger.warn(message, ...args); +} + +export function logCountTimeoutCleanupFailure(error) { + logCountWarning("[nostr] COUNT timeout cleanup failed:", [error], { + key: "count-timeout-cleanup", + }); +} + +export function logRelayCountFailure(url, error) { + const normalizedUrl = + typeof url === "string" && url.trim() ? url.trim() : "(unknown relay)"; + logCountWarning(`[nostr] COUNT request failed on ${normalizedUrl}:`, [error], { + key: `relay:${normalizedUrl}`, + }); +} + +export function logRebroadcastCountFailure(error) { + logCountWarning("[nostr] COUNT request for rebroadcast failed:", [error], { + key: "rebroadcast-count-failure", + }); +} + +export function logViewCountFailure(error) { + logCountWarning("[nostr] COUNT view request failed:", [error], { + key: "view-count-failure", + }); +} + +export function resetCountWarningHistory() { + seenWarningKeys.clear(); +} diff --git a/js/nostr/publishHelpers.js b/js/nostr/publishHelpers.js index 38eea286..79936110 100644 --- a/js/nostr/publishHelpers.js +++ b/js/nostr/publishHelpers.js @@ -1,7 +1,4 @@ -import { - ENSURE_PRESENCE_REBROADCAST_COOLDOWN_SECONDS, - isVerboseDevMode, -} from "../config.js"; +import { ENSURE_PRESENCE_REBROADCAST_COOLDOWN_SECONDS } from "../config.js"; import { publishEventToRelays, assertAnyRelayAccepted, @@ -17,6 +14,7 @@ import { } from "./toolkit.js"; import { DEFAULT_NIP07_PERMISSION_METHODS } from "./nip07Permissions.js"; import { devLogger, userLogger } from "../utils/logger.js"; +import { logRebroadcastCountFailure } from "./countDiagnostics.js"; const REBROADCAST_GUARD_PREFIX = "bitvid:rebroadcast:v1"; const rebroadcastAttemptMemory = new Map(); @@ -1024,9 +1022,7 @@ export async function rebroadcastEvent({ client, eventId, options = {} }) { }) : null; } catch (error) { - if (isVerboseDevMode) { - devLogger.warn("[nostr] COUNT request for rebroadcast failed:", error); - } + logRebroadcastCountFailure(error); } if (countResult?.total && Number(countResult.total) > 0) { diff --git a/js/nostr/viewEvents.js b/js/nostr/viewEvents.js index e0e50563..1d3bf2c0 100644 --- a/js/nostr/viewEvents.js +++ b/js/nostr/viewEvents.js @@ -1,7 +1,6 @@ import { VIEW_COUNT_DEDUPE_WINDOW_SECONDS, VIEW_COUNT_BACKFILL_MAX_DAYS, - isVerboseDevMode, } from "../config.js"; import { VIEW_FILTER_INCLUDE_LEGACY_VIDEO } from "../constants.js"; import { @@ -13,6 +12,7 @@ import { publishEventToRelay } from "../nostrPublish.js"; import { RELAY_URLS } from "./toolkit.js"; import { normalizePointerInput } from "./watchHistory.js"; import { devLogger, userLogger } from "../utils/logger.js"; +import { logViewCountFailure } from "./countDiagnostics.js"; const VIEW_EVENT_GUARD_PREFIX = "bitvid:viewed"; @@ -591,9 +591,7 @@ export async function countVideoViewEvents(client, pointer, options = {}) { } } catch (error) { if (error?.code !== "count-unsupported") { - if (isVerboseDevMode) { - devLogger.warn("[nostr] COUNT view request failed:", error); - } + logViewCountFailure(error); } } From 487fc740dad0b8e66f5648b99d73c33417926ea0 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 14:58:09 -0400 Subject: [PATCH 4/7] Gate EventEmitter listener warnings behind verbose mode --- js/nostr/client.js | 1 + js/nostr/countDiagnostics.js | 10 ++++----- js/nostr/maxListenerDiagnostics.js | 36 ++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 js/nostr/maxListenerDiagnostics.js diff --git a/js/nostr/client.js b/js/nostr/client.js index 2b5ff8d6..7483262f 100644 --- a/js/nostr/client.js +++ b/js/nostr/client.js @@ -62,6 +62,7 @@ import { logCountTimeoutCleanupFailure, logRelayCountFailure, } from "./countDiagnostics.js"; +import "./maxListenerDiagnostics.js"; import { publishEventToRelay, publishEventToRelays, diff --git a/js/nostr/countDiagnostics.js b/js/nostr/countDiagnostics.js index c731cf0f..7f63c6b0 100644 --- a/js/nostr/countDiagnostics.js +++ b/js/nostr/countDiagnostics.js @@ -1,14 +1,14 @@ // js/nostr/countDiagnostics.js -// Centralized helpers for gating high-volume COUNT diagnostics behind -// verbose dev mode and deduplicating repeated warnings so the console stays -// readable during normal development sessions. +// Centralized helpers for gating high-volume COUNT diagnostics (and other +// verbose nostr warnings) behind verbose dev mode while deduplicating repeated +// messages so the console stays readable during normal development sessions. import { devLogger } from "../utils/logger.js"; import { isVerboseDevMode } from "../config.js"; const seenWarningKeys = new Set(); -function isVerboseCountLoggingEnabled() { +export function isVerboseDiagnosticsEnabled() { if (typeof window !== "undefined" && window) { const runtimeFlag = window.__BITVID_VERBOSE_DEV_MODE__; if (typeof runtimeFlag === "boolean") { @@ -19,7 +19,7 @@ function isVerboseCountLoggingEnabled() { } function logCountWarning(message, args = [], { key, throttle = true } = {}) { - if (!isVerboseCountLoggingEnabled()) { + if (!isVerboseDiagnosticsEnabled()) { return; } diff --git a/js/nostr/maxListenerDiagnostics.js b/js/nostr/maxListenerDiagnostics.js new file mode 100644 index 00000000..09e3bbda --- /dev/null +++ b/js/nostr/maxListenerDiagnostics.js @@ -0,0 +1,36 @@ +// js/nostr/maxListenerDiagnostics.js +// Applies verbose-dev-mode-aware defaults to EventEmitter listener limits so +// high-volume MaxListeners warnings stay hidden unless explicitly requested. + +import EventEmitter from "events"; + +import { isVerboseDiagnosticsEnabled } from "./countDiagnostics.js"; + +const DEFAULT_WARNING_THRESHOLD = 10; + +function getTargetMaxListeners() { + return isVerboseDiagnosticsEnabled() ? DEFAULT_WARNING_THRESHOLD : 0; +} + +function applyMaxListenerPreference() { + try { + const targetLimit = getTargetMaxListeners(); + if (typeof EventEmitter.defaultMaxListeners === "number") { + if (EventEmitter.defaultMaxListeners !== targetLimit) { + EventEmitter.defaultMaxListeners = targetLimit; + } + return; + } + + EventEmitter.defaultMaxListeners = targetLimit; + } catch (error) { + // Swallow errors silently — diagnostics are best-effort and shouldn't + // interfere with runtime behavior if the events shim is unavailable. + } +} + +applyMaxListenerPreference(); + +export function refreshMaxListenerPreference() { + applyMaxListenerPreference(); +} From df0b5ba3df19e0a673b22e7f90d4e94a440805db Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:04:08 -0400 Subject: [PATCH 5/7] Fix verbose-only max listener warning gate --- js/nostr/maxListenerDiagnostics.js | 89 +++++++++++++++++++++++------- 1 file changed, 68 insertions(+), 21 deletions(-) diff --git a/js/nostr/maxListenerDiagnostics.js b/js/nostr/maxListenerDiagnostics.js index 09e3bbda..d151ba60 100644 --- a/js/nostr/maxListenerDiagnostics.js +++ b/js/nostr/maxListenerDiagnostics.js @@ -1,36 +1,83 @@ // js/nostr/maxListenerDiagnostics.js -// Applies verbose-dev-mode-aware defaults to EventEmitter listener limits so -// high-volume MaxListeners warnings stay hidden unless explicitly requested. - -import EventEmitter from "events"; +// Suppresses noisy MaxListenersExceededWarning diagnostics unless verbose +// dev mode is enabled. We hook into process.emitWarning because Node's +// EventEmitter implementation routes listener-limit warnings through that +// API, even in the browser bundles pulled in by WebTorrent. import { isVerboseDiagnosticsEnabled } from "./countDiagnostics.js"; -const DEFAULT_WARNING_THRESHOLD = 10; +let originalEmitWarning; +let isPatched = false; -function getTargetMaxListeners() { - return isVerboseDiagnosticsEnabled() ? DEFAULT_WARNING_THRESHOLD : 0; +function extractWarningName(warning, rest) { + if (warning && typeof warning === "object" && typeof warning.name === "string") { + return warning.name; + } + + if (typeof warning === "string") { + return rest && rest.length > 0 && typeof rest[0] === "string" + ? rest[0] + : undefined; + } + + if (rest && rest.length > 0) { + const [firstArg] = rest; + if (typeof firstArg === "string") { + return firstArg; + } + if (firstArg && typeof firstArg === "object" && typeof firstArg.name === "string") { + return firstArg.name; + } + } + + return undefined; } -function applyMaxListenerPreference() { - try { - const targetLimit = getTargetMaxListeners(); - if (typeof EventEmitter.defaultMaxListeners === "number") { - if (EventEmitter.defaultMaxListeners !== targetLimit) { - EventEmitter.defaultMaxListeners = targetLimit; - } +function shouldSuppressWarning(warning, rest) { + if (isVerboseDiagnosticsEnabled()) { + return false; + } + + const warningName = extractWarningName(warning, rest); + if (warningName === "MaxListenersExceededWarning") { + return true; + } + + if (typeof warning === "string") { + return warning.includes("MaxListenersExceededWarning"); + } + + if (warning && typeof warning.message === "string") { + return warning.message.includes("MaxListenersExceededWarning"); + } + + return false; +} + +function applyMaxListenerWarningFilter() { + if (isPatched) { + return; + } + + const processRef = typeof process !== "undefined" ? process : undefined; + if (!processRef || typeof processRef.emitWarning !== "function") { + return; + } + + originalEmitWarning = processRef.emitWarning.bind(processRef); + processRef.emitWarning = function patchedEmitWarning(warning, ...rest) { + if (shouldSuppressWarning(warning, rest)) { return; } + return originalEmitWarning(warning, ...rest); + }; - EventEmitter.defaultMaxListeners = targetLimit; - } catch (error) { - // Swallow errors silently — diagnostics are best-effort and shouldn't - // interfere with runtime behavior if the events shim is unavailable. - } + isPatched = true; } -applyMaxListenerPreference(); +applyMaxListenerWarningFilter(); export function refreshMaxListenerPreference() { - applyMaxListenerPreference(); + // No-op for now; retained for API parity in case verbose mode toggles at + // runtime and we need to evolve this helper later. } From df186fdbeae57f685c56e601ce1b5805bbba1a84 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:11:46 -0400 Subject: [PATCH 6/7] Filter max listener warnings via console patch --- js/nostr/maxListenerDiagnostics.js | 74 +++++++++++++++--------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/js/nostr/maxListenerDiagnostics.js b/js/nostr/maxListenerDiagnostics.js index d151ba60..11f78877 100644 --- a/js/nostr/maxListenerDiagnostics.js +++ b/js/nostr/maxListenerDiagnostics.js @@ -1,54 +1,51 @@ // js/nostr/maxListenerDiagnostics.js // Suppresses noisy MaxListenersExceededWarning diagnostics unless verbose -// dev mode is enabled. We hook into process.emitWarning because Node's -// EventEmitter implementation routes listener-limit warnings through that -// API, even in the browser bundles pulled in by WebTorrent. +// dev mode is enabled by filtering console.warn output. Node's EventEmitter +// polyfill emits these warnings via console.warn in browser bundles, so we +// intercept the console instead of importing the `events` module or +// patching process APIs that might vary across environments. import { isVerboseDiagnosticsEnabled } from "./countDiagnostics.js"; -let originalEmitWarning; +const MAX_LISTENER_SNIPPETS = [ + "MaxListenersExceededWarning", + "Possible EventEmitter memory leak detected", +]; + +let originalConsoleWarn; let isPatched = false; -function extractWarningName(warning, rest) { - if (warning && typeof warning === "object" && typeof warning.name === "string") { - return warning.name; +function extractCandidateStrings(value) { + if (typeof value === "string") { + return [value]; } - if (typeof warning === "string") { - return rest && rest.length > 0 && typeof rest[0] === "string" - ? rest[0] - : undefined; - } - - if (rest && rest.length > 0) { - const [firstArg] = rest; - if (typeof firstArg === "string") { - return firstArg; + if (value && typeof value === "object") { + const candidates = []; + if (typeof value.name === "string") { + candidates.push(value.name); } - if (firstArg && typeof firstArg === "object" && typeof firstArg.name === "string") { - return firstArg.name; + if (typeof value.message === "string") { + candidates.push(value.message); } + return candidates; } - return undefined; + return []; } -function shouldSuppressWarning(warning, rest) { +function shouldSuppressArgs(args) { if (isVerboseDiagnosticsEnabled()) { return false; } - const warningName = extractWarningName(warning, rest); - if (warningName === "MaxListenersExceededWarning") { - return true; - } - - if (typeof warning === "string") { - return warning.includes("MaxListenersExceededWarning"); - } - - if (warning && typeof warning.message === "string") { - return warning.message.includes("MaxListenersExceededWarning"); + for (const arg of args) { + const candidates = extractCandidateStrings(arg); + for (const candidate of candidates) { + if (MAX_LISTENER_SNIPPETS.some((snippet) => candidate.includes(snippet))) { + return true; + } + } } return false; @@ -59,17 +56,18 @@ function applyMaxListenerWarningFilter() { return; } - const processRef = typeof process !== "undefined" ? process : undefined; - if (!processRef || typeof processRef.emitWarning !== "function") { + const consoleRef = typeof console !== "undefined" ? console : null; + if (!consoleRef || typeof consoleRef.warn !== "function") { return; } - originalEmitWarning = processRef.emitWarning.bind(processRef); - processRef.emitWarning = function patchedEmitWarning(warning, ...rest) { - if (shouldSuppressWarning(warning, rest)) { + originalConsoleWarn = consoleRef.warn.bind(consoleRef); + consoleRef.warn = function patchedConsoleWarn(...args) { + if (shouldSuppressArgs(args)) { return; } - return originalEmitWarning(warning, ...rest); + + return originalConsoleWarn(...args); }; isPatched = true; From a18f52d390400d249f8e435ca8344e2e3434b5d1 Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Mon, 27 Oct 2025 15:20:48 -0400 Subject: [PATCH 7/7] Patch max listener warning suppression --- js/nostr/maxListenerDiagnostics.js | 96 ++++++++++++++++++------------ 1 file changed, 58 insertions(+), 38 deletions(-) diff --git a/js/nostr/maxListenerDiagnostics.js b/js/nostr/maxListenerDiagnostics.js index 11f78877..cc11d494 100644 --- a/js/nostr/maxListenerDiagnostics.js +++ b/js/nostr/maxListenerDiagnostics.js @@ -1,9 +1,10 @@ // js/nostr/maxListenerDiagnostics.js // Suppresses noisy MaxListenersExceededWarning diagnostics unless verbose -// dev mode is enabled by filtering console.warn output. Node's EventEmitter -// polyfill emits these warnings via console.warn in browser bundles, so we -// intercept the console instead of importing the `events` module or -// patching process APIs that might vary across environments. +// dev mode is enabled by shimming `process.emitWarning`. The EventEmitter +// polyfill used by nostr-tools surfaces listener leaks through +// `process.emitWarning`, so we intercept that hook instead of importing the +// Node `events` module (which breaks browser bundlers) or rewriting +// console.warn globally. import { isVerboseDiagnosticsEnabled } from "./countDiagnostics.js"; @@ -12,70 +13,89 @@ const MAX_LISTENER_SNIPPETS = [ "Possible EventEmitter memory leak detected", ]; -let originalConsoleWarn; -let isPatched = false; +const MAX_LISTENER_CODES = new Set(["MaxListenersExceededWarning"]); -function extractCandidateStrings(value) { - if (typeof value === "string") { - return [value]; - } +function collectCandidateStrings(value) { + const candidates = []; - if (value && typeof value === "object") { - const candidates = []; - if (typeof value.name === "string") { - candidates.push(value.name); - } - if (typeof value.message === "string") { - candidates.push(value.message); - } + if (!value) { return candidates; } - return []; + if (typeof value === "string") { + candidates.push(value); + return candidates; + } + + if (typeof value === "object") { + const fields = ["name", "message", "code", "type"]; + for (const field of fields) { + const fieldValue = value[field]; + if (typeof fieldValue === "string") { + candidates.push(fieldValue); + } + } + } + + return candidates; } -function shouldSuppressArgs(args) { +function shouldSuppressWarning(...args) { if (isVerboseDiagnosticsEnabled()) { return false; } + const candidates = []; for (const arg of args) { - const candidates = extractCandidateStrings(arg); - for (const candidate of candidates) { - if (MAX_LISTENER_SNIPPETS.some((snippet) => candidate.includes(snippet))) { - return true; - } + candidates.push(...collectCandidateStrings(arg)); + } + + for (const candidate of candidates) { + if (MAX_LISTENER_CODES.has(candidate)) { + return true; + } + if (MAX_LISTENER_SNIPPETS.some((snippet) => candidate.includes(snippet))) { + return true; } } return false; } -function applyMaxListenerWarningFilter() { - if (isPatched) { +function patchProcessEmitWarning() { + const processRef = + typeof globalThis !== "undefined" && globalThis + ? globalThis.process + : typeof process !== "undefined" + ? process + : undefined; + + if (!processRef || typeof processRef.emitWarning !== "function") { return; } - const consoleRef = typeof console !== "undefined" ? console : null; - if (!consoleRef || typeof consoleRef.warn !== "function") { + const originalEmitWarning = processRef.emitWarning.bind(processRef); + + if (processRef.emitWarning.__BITVID_MAX_LISTENER_PATCHED__) { return; } - originalConsoleWarn = consoleRef.warn.bind(consoleRef); - consoleRef.warn = function patchedConsoleWarn(...args) { - if (shouldSuppressArgs(args)) { + function patchedEmitWarning(warning, ...rest) { + if (shouldSuppressWarning(warning, ...rest)) { return; } - return originalConsoleWarn(...args); - }; + return originalEmitWarning(warning, ...rest); + } - isPatched = true; + patchedEmitWarning.__BITVID_MAX_LISTENER_PATCHED__ = true; + processRef.emitWarning = patchedEmitWarning; } -applyMaxListenerWarningFilter(); +patchProcessEmitWarning(); export function refreshMaxListenerPreference() { - // No-op for now; retained for API parity in case verbose mode toggles at - // runtime and we need to evolve this helper later. + // The shim dynamically checks the verbose flag on each invocation, so there + // is nothing else to refresh right now. This placeholder remains for API + // parity if runtime toggling ever requires additional work. }