/** * Browser/Node.js Compatibility Layer * * Provides compatibility utilities and polyfills for: * - Cross-platform timing functions * - Memory management utilities * - Performance monitoring * - Promise implementations * - Error handling consistency */ /** * Memory management utilities with WeakSet fallback * @returns {Array} [checkCircular, removeFromMemory] */ function createMemoBuilder() { const supportsWeakSet = typeof WeakSet === "function"; const processed = supportsWeakSet ? new WeakSet() : []; function checkCircular(obj) { if (supportsWeakSet) { if (processed.has(obj)) return true; processed.add(obj); return false; } else { // Array fallback for environments without WeakSet for (let i = 0; i < processed.length; i++) { if (processed[i] === obj) return true; } processed.push(obj); return false; } } function removeFromMemory(obj) { if (supportsWeakSet) { processed.delete(obj); } else { for (let i = 0; i < processed.length; i++) { if (processed[i] === obj) { processed.splice(i, 1); break; } } } } return [checkCircular, removeFromMemory]; } /** * Cross-platform high-resolution timing */ class TimingUtils { constructor() { this.MS_PER_SECOND = 1000; this._timeOriginMode = this._detectTimeOriginMode(); this._performanceTimeOrigin = this._calculateTimeOrigin(); } /** * Get current timestamp in seconds with high precision * @returns {number} Timestamp in seconds */ timestampInSeconds() { return this._getHighResolutionTime() / this.MS_PER_SECOND; } /** * Get current timestamp in milliseconds with high precision * @returns {number} Timestamp in milliseconds */ timestampWithMs() { return this._getHighResolutionTime(); } /** * Get current date timestamp in seconds (lower precision) * @returns {number} Date timestamp in seconds */ dateTimestampInSeconds() { return Date.now() / this.MS_PER_SECOND; } /** * Get browser performance time origin * @returns {number} Performance time origin */ get browserPerformanceTimeOrigin() { return this._performanceTimeOrigin; } /** * Get the time origin mode being used * @returns {string} Time origin mode */ get timeOriginMode() { return this._timeOriginMode; } /** * Detect the best available high-resolution timing method * @returns {Function} High resolution time function * @private */ _detectTimeOriginMode() { const global = this._getGlobalObject(); const performance = global.performance; if (!performance || !performance.now) { return "dateNow"; } const currentTime = Date.now() - performance.now(); const timeOriginDelta = performance.timeOrigin ? Math.abs(performance.timeOrigin + performance.now() - Date.now()) : 3600000; // 1 hour fallback const timingNavigationStart = performance.timing && performance.timing.navigationStart; const navigationDelta = typeof timingNavigationStart === "number" ? Math.abs(timingNavigationStart + performance.now() - Date.now()) : 3600000; const timeOriginIsGood = timeOriginDelta < 3600000; const navigationIsGood = navigationDelta < 3600000; if (timeOriginIsGood || navigationIsGood) { if (timeOriginDelta <= navigationDelta) { return "timeOrigin"; } else { return "navigationStart"; } } return "dateNow"; } /** * Calculate the performance time origin * @returns {number} Calculated time origin * @private */ _calculateTimeOrigin() { const global = this._getGlobalObject(); const performance = global.performance; if (!performance || !performance.now) { return Date.now(); } const currentTime = Date.now(); const performanceNow = performance.now(); if (this._timeOriginMode === "timeOrigin" && performance.timeOrigin) { return performance.timeOrigin; } else if ( this._timeOriginMode === "navigationStart" && performance.timing?.navigationStart ) { return performance.timing.navigationStart; } else { return currentTime - performanceNow; } } /** * Get high resolution time using the best available method * @returns {number} High resolution timestamp * @private */ _getHighResolutionTime() { if (this._timeOriginMode === "dateNow") { return Date.now(); } const global = this._getGlobalObject(); const performance = global.performance; return this._performanceTimeOrigin + performance.now(); } /** * Get global object * @returns {Object} Global object * @private */ _getGlobalObject() { return ( (typeof globalThis === "object" && globalThis) || (typeof window === "object" && window) || (typeof self === "object" && self) || (typeof global === "object" && global) || {} ); } } // Singleton instance const timingUtils = new TimingUtils(); /** * Compatibility layer for async operations */ class AsyncCompatibility { /** * Nullish coalescing operator compatibility * @param {any} value - Value to check * @param {Function} fallback - Fallback function * @returns {any} Value or fallback result */ static nullishCoalesce(value, fallback) { return value != null ? value : fallback(); } /** * Async nullish coalescing * @param {any} value - Value to check * @param {Function} fallback - Async fallback function * @returns {Promise} Value or fallback result */ static async asyncNullishCoalesce(value, fallback) { return this.nullishCoalesce(value, fallback); } /** * Optional chaining compatibility * @param {Array} operations - Chain operations * @returns {any} Result of chain or undefined */ static optionalChain(operations) { let context = undefined; let value = operations[0]; let index = 1; while (index < operations.length) { const operation = operations[index]; const accessor = operations[index + 1]; index += 2; if ( (operation === "optionalAccess" || operation === "optionalCall") && value == null ) { return undefined; } if (operation === "access" || operation === "optionalAccess") { context = value; value = accessor(value); } else if (operation === "call" || operation === "optionalCall") { value = accessor((...args) => value.call(context, ...args)); context = undefined; } } return value; } /** * Async optional chaining * @param {Array} operations - Chain operations * @returns {Promise} Result of chain or undefined */ static async asyncOptionalChain(operations) { let context = undefined; let value = operations[0]; let index = 1; while (index < operations.length) { const operation = operations[index]; const accessor = operations[index + 1]; index += 2; if ( (operation === "optionalAccess" || operation === "optionalCall") && value == null ) { return undefined; } if (operation === "access" || operation === "optionalAccess") { context = value; value = await accessor(value); } else if (operation === "call" || operation === "optionalCall") { value = await accessor((...args) => value.call(context, ...args)); context = undefined; } } return value; } /** * Optional chain delete compatibility * @param {Array} operations - Chain operations * @returns {boolean} True if deletion was successful or target was null */ static optionalChainDelete(operations) { const result = this.optionalChain(operations); return result == null ? true : result; } /** * Async optional chain delete compatibility * @param {Array} operations - Chain operations * @returns {Promise} True if deletion was successful or target was null */ static async asyncOptionalChainDelete(operations) { const result = await this.asyncOptionalChain(operations); return result == null ? true : result; } } /** * String escaping utilities for security */ class StringEscaping { /** * Escape string for use in regular expressions * @param {string} str - String to escape * @returns {string} Escaped string */ static escapeStringForRegex(str) { return str .replace(/[|\\{}()[\]^$+*?.]/g, "\\$&") // Escape regex special chars .replace(/-/g, "\\x2d"); // Escape hyphens } /** * Escape HTML characters * @param {string} str - String to escape * @returns {string} HTML-escaped string */ static escapeHtml(str) { const htmlEscapes = { "&": "&", "<": "<", ">": ">", '"': """, "'": "'", "/": "/", }; return str.replace(/[&<>"'/]/g, (char) => htmlEscapes[char]); } /** * Escape string for shell command usage * @param {string} str - String to escape * @returns {string} Shell-escaped string */ static escapeShell(str) { if (str === "") return "''"; // If no special characters, return as-is if (!/[^a-zA-Z0-9_\-.,/@]/.test(str)) { return str; } // Single quote and escape any existing single quotes return "'" + str.replace(/'/g, "'\"'\"'") + "'"; } } /** * Feature detection utilities */ class FeatureDetection { /** * Test if a feature is available * @param {string} feature - Feature name to test * @returns {boolean} True if feature is available */ static hasFeature(feature) { const tests = { weakset: () => typeof WeakSet === "function", weakmap: () => typeof WeakMap === "function", map: () => typeof Map === "function", set: () => typeof Set === "function", promise: () => typeof Promise === "function", proxy: () => typeof Proxy === "function", symbol: () => typeof Symbol === "function", bigint: () => typeof BigInt === "function", fetch: () => typeof fetch === "function", websocket: () => typeof WebSocket === "function", indexeddb: () => typeof indexedDB !== "undefined", localstorage: () => { try { return typeof localStorage !== "undefined" && localStorage !== null; } catch (e) { return false; } }, crypto: () => typeof crypto !== "undefined", performance: () => typeof performance !== "undefined", }; const test = tests[feature.toLowerCase()]; return test ? test() : false; } /** * Get list of available features * @returns {Array} List of available feature names */ static getAvailableFeatures() { const features = [ "weakset", "weakmap", "map", "set", "promise", "proxy", "symbol", "bigint", "fetch", "websocket", "indexeddb", "localstorage", "crypto", "performance", ]; return features.filter((feature) => this.hasFeature(feature)); } } module.exports = { // Memory management createMemoBuilder, // Timing utilities TimingUtils, timingUtils, timestampInSeconds: () => timingUtils.timestampInSeconds(), timestampWithMs: () => timingUtils.timestampWithMs(), dateTimestampInSeconds: () => timingUtils.dateTimestampInSeconds(), browserPerformanceTimeOrigin: timingUtils.browserPerformanceTimeOrigin, // Async compatibility AsyncCompatibility, nullishCoalesce: AsyncCompatibility.nullishCoalesce, asyncNullishCoalesce: AsyncCompatibility.asyncNullishCoalesce, optionalChain: AsyncCompatibility.optionalChain, asyncOptionalChain: AsyncCompatibility.asyncOptionalChain, optionalChainDelete: AsyncCompatibility.optionalChainDelete, asyncOptionalChainDelete: AsyncCompatibility.asyncOptionalChainDelete, // String utilities StringEscaping, escapeStringForRegex: StringEscaping.escapeStringForRegex, escapeHtml: StringEscaping.escapeHtml, escapeShell: StringEscaping.escapeShell, // Feature detection FeatureDetection, hasFeature: FeatureDetection.hasFeature, getAvailableFeatures: FeatureDetection.getAvailableFeatures, };