UNPKG

no-sleep-app

Version:

NoSleepApp is a lightweight JavaScript library that prevents devices from going to sleep during critical activities. It uses the Screen Wake Lock API or a fallback video playback method to keep the screen active, ensuring uninterrupted user experiences ac

155 lines (143 loc) 5.31 kB
/** * @fileoverview NoSleepApp is a utility for preventing devices from sleeping * by leveraging different strategies depending on platform capabilities. */ import VisibilityListener from './visibility-listener.js'; import NoSleepElement from './no-sleep-element.js'; import { isOldIOS } from './detect.js'; /** * Determines if the native Wake Lock API is supported. * @returns {boolean} True if Wake Lock API is supported. */ const isNativeWakeLockSupported = () => "wakeLock" in navigator; /** * NoSleepApp prevents devices from entering sleep mode. * It uses the Wake Lock API, older iOS hacks, or video playback depending on device support. */ export default class NoSleepApp { /** * @param {Object} options Configuration options for NoSleepApp. * @param {'wakeLock'|'video'|'legacy'} [options.strategy='wakeLock'] The strategy to use for preventing sleep. * @param {number} [options.retryInterval=10000] Interval to retry enabling Wake Lock if it fails (in milliseconds). * @param {boolean} [options.fallbackEnabled=true] Whether to enable fallback mechanisms if Wake Lock is not available. */ constructor(options = {}) { const defaultOptions = { strategy: 'wakeLock', // Default strategy is wakeLock retryInterval: 10000, // Default retry interval is 10 seconds fallbackEnabled: true, // Default fallback is enabled }; this.options = { ...defaultOptions, ...options }; /** * @type {boolean} Indicates whether NoSleepApp is currently enabled. */ this.enabled = false; if (isNativeWakeLockSupported() && this.options.strategy === 'wakeLock') { /** @type {WakeLockSentinel|null} The active Wake Lock instance. */ this._wakeLock = null; /** @type {VisibilityListener|null} Listener for visibility changes to re-enable Wake Lock. */ this.visibilityListener = new VisibilityListener(this.enable.bind(this)); } else if (isOldIOS() && this.options.strategy === 'legacy') { /** @type {number|null} Timer ID for the iOS hack to prevent sleep. */ this.noSleepTimer = null; } else { /** @type {NoSleepElement} Video element for non-iOS and non-Wake Lock devices. */ this.noSleepElement = new NoSleepElement(); this.noSleepElement.setMetadataListener(); } } /** * Checks if NoSleepApp is enabled. * @returns {boolean} True if NoSleepApp is enabled. */ get isEnabled() { return this.enabled; } /** * Enables the NoSleepApp functionality. * Uses the Wake Lock API, older iOS hack, or video playback as appropriate. * @returns {Promise<void>} Resolves when enabling is complete. * @throws {Error} Throws an error if enabling fails. */ async enable() { if (this.options.strategy === 'wakeLock' && isNativeWakeLockSupported()) { try { this._wakeLock = await navigator.wakeLock.request("screen"); this.enabled = true; console.log("Wake Lock active."); this._wakeLock.addEventListener("release", this._onWakeLockRelease); } catch (err) { this.enabled = false; console.error(`${err.name}, ${err.message}`); throw err; } } else if (this.options.strategy === 'legacy' && isOldIOS()) { this._enableOldIOS(); } else if (this.options.strategy === 'video') { await this._enableVideoPlayback(); } } /** * Handles Wake Lock release events. * @private */ _onWakeLockRelease() { console.log("Wake Lock released."); } /** * Enables the older iOS-specific hack to prevent sleep. * This hack repeatedly refreshes the page. * @private */ _enableOldIOS() { this.disable(); console.warn("NoSleep enabled for older iOS devices. This can interrupt active network requests."); this.noSleepTimer = setInterval(() => { if (!document.hidden) { window.location.href = window.location.href.split("#")[0]; setTimeout(window.stop, 0); } }, 15000); this.enabled = true; } /** * Enables video playback to prevent sleep on devices without Wake Lock or old iOS. * @private * @returns {Promise<void>} Resolves when video playback is successfully started. * @throws {Error} Throws an error if video playback fails. */ async _enableVideoPlayback() { try { await this.noSleepElement.play(); this.enabled = true; } catch (err) { this.enabled = false; console.error("Failed to start video playback:", err); throw err; } } /** * Disables NoSleepApp functionality. * Stops Wake Lock, iOS hack, or video playback depending on the platform. */ disable() { if (this.options.strategy === 'wakeLock' && isNativeWakeLockSupported()) { if (this._wakeLock) { this._wakeLock.release(); } this._wakeLock = null; if (this.visibilityListener) { this.visibilityListener.removeListeners(); } } else if (this.options.strategy === 'legacy' && isOldIOS()) { if (this.noSleepTimer) { console.warn("NoSleep disabled for older iOS devices."); clearInterval(this.noSleepTimer); this.noSleepTimer = null; } } else if (this.options.strategy === 'video') { this.noSleepElement.pause(); } this.enabled = false; } }