diff --git a/common/modules/debug.js b/common/modules/debug.js new file mode 100644 index 00000000..65e80961 --- /dev/null +++ b/common/modules/debug.js @@ -0,0 +1,545 @@ +/* eslint-env browser */ +// patched version of debug because there's actually not a way to disable colors globally! + +/** + * This is the web browser implementation of `debug()`. + */ + +exports.formatArgs = formatArgs +exports.save = save +exports.load = load +exports.useColors = useColors +exports.storage = localstorage() +exports.destroy = (() => { + let warned = false + + return () => { + if (!warned) { + warned = true + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.') + } + } +})() + +/** + * Colors. + */ + +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +] + +const { formatters = {} } = module.exports + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +// eslint-disable-next-line complexity +function useColors () { + return false + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false + } + + let m + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)) +} + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs (args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff) + + if (!this.useColors) { + return + } + + const c = 'color: ' + this.color + args.splice(1, 0, c, 'color: inherit') + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0 + let lastC = 0 + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return + } + index++ + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index + } + }) + + args.splice(lastC, 0, c) +} + +/** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ +exports.log = console.debug || console.log || (() => {}) + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save (namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces) + } else { + exports.storage.removeItem('debug') + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ +function load () { + let r + try { + r = exports.storage.getItem('debug') + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG + } + + return r +} + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage () { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v) + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message + } +} + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + +function setup (env) { + createDebug.debug = createDebug + createDebug.default = createDebug + createDebug.coerce = coerce + createDebug.disable = disable + createDebug.enable = enable + createDebug.enabled = enabled + createDebug.humanize = require('ms') + createDebug.destroy = destroy + + Object.keys(env).forEach(key => { + createDebug[key] = env[key] + }) + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = [] + createDebug.skips = [] + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {} + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor (namespace) { + let hash = 0 + + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i) + hash |= 0 // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length] + } + createDebug.selectColor = selectColor + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug (namespace) { + let prevTime + let enableOverride = null + let namespacesCache + let enabledCache + + function debug (...args) { + // Disabled? + if (!debug.enabled) { + return + } + + const self = debug + + // Set `diff` timestamp + const curr = Number(new Date()) + const ms = curr - (prevTime || curr) + self.diff = ms + self.prev = prevTime + self.curr = curr + prevTime = curr + + args[0] = createDebug.coerce(args[0]) + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O') + } + + // Apply any `formatters` transformations + let index = 0 + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%' + } + index++ + const formatter = createDebug.formatters[format] + if (typeof formatter === 'function') { + const val = args[index] + match = formatter.call(self, val) + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1) + index-- + } + return match + }) + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args) + + const logFn = self.log || createDebug.log + logFn.apply(self, args) + } + + debug.namespace = namespace + debug.useColors = createDebug.useColors() + debug.color = createDebug.selectColor(namespace) + debug.extend = extend + debug.destroy = createDebug.destroy // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces + enabledCache = createDebug.enabled(namespace) + } + + return enabledCache + }, + set: v => { + enableOverride = v + } + }) + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug) + } + + return debug + } + + function extend (namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace) + newDebug.log = this.log + return newDebug + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable (namespaces) { + createDebug.save(namespaces) + createDebug.namespaces = namespaces + + createDebug.names = [] + createDebug.skips = [] + + let i + const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/) + const len = split.length + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue + } + + namespaces = split[i].replace(/\*/g, '.*?') + + if (namespaces[0] === '-') { + createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$')) + } else { + createDebug.names.push(new RegExp('^' + namespaces + '$')) + } + } + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable () { + const namespaces = [ + ...createDebug.names.map(toNamespace), + ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) + ].join(',') + createDebug.enable('') + return namespaces + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled (name) { + if (name[name.length - 1] === '*') { + return true + } + + let i + let len + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true + } + } + + return false + } + + /** + * Convert regexp to namespace + * + * @param {RegExp} regxep + * @return {String} namespace + * @api private + */ + function toNamespace (regexp) { + return regexp.toString() + .substring(2, regexp.toString().length - 2) + .replace(/\.\*\?$/, '*') + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce (val) { + if (val instanceof Error) { + return val.stack || val.message + } + return val + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy () { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.') + } + + createDebug.enable(createDebug.load()) + + return createDebug +} + +module.exports = setup(exports) diff --git a/common/webpack.config.cjs b/common/webpack.config.cjs index 6f3fa9a4..2b146393 100644 --- a/common/webpack.config.cjs +++ b/common/webpack.config.cjs @@ -59,6 +59,7 @@ module.exports = (parentDir, alias = {}, aliasFields = 'browser', filename = 'ap '@': __dirname, module: false, url: false, + debug: resolve(__dirname, './modules/debug.js'), 'bittorrent-tracker/lib/client/websocket-tracker.js': resolve('../node_modules/bittorrent-tracker/lib/client/websocket-tracker.js') }, extensions: ['.mjs', '.js', '.svelte'] diff --git a/electron/webpack.config.cjs b/electron/webpack.config.cjs index 29182546..71c5d8c5 100644 --- a/electron/webpack.config.cjs +++ b/electron/webpack.config.cjs @@ -27,6 +27,7 @@ module.exports = [ 'node-fetch': false, ws: false, wrtc: false, + debug: resolve(__dirname, '../common/modules/debug.js'), 'bittorrent-tracker/lib/client/http-tracker.js': resolve('../node_modules/bittorrent-tracker/lib/client/http-tracker.js'), 'webrtc-polyfill': resolve('../node_modules/webrtc-polyfill/browser.js') }