UNPKG

awake-lock

Version:

A comprehensive wake lock library for preventing device sleep with intelligent fallback strategies and exceptional performance

1,304 lines (1,288 loc) 65.4 kB
'use strict'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; class WakeLockError extends Error { constructor(message, code, strategy, originalError) { super(message); this.name = 'WakeLockError'; this.code = code; this.strategy = strategy; this.originalError = originalError; } } var WakeLockErrorCode; (function (WakeLockErrorCode) { WakeLockErrorCode["NOT_SUPPORTED"] = "NOT_SUPPORTED"; WakeLockErrorCode["PERMISSION_DENIED"] = "PERMISSION_DENIED"; WakeLockErrorCode["INVALID_STATE"] = "INVALID_STATE"; WakeLockErrorCode["TIMEOUT"] = "TIMEOUT"; WakeLockErrorCode["BATTERY_LOW"] = "BATTERY_LOW"; WakeLockErrorCode["STRATEGY_FAILED"] = "STRATEGY_FAILED"; WakeLockErrorCode["UNKNOWN"] = "UNKNOWN"; })(WakeLockErrorCode || (WakeLockErrorCode = {})); class EventEmitter { constructor() { this.listeners = new Map(); this.onceListeners = new Map(); } addEventListener(type, listener, options) { const once = typeof options === 'object' ? options.once : false; const listenerMap = once ? this.onceListeners : this.listeners; if (!listenerMap.has(type)) { listenerMap.set(type, new Set()); } listenerMap.get(type).add(listener); } removeEventListener(type, listener) { var _a, _b; (_a = this.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.delete(listener); (_b = this.onceListeners.get(type)) === null || _b === void 0 ? void 0 : _b.delete(listener); } dispatchEvent(type, event) { const regularListeners = this.listeners.get(type); const onceListeners = this.onceListeners.get(type); if (regularListeners) { for (const listener of regularListeners) { try { listener(event); } catch (error) { console.error(`Error in event listener for '${String(type)}':`, error); } } } if (onceListeners) { for (const listener of onceListeners) { try { listener(event); } catch (error) { console.error(`Error in event listener for '${String(type)}':`, error); } } this.onceListeners.delete(type); } return true; } on(type, listener) { this.addEventListener(type, listener); } off(type, listener) { this.removeEventListener(type, listener); } once(type, listener) { this.addEventListener(type, listener, { once: true }); } emit(type, event) { return this.dispatchEvent(type, event); } listenerCount(type) { var _a, _b, _c, _d; const regular = (_b = (_a = this.listeners.get(type)) === null || _a === void 0 ? void 0 : _a.size) !== null && _b !== void 0 ? _b : 0; const once = (_d = (_c = this.onceListeners.get(type)) === null || _c === void 0 ? void 0 : _c.size) !== null && _d !== void 0 ? _d : 0; return regular + once; } hasListeners(type) { return this.listenerCount(type) > 0; } removeAllListeners(type) { if (type !== undefined) { this.listeners.delete(type); this.onceListeners.delete(type); } else { this.listeners.clear(); this.onceListeners.clear(); } } eventNames() { const names = new Set(); for (const name of this.listeners.keys()) { names.add(name); } for (const name of this.onceListeners.keys()) { names.add(name); } return Array.from(names); } } function isSSR() { return typeof window === 'undefined' || typeof document === 'undefined'; } function isWakeLockSupported() { var _a; if (isSSR()) return false; return 'wakeLock' in navigator && typeof ((_a = navigator.wakeLock) === null || _a === void 0 ? void 0 : _a.request) === 'function'; } function isPermissionsSupported() { var _a; if (isSSR()) return false; return 'permissions' in navigator && typeof ((_a = navigator.permissions) === null || _a === void 0 ? void 0 : _a.query) === 'function'; } function isBatterySupported() { if (isSSR()) return false; const nav = navigator; return typeof nav.getBattery === 'function'; } function getBattery() { return __awaiter(this, void 0, void 0, function* () { if (!isBatterySupported()) return null; try { const nav = navigator; return yield nav.getBattery(); } catch (_a) { return null; } }); } function checkPermission(name) { return __awaiter(this, void 0, void 0, function* () { if (!isPermissionsSupported()) return null; try { const result = yield navigator.permissions.query({ name }); return result.state; } catch (_a) { return null; } }); } function createTimeout(ms, signal) { return new Promise((_, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Operation timed out after ${ms}ms`)); }, ms); signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', () => { clearTimeout(timeoutId); reject(new Error('Operation was aborted')); }); }); } function withTimeout(promise, ms, signal) { return Promise.race([promise, createTimeout(ms, signal)]); } function throttle(fn, limit) { let inThrottle; return (...args) => { if (!inThrottle) { fn(...args); inThrottle = true; setTimeout(() => { inThrottle = false; }, limit); } }; } function isLowPowerMode() { if (isSSR()) return false; try { if ('connection' in navigator) { const connection = navigator.connection; if (connection === null || connection === void 0 ? void 0 : connection.saveData) return true; } if ('deviceMemory' in navigator) { const memory = navigator.deviceMemory; if (memory && memory <= 2) return true; } if (navigator.hardwareConcurrency <= 2) return true; return false; } catch (_a) { return false; } } function generateUniqueId() { return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; } function isDocumentHidden() { if (isSSR()) return false; return document.visibilityState === 'hidden'; } function getPerformanceNow() { if (isSSR()) return Date.now(); return typeof performance !== 'undefined' && performance.now ? performance.now() : Date.now(); } function getMemoryUsage() { var _a; if (isSSR()) return 0; try { if ('memory' in performance) { const memory = performance.memory; return (_a = memory === null || memory === void 0 ? void 0 : memory.usedJSHeapSize) !== null && _a !== void 0 ? _a : 0; } return 0; } catch (_b) { return 0; } } class PermissionManager { constructor() { this.permissionCache = new Map(); this.cacheExpiryTime = 5 * 60 * 1000; this.cacheTimestamps = new Map(); } checkWakeLockPermission(type_1) { return __awaiter(this, arguments, void 0, function* (type, passive = false) { if (isSSR()) return null; const cacheKey = `wake-lock-${type}`; const cached = this.getCachedPermission(cacheKey); if (cached) return cached; try { const wakeLockPermission = yield this.checkSpecificPermission('wake-lock'); if (wakeLockPermission) { this.setCachedPermission(cacheKey, wakeLockPermission); return wakeLockPermission; } if (type === 'screen') { const screenPermission = yield this.checkSpecificPermission('screen-wake-lock'); if (screenPermission) { this.setCachedPermission(cacheKey, screenPermission); return screenPermission; } } if (passive) { this.setCachedPermission(cacheKey, 'granted'); return 'granted'; } return null; } catch (error) { if (passive) { return null; } throw new WakeLockError(`Failed to check ${type} wake lock permission`, WakeLockErrorCode.PERMISSION_DENIED, 'permission-manager', error instanceof Error ? error : undefined); } }); } requestWakeLockPermission(type) { return __awaiter(this, void 0, void 0, function* () { if (isSSR()) { throw new WakeLockError('Wake lock not supported in server-side rendering', WakeLockErrorCode.NOT_SUPPORTED, 'permission-manager'); } const currentPermission = yield this.checkWakeLockPermission(type, true); if (currentPermission === 'granted') { return 'granted'; } try { if ('wakeLock' in navigator && navigator.wakeLock) { const wakeLock = yield navigator.wakeLock.request(type); yield wakeLock.release(); const cacheKey = `wake-lock-${type}`; this.setCachedPermission(cacheKey, 'granted'); return 'granted'; } } catch (error) { if (error instanceof Error) { if (error.name === 'NotAllowedError') { const cacheKey = `wake-lock-${type}`; this.setCachedPermission(cacheKey, 'denied'); return 'denied'; } throw new WakeLockError(`Failed to request ${type} wake lock permission: ${error.message}`, WakeLockErrorCode.PERMISSION_DENIED, 'permission-manager', error); } } throw new WakeLockError(`Wake lock not supported for type: ${type}`, WakeLockErrorCode.NOT_SUPPORTED, 'permission-manager'); }); } canRequestWithoutPrompt(type) { return __awaiter(this, void 0, void 0, function* () { if (isSSR()) return false; try { const permission = yield this.checkWakeLockPermission(type, true); if (permission === 'granted' || permission === 'denied') { return true; } return false; } catch (_a) { return false; } }); } isPassiveModeRecommended() { if (isSSR()) return true; try { if (window !== window.top) return true; if (document.visibilityState === 'hidden') return true; if (!this.hasRecentUserInteraction()) return true; if (this.isMobileDevice()) return true; return false; } catch (_a) { return true; } } clearPermissionCache() { this.permissionCache.clear(); this.cacheTimestamps.clear(); } checkSpecificPermission(name) { return __awaiter(this, void 0, void 0, function* () { if (!isPermissionsSupported()) return null; try { return yield checkPermission(name); } catch (_a) { return null; } }); } getCachedPermission(key) { var _a; const timestamp = this.cacheTimestamps.get(key); if (!timestamp || Date.now() - timestamp > this.cacheExpiryTime) { this.permissionCache.delete(key); this.cacheTimestamps.delete(key); return null; } return (_a = this.permissionCache.get(key)) !== null && _a !== void 0 ? _a : null; } setCachedPermission(key, permission) { this.permissionCache.set(key, permission); this.cacheTimestamps.set(key, Date.now()); } hasRecentUserInteraction() { return document.hasFocus() && Date.now() - this.getLastUserActivity() < 30000; } getLastUserActivity() { return Date.now() - 1000; } isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } } class PerformanceMonitor extends EventEmitter { constructor(options = {}) { var _a, _b, _c, _d; super(); this.monitoringTimer = null; this.battery = null; this.lastMetrics = null; this.isMonitoring = false; this.throttledEmitPerformance = throttle((metrics) => this.emit('performance', metrics), 1000); this.enabled = (_a = options.enabled) !== null && _a !== void 0 ? _a : true; this.monitoringInterval = (_b = options.monitoringInterval) !== null && _b !== void 0 ? _b : 5000; this.batteryThreshold = (_c = options.batteryThreshold) !== null && _c !== void 0 ? _c : 0.2; this.performanceThreshold = (_d = options.performanceThreshold) !== null && _d !== void 0 ? _d : 80; } start() { return __awaiter(this, void 0, void 0, function* () { if (!this.enabled || this.isMonitoring || isSSR()) return; this.isMonitoring = true; try { yield this.initializeBatteryMonitoring(); this.startPerformanceMonitoring(); const initialMetrics = yield this.collectMetrics(); this.lastMetrics = initialMetrics; this.throttledEmitPerformance(initialMetrics); } catch (error) { console.warn('Failed to start performance monitoring:', error); this.isMonitoring = false; } }); } stop() { if (!this.isMonitoring) return; this.isMonitoring = false; if (this.monitoringTimer) { clearInterval(this.monitoringTimer); this.monitoringTimer = null; } if (this.battery) { this.battery.onlevelchange = null; this.battery.onchargingchange = null; } } getCurrentMetrics() { return __awaiter(this, void 0, void 0, function* () { return yield this.collectMetrics(); }); } getLastMetrics() { return this.lastMetrics; } isLowBattery() { if (!this.battery) return false; return this.battery.level <= this.batteryThreshold; } isHighCpuUsage() { if (!this.lastMetrics) return false; return this.lastMetrics.cpuUsage >= this.performanceThreshold; } shouldOptimizePerformance() { return this.isLowBattery() || this.isHighCpuUsage() || isLowPowerMode(); } initializeBatteryMonitoring() { return __awaiter(this, void 0, void 0, function* () { try { this.battery = yield getBattery(); if (!this.battery) return; this.battery.onlevelchange = () => { if (this.battery) { this.emit('battery-change', { level: this.battery.level, charging: this.battery.charging, }); if (this.isLowBattery()) { this.handleLowBattery(); } } }; this.battery.onchargingchange = () => { if (this.battery) { this.emit('battery-change', { level: this.battery.level, charging: this.battery.charging, }); } }; this.emit('battery-change', { level: this.battery.level, charging: this.battery.charging, }); } catch (error) { console.warn('Failed to initialize battery monitoring:', error); } }); } startPerformanceMonitoring() { this.monitoringTimer = setInterval(() => __awaiter(this, void 0, void 0, function* () { if (!this.isMonitoring) return; try { const metrics = yield this.collectMetrics(); this.lastMetrics = metrics; this.throttledEmitPerformance(metrics); if (this.shouldOptimizePerformance()) { this.handlePerformanceOptimization(); } } catch (error) { console.warn('Error collecting performance metrics:', error); } }), this.monitoringInterval); } collectMetrics() { return __awaiter(this, void 0, void 0, function* () { const timestamp = getPerformanceNow(); const cpuUsage = yield this.estimateCpuUsage(); const memoryUsage = getMemoryUsage(); const batteryDrain = this.estimateBatteryDrain(); return { cpuUsage, memoryUsage, batteryDrain, timestamp, }; }); } estimateCpuUsage() { return __awaiter(this, void 0, void 0, function* () { if (isSSR()) return 0; try { const start = getPerformanceNow(); const iterations = 100000; let result = 0; for (let i = 0; i < iterations; i++) { result += Math.random() * Math.sin(i); } const end = getPerformanceNow(); const duration = end - start; const normalizedUsage = Math.min(100, Math.max(0, (duration / 10) * 100)); globalThis.__performanceTestResult = result; return normalizedUsage; } catch (_a) { return 0; } }); } estimateBatteryDrain() { if (!this.battery || !this.lastMetrics) return 0; try { const timeDiff = Date.now() - this.lastMetrics.timestamp; if (timeDiff === 0) return 0; const baselineDrain = 0.01; const wakeLockMultiplier = 1.5; return baselineDrain * wakeLockMultiplier; } catch (_a) { return 0; } } handleLowBattery() { console.warn('Low battery detected, consider optimizing wake lock usage'); } handlePerformanceOptimization() { console.info('Performance optimization recommended'); if (this.lastMetrics) { this.throttledEmitPerformance(this.lastMetrics); } } getOptimizationRecommendations() { const recommendations = []; if (this.isLowBattery()) { recommendations.push('Consider releasing wake lock due to low battery'); } if (this.isHighCpuUsage()) { recommendations.push('High CPU usage detected, consider using less aggressive wake lock strategy'); } if (isLowPowerMode()) { recommendations.push('Device is in low power mode, consider passive wake lock mode'); } if (this.lastMetrics && this.lastMetrics.memoryUsage > 50 * 1024 * 1024) { recommendations.push('High memory usage detected, consider optimizing wake lock strategy'); } return recommendations; } measureStrategy(strategyName, operation) { return __awaiter(this, void 0, void 0, function* () { const start = getPerformanceNow(); const cpuBefore = yield this.estimateCpuUsage(); const memoryBefore = getMemoryUsage(); yield operation(); const end = getPerformanceNow(); const cpuAfter = yield this.estimateCpuUsage(); const memoryAfter = getMemoryUsage(); const measurement = { duration: end - start, cpuBefore, cpuAfter, memoryBefore, memoryAfter, }; console.debug(`Strategy ${strategyName} performance:`, measurement); return measurement; }); } } class ScreenWakeLockStrategy { constructor() { this.name = 'screen-wake-lock'; this.priority = 1; this.activeSentinels = new Set(); } isSupported() { return !isSSR() && isWakeLockSupported(); } request(type_1) { return __awaiter(this, arguments, void 0, function* (type, options = {}) { var _a; if (!this.isSupported()) { throw new WakeLockError('Screen Wake Lock API is not supported', WakeLockErrorCode.NOT_SUPPORTED, this.name); } if (type === 'system') { throw new WakeLockError('System wake locks are not yet supported by the Screen Wake Lock API', WakeLockErrorCode.NOT_SUPPORTED, this.name); } try { const requestPromise = navigator.wakeLock.request(type); const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 10000; const nativeSentinel = timeout > 0 ? yield withTimeout(requestPromise, timeout, options.signal) : yield requestPromise; const wrappedSentinel = this.createWrappedSentinel(nativeSentinel); this.activeSentinels.add(wrappedSentinel); return wrappedSentinel; } catch (error) { if (error instanceof Error) { let errorCode = WakeLockErrorCode.UNKNOWN; switch (error.name) { case 'NotAllowedError': errorCode = WakeLockErrorCode.PERMISSION_DENIED; break; case 'NotSupportedError': errorCode = WakeLockErrorCode.NOT_SUPPORTED; break; case 'AbortError': errorCode = WakeLockErrorCode.TIMEOUT; break; default: if (error.message.includes('timeout')) { errorCode = WakeLockErrorCode.TIMEOUT; } } throw new WakeLockError(`Screen Wake Lock request failed: ${error.message}`, errorCode, this.name, error); } throw new WakeLockError('Unknown error occurred while requesting screen wake lock', WakeLockErrorCode.UNKNOWN, this.name); } }); } release() { return __awaiter(this, void 0, void 0, function* () { const releasePromises = Array.from(this.activeSentinels).map(sentinel => { return sentinel.release().catch(error => { console.warn('Failed to release wake lock sentinel:', error); }); }); yield Promise.allSettled(releasePromises); this.activeSentinels.clear(); }); } getActiveSentinels() { return this.activeSentinels; } createWrappedSentinel(nativeSentinel) { const wrappedSentinel = { type: nativeSentinel.type, get released() { return this._released || nativeSentinel.released; }, _strategy: this, _releaseListeners: new Set(), _released: false, release() { return __awaiter(this, void 0, void 0, function* () { if (this._released) return; try { yield nativeSentinel.release(); } finally { this._released = true; this._strategy.activeSentinels.delete(this); for (const listener of this._releaseListeners) { try { listener(); } catch (error) { console.error('Error in release listener:', error); } } this._releaseListeners.clear(); } }); }, addEventListener(type, listener) { if (type === 'release') { this._releaseListeners.add(listener); nativeSentinel.addEventListener('release', listener); } }, removeEventListener(type, listener) { if (type === 'release') { this._releaseListeners.delete(listener); nativeSentinel.removeEventListener('release', listener); } }, }; nativeSentinel.addEventListener('release', () => { if (!wrappedSentinel._released) { wrappedSentinel._released = true; this.activeSentinels.delete(wrappedSentinel); } }); return wrappedSentinel; } } class VideoElementStrategy { constructor() { this.name = 'video-element'; this.priority = 2; this.activeElements = new Map(); this.mediaSource = null; } isSupported() { if (isSSR()) return false; try { const video = document.createElement('video'); const canPlayType = video.canPlayType('video/mp4'); const hasMediaSource = 'MediaSource' in window; return canPlayType !== '' && hasMediaSource; } catch (_a) { return false; } } request(type_1) { return __awaiter(this, arguments, void 0, function* (type, options = {}) { var _a; if (!this.isSupported()) { throw new WakeLockError('Video element strategy is not supported', WakeLockErrorCode.NOT_SUPPORTED, this.name); } if (type === 'system') { throw new WakeLockError('System wake locks are not supported by video element strategy', WakeLockErrorCode.NOT_SUPPORTED, this.name); } try { const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 15000; const setupPromise = this.setupVideoElement(); const { video, sentinelId } = timeout > 0 ? yield withTimeout(setupPromise, timeout, options.signal) : yield setupPromise; const sentinel = this.createSentinel(sentinelId, type); this.activeElements.set(sentinelId, { video, sentinel }); return sentinel; } catch (error) { throw new WakeLockError(`Video element wake lock failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error && error.message.includes('timeout') ? WakeLockErrorCode.TIMEOUT : WakeLockErrorCode.STRATEGY_FAILED, this.name, error instanceof Error ? error : undefined); } }); } release() { return __awaiter(this, void 0, void 0, function* () { const releasePromises = Array.from(this.activeElements.entries()).map(([id, { video, sentinel }]) => { return this.releaseSentinel(id, video, sentinel).catch(error => { console.warn(`Failed to release video element ${id}:`, error); }); }); yield Promise.allSettled(releasePromises); this.activeElements.clear(); }); } setupVideoElement() { return __awaiter(this, void 0, void 0, function* () { const video = document.createElement('video'); const sentinelId = generateUniqueId(); video.style.position = 'fixed'; video.style.top = '-1000px'; video.style.left = '-1000px'; video.style.width = '1px'; video.style.height = '1px'; video.style.opacity = '0.01'; video.style.pointerEvents = 'none'; video.style.zIndex = '-1000'; video.muted = true; video.loop = true; video.playsInline = true; video.controls = false; video.setAttribute('webkit-playsinline', 'true'); video.setAttribute('playsinline', 'true'); const mediaSource = yield this.createMediaSource(); video.src = URL.createObjectURL(mediaSource); document.body.appendChild(video); yield new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error('Video setup timeout')); }, 10000); const cleanup = () => { clearTimeout(timeoutId); video.removeEventListener('canplay', onCanPlay); video.removeEventListener('error', onError); }; const onCanPlay = () => { cleanup(); video .play() .then(() => resolve()) .catch(reject); }; const onError = () => { cleanup(); reject(new Error('Video failed to load')); }; video.addEventListener('canplay', onCanPlay); video.addEventListener('error', onError); video.load(); }); return { video, sentinelId }; }); } createMediaSource() { return __awaiter(this, void 0, void 0, function* () { if (this.mediaSource && this.mediaSource.readyState === 'open') { return this.mediaSource; } const mediaSource = new MediaSource(); return new Promise((resolve, reject) => { const onSourceOpen = () => { try { const sourceBuffer = mediaSource.addSourceBuffer('video/mp4; codecs="avc1.42E01E"'); const videoData = this.createMinimalVideoData(); sourceBuffer.appendBuffer(videoData); sourceBuffer.addEventListener('updateend', () => { try { mediaSource.endOfStream(); resolve(mediaSource); } catch (error) { reject(error); } }); } catch (error) { reject(error); } }; mediaSource.addEventListener('sourceopen', onSourceOpen); }); }); } createMinimalVideoData() { const mp4Data = new Uint8Array([ 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d, 0x00, 0x00, 0x02, 0x00, 0x69, 0x73, 0x6f, 0x6d, 0x69, 0x73, 0x6f, 0x32, 0x61, 0x76, 0x63, 0x31, 0x6d, 0x70, 0x34, 0x31, ]); return mp4Data.buffer; } createSentinel(sentinelId, type) { const sentinel = { type, get released() { return this._released; }, _strategy: this, _releaseListeners: new Set(), _released: false, release() { return __awaiter(this, void 0, void 0, function* () { if (this._released) return; const element = this._strategy.activeElements.get(sentinelId); if (element) { yield this._strategy.releaseSentinel(sentinelId, element.video, this); } }); }, addEventListener(type, listener) { if (type === 'release') { this._releaseListeners.add(listener); } }, removeEventListener(type, listener) { if (type === 'release') { this._releaseListeners.delete(listener); } }, }; return sentinel; } releaseSentinel(sentinelId, video, sentinel) { return __awaiter(this, void 0, void 0, function* () { if (sentinel._released) return; try { video.pause(); video.src = ''; video.load(); if (video.parentNode) { video.parentNode.removeChild(video); } if (video.src.startsWith('blob:')) { URL.revokeObjectURL(video.src); } } catch (error) { console.warn('Error cleaning up video element:', error); } finally { sentinel._released = true; this.activeElements.delete(sentinelId); for (const listener of sentinel._releaseListeners) { try { listener(); } catch (error) { console.error('Error in release listener:', error); } } sentinel._releaseListeners.clear(); } }); } } class AudioContextStrategy { constructor() { this.name = 'audio-context'; this.priority = 3; this.activeContexts = new Map(); } isSupported() { if (isSSR()) return false; try { const AudioContextClass = window.AudioContext || window.webkitAudioContext; return typeof AudioContextClass === 'function'; } catch (_a) { return false; } } request(type_1) { return __awaiter(this, arguments, void 0, function* (type, options = {}) { var _a; if (!this.isSupported()) { throw new WakeLockError('Audio context strategy is not supported', WakeLockErrorCode.NOT_SUPPORTED, this.name); } if (type === 'system') { throw new WakeLockError('System wake locks are not supported by audio context strategy', WakeLockErrorCode.NOT_SUPPORTED, this.name); } try { const timeout = (_a = options.timeout) !== null && _a !== void 0 ? _a : 10000; const setupPromise = this.setupAudioContext(); const { context, oscillator, gainNode, sentinelId } = timeout > 0 ? yield withTimeout(setupPromise, timeout, options.signal) : yield setupPromise; const sentinel = this.createSentinel(sentinelId, type); this.activeContexts.set(sentinelId, { context, oscillator, gainNode, sentinel }); return sentinel; } catch (error) { throw new WakeLockError(`Audio context wake lock failed: ${error instanceof Error ? error.message : 'Unknown error'}`, error instanceof Error && error.message.includes('timeout') ? WakeLockErrorCode.TIMEOUT : WakeLockErrorCode.STRATEGY_FAILED, this.name, error instanceof Error ? error : undefined); } }); } release() { return __awaiter(this, void 0, void 0, function* () { const releasePromises = Array.from(this.activeContexts.entries()).map(([id, audio]) => { return this.releaseSentinel(id, audio).catch(error => { console.warn(`Failed to release audio context ${id}:`, error); }); }); yield Promise.allSettled(releasePromises); this.activeContexts.clear(); }); } setupAudioContext() { return __awaiter(this, void 0, void 0, function* () { const AudioContextClass = window.AudioContext || window.webkitAudioContext; const context = new AudioContextClass(); const sentinelId = generateUniqueId(); try { if (context.state === 'suspended') { yield context.resume(); } const oscillator = context.createOscillator(); const gainNode = context.createGain(); oscillator.frequency.setValueAtTime(20000, context.currentTime); gainNode.gain.setValueAtTime(0.0001, context.currentTime); oscillator.connect(gainNode); gainNode.connect(context.destination); oscillator.start(); yield new Promise((resolve, reject) => { const startTime = Date.now(); const checkRunning = () => { if (context.state === 'running') { resolve(); } else if (Date.now() - startTime > 5000) { reject(new Error('Audio context failed to start')); } else { setTimeout(checkRunning, 100); } }; checkRunning(); }); return { context, oscillator, gainNode, sentinelId }; } catch (error) { try { yield context.close(); } catch (_a) { } throw error; } }); } createSentinel(sentinelId, type) { const sentinel = { type, get released() { return this._released; }, _strategy: this, _releaseListeners: new Set(), _released: false, release() { return __awaiter(this, void 0, void 0, function* () { if (this._released) return; const audio = this._strategy.activeContexts.get(sentinelId); if (audio) { yield this._strategy.releaseSentinel(sentinelId, audio); } }); }, addEventListener(type, listener) { if (type === 'release') { this._releaseListeners.add(listener); } }, removeEventListener(type, listener) { if (type === 'release') { this._releaseListeners.delete(listener); } }, }; return sentinel; } releaseSentinel(sentinelId, audio) { return __awaiter(this, void 0, void 0, function* () { if (audio.sentinel._released) return; try { if (audio.oscillator) { try { audio.oscillator.stop(); } catch (_a) { } } if (audio.gainNode) { try { audio.gainNode.disconnect(); } catch (_b) { } } if (audio.context && audio.context.state !== 'closed') { yield audio.context.close(); } } catch (error) { console.warn('Error cleaning up audio context:', error); } finally { audio.sentinel._released = true; this.activeContexts.delete(sentinelId); for (const listener of audio.sentinel._releaseListeners) { try { listener(); } catch (error) { console.error('Error in release listener:', error); } } audio.sentinel._releaseListeners.clear(); } }); } getActiveContextsCount() { return this.activeContexts.size; } suspendAll() { return __awaiter(this, void 0, void 0, function* () { const suspendPromises = Array.from(this.activeContexts.values()).map((audio) => __awaiter(this, void 0, void 0, function* () { if (audio.context.state === 'running') { try { yield audio.context.suspend(); } catch (error) { console.warn('Failed to suspend audio context:', error); } } })); yield Promise.allSettled(suspendPromises); }); } resumeAll() { return __awaiter(this, void 0, void 0, function* () { const resumePromises = Array.from(this.activeContexts.values()).map((audio) => __awaiter(this, void 0, void 0, function* () { if (audio.context.state === 'suspended') { try { yield audio.context.resume(); } catch (error) { console.warn('Failed to resume audio context:', error); } } })); yield Promise.allSettled(resumePromises); }); } } class TimerStrategy { constructor() { this.name = 'timer'; this.priority = 4; this.activeTimers = new Map(); this.DEFAULT_INTERVAL = 1000; this.AGGRESSIVE_INTERVAL = 500; this.WORKER_SCRIPT = ` let intervalId; self.onmessage = function(e) { if (e.data.action === 'start') { intervalId = setInterval(() => { self.postMessage({ type: 'tick', timestamp: Date.now() }); }, e.data.interval || 1000); } else if (e.data.action === 'stop') { if (intervalId) { clearInterval(intervalId); intervalId = null; } self.close(); } }; `; } isSupported() { return !isSSR(); } request(type_1) { return __awaiter(this, arguments, void 0, function* (type, _options = {}) { if (!this.isSupported()) { throw new WakeLockError('Timer strategy is not supported in SSR environment', WakeLockErrorCode.NOT_SUPPORTED, this.name); } const sentinelId = generateUniqueId(); const sentinel = this.createSentinel(sentinelId, type); try { const useWorker = this.shouldUseWorker(); const interval = this.determineInterval(); if (useWorker) { yield this.setupWorkerTimer(sentinelId, interval, sentinel); } else { this.setupMainThreadTimer(sentinelId, interval, sentinel); } this.setupVisibilityHandling(sentinelId); return sentinel; } catch (error) { throw new WakeLockError(`Timer strategy failed to start: ${error instanceof Error ? error.message : 'Unknown error'}`, WakeLockErrorCode.STRATEGY_FAILED, this.name, error instanceof Error ? error : undefined); } }); } release() { return __awaiter(this, void 0, void 0, function* () { const releasePromises = Array.from(this.activeTimers.entries()).map(([id, timer]) => { return this.releaseSentinel(id, timer).catch(error => { console.warn(`Failed to release timer ${id}:`, error); }); }); yield Promise.allSettled(releasePromises); this.activeTimers.clear(); }); } shouldUseWorker() { try { return (typeof Worker !== 'undefined' && typeof window !== 'undefined' && typeof window.importScripts === 'undefined'); } catch (_a) { return false; } } determineInterval() { const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); return isMobile ? this.AGGRESSIVE_INTERVAL : this.DEFAULT_INTERVAL; } setupWorkerTimer(sentinelId, interval, sentinel) { return __awaiter(this, void 0, void 0, function* () { try { const blob = new Blob([this.WORKER_SCRIPT], { type: 'application/javascript' }); const workerScript = URL.createObjectURL(blob); const worker = new Worker(workerScript); worker.onmessage = (e) => { if (e.data.type === 'tick') { const now = Date.now(); globalThis.__wakeLockTimestamp = now; } }; worker.onerror = (error) => { console.warn('Wake lock worker error:', error); this.setupMainThreadTimer(sentinelId, interval, sentinel); }; worker.postMessage({ action: 'start', interval }); const timerInfo = this.activeTimers.get(sentinelId); if (timerInfo) { timerInfo.worker = worker; timerInfo.workerScript = workerScript; } } catch (error) { console.warn('Failed to setup worker timer, falling back to main thread:', error); this.setupMainThreadTimer(sentinelId, interval, sentinel); } }); } setupMainThreadTimer(sentinelId, interval, sentinel) { const intervalId = setInterval(() => { if (sentinel._released) { clearInterval(intervalId); return; } const now = Date.now(); if (!isDocumentHidden()) { try { const el = document.createElement('div'); el.style.position = 'absolute'; el.style.top = '-1px'; el.style.left = '-1px'; el.style.width = '1px'; el.style.height = '1px'; el.style.opacity = '0'; document.body.appendChild(el); document.body.removeChild(el); } catch (_a) { } } globalThis.__wakeLockTimestamp = now; }, interval); const timerInfo = this.activeTimers.get(sentinelId); if (timerInfo) { timerInfo.intervalId = intervalId; } } setupVisibilityHandling(sentinelId) { const handleVisibilityChange = () => { const timer = this.activeTimers.get(sentinelId); if (!timer || timer.sentinel._released) return; if (isDocumentHidden()) { this.adjustTimerAggression(sentinelId, true); } else { this.adjustTi