From 44b8764e19f54d2c1a0b7d2c3203830b345b286e Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Wed, 15 Oct 2025 11:31:05 -0400 Subject: [PATCH] Add shared logger utility --- js/utils/logger.js | 162 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 js/utils/logger.js diff --git a/js/utils/logger.js b/js/utils/logger.js new file mode 100644 index 00000000..9cad91cc --- /dev/null +++ b/js/utils/logger.js @@ -0,0 +1,162 @@ +// js/utils/logger.js +// Provides frozen logger channels so both modules and inline scripts share +// consistent dev-vs-user logging semantics without duplicating feature gates. + +import { isDevMode } from "../config.js"; + +const NOOP = () => {}; + +function resolveConsole() { + if (typeof console === "undefined" || console === null) { + return null; + } + return console; +} + +/** + * Returns a console shim with safe wrappers for the standard log levels. + * Each method gracefully falls back to console.log or a noop when unavailable. + */ +function createConsoleAdapter() { + const consoleRef = resolveConsole(); + if (!consoleRef) { + return { + log: NOOP, + info: NOOP, + warn: NOOP, + error: NOOP, + }; + } + + const fallback = typeof consoleRef.log === "function" ? consoleRef.log.bind(consoleRef) : NOOP; + + return { + log: typeof consoleRef.log === "function" ? consoleRef.log.bind(consoleRef) : fallback, + info: typeof consoleRef.info === "function" ? consoleRef.info.bind(consoleRef) : fallback, + warn: typeof consoleRef.warn === "function" ? consoleRef.warn.bind(consoleRef) : fallback, + error: typeof consoleRef.error === "function" ? consoleRef.error.bind(consoleRef) : fallback, + }; +} + +const consoleAdapter = createConsoleAdapter(); +const USER_PREFIX = "[bitvid]"; + +function extractForceFlag(args) { + if (args.length === 0) { + return { args, force: false }; + } + + const last = args[args.length - 1]; + if (last && typeof last === "object" && !Array.isArray(last) && Object.prototype.hasOwnProperty.call(last, "force")) { + const cloned = [...args]; + const options = cloned.pop(); + return { args: cloned, force: Boolean(options.force) }; + } + + return { args, force: false }; +} + +const devLogger = Object.freeze({ + log: (...args) => { + if (!isDevMode) return; + consoleAdapter.log(...args); + }, + info: (...args) => { + if (!isDevMode) return; + consoleAdapter.info(...args); + }, + warn: (...args) => { + if (!isDevMode) return; + consoleAdapter.warn(...args); + }, + error: (...args) => { + if (!isDevMode) return; + consoleAdapter.error(...args); + }, +}); + +const userLogger = Object.freeze({ + log: (...rawArgs) => { + const { args, force } = extractForceFlag(rawArgs); + if (!force && args.length === 0) { + return; + } + + if (force) { + consoleAdapter.log(USER_PREFIX, ...args); + return; + } + + const [message, ...rest] = args; + if (typeof message === "string" && rest.length === 0) { + consoleAdapter.log(`${USER_PREFIX} ${message}`); + return; + } + + if (message !== undefined) { + consoleAdapter.log(USER_PREFIX, message); + } + }, + info: (...rawArgs) => { + const { args, force } = extractForceFlag(rawArgs); + if (!force && args.length === 0) { + return; + } + + if (force) { + consoleAdapter.info(USER_PREFIX, ...args); + return; + } + + const [message, ...rest] = args; + if (typeof message === "string" && rest.length === 0) { + consoleAdapter.info(`${USER_PREFIX} ${message}`); + return; + } + + if (message !== undefined) { + consoleAdapter.info(USER_PREFIX, message); + } + }, + warn: (...args) => { + consoleAdapter.warn(USER_PREFIX, ...args); + }, + error: (...args) => { + consoleAdapter.error(USER_PREFIX, ...args); + }, +}); + +const logger = Object.freeze({ + dev: devLogger, + user: userLogger, +}); + +function isCompatibleLogger(candidate) { + if (!candidate || typeof candidate !== "object") { + return false; + } + + const requiredChannels = ["dev", "user"]; + const requiredMethods = ["log", "info", "warn", "error"]; + + return requiredChannels.every((channel) => { + const obj = candidate[channel]; + return ( + obj && + typeof obj === "object" && + requiredMethods.every((method) => typeof obj[method] === "function") + ); + }); +} + +if (typeof window !== "undefined") { + if (!isCompatibleLogger(window.bitvidLogger)) { + window.bitvidLogger = logger; + } +} + +Object.freeze(logger); + +export default logger; +export const devLogger = logger.dev; +export const userLogger = logger.user;