UNPKG

tiny-electron-essentials

Version:

A lightweight and modular utility library for Electron apps, offering simplified window management, tray support, IPC channels, and custom frameless window styling.

821 lines (820 loc) 32.9 kB
import { BrowserWindow, shell } from 'electron'; import { EventEmitter } from 'events'; import { isJsonObject } from 'tiny-essentials'; import { AppEvents, RootEvents } from '../global/Events.mjs'; import { checkEventsList } from '../global/Utils.mjs'; /** * Represents a single managed Electron BrowserWindow instance. * * This class tracks visibility, readiness, and index of the window. * It allows toggling visibility and storing references to the parent controller and window instance. */ class TinyWinInstance { /** @typedef {function(string | symbol, ...any): void} Emit */ /** @typedef {(win: Electron.BrowserWindow, ops?: Electron.OpenDevToolsOptions) => void} OpenDevTools */ /** @typedef {(win: Electron.BrowserWindow, config: Electron.ProxyConfig) => void} SetProxy */ /** @typedef {(win: Electron.BrowserWindow, page: string|string[], ops?: Electron.LoadFileOptions|Electron.LoadURLOptions) => void} LoadPath */ #AppEvents = AppEvents; /** * Important instance used to make event emitter. * @type {EventEmitter} */ #events = new EventEmitter(); /** * Important instance used to make system event emitter. * @type {EventEmitter} */ #sysEvents = new EventEmitter(); #sysEventsUsed = false; /** * Provides access to a secure internal EventEmitter for subclass use only. * * This method exposes a dedicated EventEmitter instance intended specifically for subclasses * that extend the main class. It prevents subclasses from accidentally or intentionally using * the primary class's public event system (`emit`), which could lead to unpredictable behavior * or interference in the base class's event flow. * * For security and consistency, this method is designed to be accessed only once. * Multiple accesses are blocked to avoid leaks or misuse of the internal event bus. * * @returns {EventEmitter} A special internal EventEmitter instance for subclass use. * @throws {Error} If the method is called more than once. */ getSysEvents() { if (this.#sysEventsUsed) throw new Error('Access denied: getSysEvents() can only be called once. ' + 'This restriction ensures subclass event isolation and prevents accidental interference ' + 'with the main class event emitter.'); this.#sysEventsUsed = true; return this.#sysEvents; } /** * Emits an event with optional arguments to all system emit. * @param {string | symbol} event - The name of the event to emit. * @param {...any} args - Arguments passed to event listeners. */ #emit(event, ...args) { this.#events.emit(event, ...args); if (this.#sysEventsUsed) this.#sysEvents.emit(event, ...args); } /** * @typedef {(...args: any[]) => void} ListenerCallback * A generic callback function used for event listeners. */ /** * Sets the maximum number of listeners for the internal event emitter. * * @param {number} max - The maximum number of listeners allowed. */ setMaxListeners(max) { this.#events.setMaxListeners(max); } /** * Emits an event with optional arguments. * @param {string | symbol} event - The name of the event to emit. * @param {...any} args - Arguments passed to event listeners. * @returns {boolean} `true` if the event had listeners, `false` otherwise. */ emit(event, ...args) { return this.#events.emit(event, ...args); } /** * Registers a listener for the specified event. * @param {string | symbol} event - The name of the event to listen for. * @param {ListenerCallback} listener - The callback function to invoke. * @returns {this} The current class instance (for chaining). */ on(event, listener) { this.#events.on(event, listener); return this; } /** * Registers a one-time listener for the specified event. * @param {string | symbol} event - The name of the event to listen for once. * @param {ListenerCallback} listener - The callback function to invoke. * @returns {this} The current class instance (for chaining). */ once(event, listener) { this.#events.once(event, listener); return this; } /** * Removes a listener from the specified event. * @param {string | symbol} event - The name of the event. * @param {ListenerCallback} listener - The listener to remove. * @returns {this} The current class instance (for chaining). */ off(event, listener) { this.#events.off(event, listener); return this; } /** * Alias for `on`. * @param {string | symbol} event - The name of the event. * @param {ListenerCallback} listener - The callback to register. * @returns {this} The current class instance (for chaining). */ addListener(event, listener) { this.#events.addListener(event, listener); return this; } /** * Alias for `off`. * @param {string | symbol} event - The name of the event. * @param {ListenerCallback} listener - The listener to remove. * @returns {this} The current class instance (for chaining). */ removeListener(event, listener) { this.#events.removeListener(event, listener); return this; } /** * Removes all listeners for a specific event, or all events if no event is specified. * @param {string | symbol} [event] - The name of the event. If omitted, all listeners from all events will be removed. * @returns {this} The current class instance (for chaining). */ removeAllListeners(event) { this.#events.removeAllListeners(event); return this; } /** * Returns the number of times the given `listener` is registered for the specified `event`. * If no `listener` is passed, returns how many listeners are registered for the `event`. * @param {string | symbol} eventName - The name of the event. * @param {Function} [listener] - Optional listener function to count. * @returns {number} Number of matching listeners. */ listenerCount(eventName, listener) { return this.#events.listenerCount(eventName, listener); } /** * Adds a listener function to the **beginning** of the listeners array for the specified event. * The listener is called every time the event is emitted. * @param {string | symbol} eventName - The event name. * @param {ListenerCallback} listener - The callback function. * @returns {this} The current class instance (for chaining). */ prependListener(eventName, listener) { this.#events.prependListener(eventName, listener); return this; } /** * Adds a **one-time** listener function to the **beginning** of the listeners array. * The next time the event is triggered, this listener is removed and then invoked. * @param {string | symbol} eventName - The event name. * @param {ListenerCallback} listener - The callback function. * @returns {this} The current class instance (for chaining). */ prependOnceListener(eventName, listener) { this.#events.prependOnceListener(eventName, listener); return this; } /** * Returns an array of event names for which listeners are currently registered. * @returns {(string | symbol)[]} Array of event names. */ eventNames() { return this.#events.eventNames(); } /** * Gets the current maximum number of listeners allowed for any single event. * @returns {number} The max listener count. */ getMaxListeners() { return this.#events.getMaxListeners(); } /** * Returns a copy of the listeners array for the specified event. * @param {string | symbol} eventName - The event name. * @returns {Function[]} An array of listener functions. */ listeners(eventName) { return this.#events.listeners(eventName); } /** * Returns a copy of the internal listeners array for the specified event, * including wrapper functions like those used by `.once()`. * @param {string | symbol} eventName - The event name. * @returns {Function[]} An array of raw listener functions. */ rawListeners(eventName) { return this.#events.rawListeners(eventName); } #checkDestroy() { if (!this.#win || this.#win.isDestroyed?.()) throw new Error('The BrowserWindow instance "win" is not available or has been destroyed.'); } /** * Checks if a given value exists in the AppEvents values. * * @param {string} value - The value to check for. * @returns {boolean} True if the value exists, false otherwise. */ isValidAppEvent(value) { return Object.keys(this.#AppEvents).includes(value); } /** * Gets the key (event name) associated with a given AppEvents value. * * @param {string} value - The value to look up. * @returns {string} The matching AppEvents key. * @throws {Error} If the value is not found. */ getAppEventKey(value) { if (!this.isValidAppEvent(value)) throw new Error(`AppEvent value "${value}" not found.`); // @ts-ignore if (typeof this.#AppEvents[value] !== 'string') throw new Error(`AppEvent value "${value}" is invalid.`); // @ts-ignore return this.#AppEvents[value]; } #preparingDestroy = false; #visible = false; #ready = false; #isFullScreenable = true; #isMaximizable = true; #isClosable = true; #isFocusable = true; /** @type {string|number|null} */ #index = null; /** @type {BrowserWindow} */ #win; /** * Emits an event with optional arguments. * @type {Emit} */ #primaryEmit; /** * @type {OpenDevTools} */ #openDevTools; /** * @type {SetProxy} */ #setProxy; /** * @type {LoadPath} */ #loadPath; /** * Returns whether the window is currently maximizable. * * @returns {boolean} True if the window can be maximized; otherwise, false. */ isMaximizable() { const result = this.#win.isMaximizable(); this.#setMaximizable(result); return this.#isMaximizable; } /** * Returns whether the window is currently closable. * * @returns {boolean} True if the window can be closed; otherwise, false. */ isClosable() { const result = this.#win.isClosable(); this.#setClosable(result); return this.#isClosable; } /** * Returns whether the window is currently fullscreenable. * * @returns {boolean} True if the window can enter fullscreen; otherwise, false. */ isFullScreenable() { const result = this.#win.isFullScreenable(); this.#setFullScreenable(result); return this.#isFullScreenable; } /** * Returns whether the window is currently focusable. * * @returns {boolean} True if the window can be focused; otherwise, false. */ isFocusable() { const result = this.#win.isFocusable(); this.#setFocusable(result); return this.#isFocusable; } /** * Updates the internal maximizable state. * * @param {boolean} value - True to make the window maximizable, false otherwise. * @throws {TypeError} If the value is not a boolean. */ #setMaximizable(value) { if (typeof value !== 'boolean') throw new TypeError('The "value" argument must be a boolean.'); const oldValue = this.#isMaximizable; if (oldValue === value) return; this.#isMaximizable = value; this.#win.webContents.send(this.#AppEvents.WindowIsMaximizable, { value, time: Date.now(), }); this.#emit(this.#AppEvents.WindowIsMaximizable, value); this.#primaryEmit(this.#AppEvents.WindowIsMaximizable, this.#index, value); } /** * Updates the internal closable state. * * @param {boolean} value - True to make the window closable, false otherwise. * @throws {TypeError} If the value is not a boolean. */ #setClosable(value) { if (typeof value !== 'boolean') throw new TypeError('The "value" argument must be a boolean.'); const oldValue = this.#isClosable; if (oldValue === value) return; this.#isClosable = value; this.#win.webContents.send(this.#AppEvents.WindowIsClosable, { value, time: Date.now(), }); this.#emit(this.#AppEvents.WindowIsClosable, value); this.#primaryEmit(this.#AppEvents.WindowIsClosable, this.#index, value); } /** * Updates the internal fullscreenable state. * * @param {boolean} value - True to allow fullscreen, false otherwise. * @throws {TypeError} If the value is not a boolean. */ #setFullScreenable(value) { if (typeof value !== 'boolean') throw new TypeError('The "value" argument must be a boolean.'); const oldValue = this.#isFullScreenable; if (oldValue === value) return; this.#isFullScreenable = value; this.#win.webContents.send(this.#AppEvents.WindowIsFullScreenable, { value, time: Date.now(), }); this.#emit(this.#AppEvents.WindowIsFullScreenable, value); this.#primaryEmit(this.#AppEvents.WindowIsFullScreenable, this.#index, value); } /** * Updates the internal focusable state. * * @param {boolean} value - True to make the window focusable, false otherwise. * @throws {TypeError} If the value is not a boolean. */ #setFocusable(value) { if (typeof value !== 'boolean') throw new TypeError('The "value" argument must be a boolean.'); const oldValue = this.#isFocusable; if (oldValue === value) return; this.#isFocusable = value; this.#win.webContents.send(this.#AppEvents.WindowIsFocusable, { value, time: Date.now(), }); this.#emit(this.#AppEvents.WindowIsFocusable, value); this.#primaryEmit(this.#AppEvents.WindowIsFocusable, this.#index, value); } /** * Returns the window index assigned to this instance. * @returns {string|number|null} */ getIndex() { return this.#index; } /** * Checks whether the window is currently visible. * @returns {boolean} */ isVisible() { return this.#visible; } /** * Checks whether the window is marked as ready. * @returns {boolean} */ isReady() { return this.#ready; } /** * Loads a page. Depending on configuration, it can load a local file path or a URL. * * @param {string|string[]} page - The page or path segments to load. * @param {Electron.LoadFileOptions|Electron.LoadURLOptions} [ops] - Options passed to `loadFile` or `loadURL`. * @throws {TypeError} If page is not a string or string[]. * @throws {TypeError} If page contains non-string entries. * @throws {TypeError} If ops is not an object. */ loadPath(page, ops) { this.#checkDestroy(); return this.#loadPath(this.#win, page, ops); } /** * Opens the developer tools. * Also sends the custom console warning message to the devtools console. * @param {Electron.OpenDevToolsOptions} [ops] - The target DevTools config. */ openDevTools(ops) { this.#checkDestroy(); return this.#openDevTools(this.#win, ops); } /** * Sets whether the window can be maximized. * Sends an event to the renderer with the updated state. * * @param {boolean} value - If true, the window becomes maximizable; otherwise, it cannot be maximized. * @returns {boolean} - Edit result. */ setMaximizable(value) { this.#checkDestroy(); this.#win.setMaximizable(value); const result = this.#win.isMaximizable(); this.#setMaximizable(result); return result; } /** * Sets whether the window can be closed. * Sends an event to the renderer with the updated state. * * @param {boolean} value - If true, the window can be closed; otherwise, it cannot be closed. * @returns {boolean} - Edit result. */ setClosable(value) { this.#checkDestroy(); this.#win.setClosable(value); const result = this.#win.isClosable(); this.#setClosable(result); return result; } /** * Sets whether the window can be focused. * Sends an event to the renderer with the updated state. * * @param {boolean} value - If true, the window can be focused; otherwise, it cannot receive focus. * @returns {boolean} - Edit result. */ setFocusable(value) { this.#checkDestroy(); this.#win.setFocusable(value); const result = this.#win.isFocusable(); this.#setFocusable(result); return result; } /** * Sets whether the window can enter fullscreen mode. * Sends an event to the renderer with the updated state. * * @param {boolean} value - If true, the window can enter fullscreen mode; otherwise, it cannot receive focus. * @returns {boolean} - Edit result. */ setFullScreenable(value) { this.#checkDestroy(); this.#win.setFullScreenable(value); const result = this.#win.isFullScreenable(); this.#setFullScreenable(result); return result; } /** * Applies a network proxy configuration. * * This method sets the proxy settings for the session. * Upon completion, it emits events back to the renderer process to indicate success or failure. * * @param {Electron.ProxyConfig} config - The proxy configuration object following Electron's ProxyConfig structure. * Example: `{ proxyRules: 'http=myproxy.com:8080;https=myproxy.com:8080', proxyBypassRules: 'localhost' }` * * @throws {Error} Throws an error if the provided window (`win`) is invalid (null, destroyed, or missing webContents). * * @returns {void} */ setProxy(config) { this.#checkDestroy(); return this.#setProxy(this.#win, config); } /** * Returns the internal BrowserWindow instance. * @returns {BrowserWindow} * @throws {Error} If the window is not initialized. */ getWin() { if (!this.#win) throw new Error('[getWindow Error] The BrowserWindow instance is not available. Make sure it was properly created.'); this.#checkDestroy(); return this.#win; } /** * Toggles the visibility of the window, or sets it explicitly if a value is provided. * * Emits the `ShowApp` event to the root instance when visibility changes. * * @param {boolean} [isVisible] - If defined, sets visibility to this value. Otherwise, it toggles. * @returns {boolean} - The new visibility state. * @throws {Error} If `isVisible` is not a boolean or undefined. */ toggleVisible(isVisible) { this.#checkDestroy(); if (typeof isVisible !== 'undefined' && typeof isVisible !== 'boolean') throw new Error(`[toggleVisible Error] Expected a boolean or undefined, but got: ${typeof isVisible}`); if (!this.#ready) return this.#visible; const changeVisibleTo = typeof isVisible === 'boolean' ? isVisible : !this.#visible; if (changeVisibleTo) this.#win?.show(); else this.#win?.hide(); this.#visible = changeVisibleTo; this.#emit(RootEvents.ShowApp, changeVisibleTo); this.#primaryEmit(RootEvents.ShowApp, this.#index, changeVisibleTo); return changeVisibleTo; } /** * Sends a ping event with custom data to the renderer process. * * This method allows the main process to send arbitrary data to the window * via the `Ping` event. It is commonly used for connection checks, * heartbeat signals, or simple data synchronization. * * @param {any} data - Any serializable data to send along with the ping event. * * @throws {Error} Throws an error if the window instance is destroyed or unavailable. * * @returns {void} */ ping(data) { this.#checkDestroy(); if (this.#win.webContents) this.#win.webContents.send(this.#AppEvents.Ping, { value: data, time: Date.now() }); } /** * Checks whether the given IPC event originated from this window instance. * * This is useful when multiple windows exist and you want to ensure an IPC event * came from the correct one before handling it. * * @param {Electron.IpcMainEvent} event - The IPC event object received in the main process. * @returns {boolean} - Returns true if the event originated from this instance's window. */ isFromWin(event) { this.#checkDestroy(); const webContents = event.sender; if (!event.senderFrame) return false; const win = BrowserWindow.fromWebContents(webContents); if (win && win.id === this.#win.id) return true; return false; } /** * @param {Object} [settings2={}] - Configuration for the new instance. * @param {Emit} [settings2.emit] - The root controller or application class managing this instance. * @param {SetProxy} [settings2.setProxy] - SetProxy callback. * @param {OpenDevTools} [settings2.openDevTools] - OpenDevTools callback. * @param {LoadPath} [settings2.loadPath] - Load path callback. * @param {AppEvents} [settings2.eventNames=this.#AppEvents] - Set of event names for internal messaging. * @param {Object} [settings={}] - Configuration for the new BrowserWindow. * @param {Electron.BrowserWindowConstructorOptions} [settings.config] - Configuration for the new BrowserWindow. * @param {string|number} [settings.index] - (Optional) Index of the window in the manager. * @param {boolean} [settings.isMaximized=false] - The window will try to be maximized by booting. * @param {boolean} [settings.openWithBrowser=true] - if you will make all links open with the browser, not with the application. * @param {boolean} [settings.show] - The window will appear when the load is finished. * @param {string[]} [settings.urls=['https:', 'http:']] - List of allowed URL protocols to permit external opening. * @throws {Error} If any parameter is invalid. */ constructor({ eventNames = this.#AppEvents, emit, loadPath, openDevTools, setProxy } = {}, { config, index, show, isMaximized = false, openWithBrowser = true, urls = ['https:', 'http:'], } = {}) { checkEventsList(eventNames, this.#AppEvents); if (typeof emit !== 'function') throw new Error(`[Window Creation Error] 'emit' must be a event emit.`); if (typeof loadPath !== 'function') throw new Error(`[Window Creation Error] 'loadPath' must be a loadPath.`); if (typeof openDevTools !== 'function') throw new Error(`[Window Creation Error] 'openDevTools' must be a openDevTools.`); if (typeof setProxy !== 'function') throw new Error(`[Window Creation Error] 'setProxy' must be a setProxy.`); if (!isJsonObject(config)) throw new Error('[Window Creation Error] Expected "config" to be an object.'); if (typeof isMaximized !== 'boolean') throw new Error('[Window Creation Error] Expected "isMaximized" to be an boolean.'); if (typeof openWithBrowser !== 'boolean') throw new Error('[Window Creation Error] Expected "openWithBrowser" to be an boolean.'); if (typeof show !== 'boolean') throw new Error('[Window Creation Error] Expected "show" to be an boolean.'); if (!Array.isArray(urls)) throw new Error('[Window Creation Error] Expected an url list of strings.'); for (const item of urls) if (typeof item !== 'string') throw new Error('[Window Creation Error] All urls in the array must be strings.'); if (typeof index !== 'undefined' && typeof index !== 'string' && (typeof index !== 'number' || !Number.isFinite(index) || Number.isNaN(index))) throw new Error('[Window Creation Error] Expected "index" to be an string or number.'); this.#win = new BrowserWindow(config); this.#primaryEmit = emit; this.#openDevTools = openDevTools; this.#setProxy = setProxy; this.#loadPath = loadPath; this.#index = typeof index === 'number' || typeof index === 'string' ? index : null; this.#visible = show; // Make all links open with the browser, not with the application if (openWithBrowser) this.#win.webContents.setWindowOpenHandler(({ url }) => { let allowed = false; for (const name of urls) { if (url.startsWith(name)) { allowed = true; break; } } if (allowed) shell.openExternal(url); return { action: 'deny' }; }); // Show Page this.#win.once('ready-to-show', (...args) => { this.#ready = true; this.toggleVisible(show); if (isMaximized && show) this.#win.maximize(); this.#emit(RootEvents.ReadyToShow, ...args); this.#primaryEmit(RootEvents.ReadyToShow, this.#index, ...args); if (this.#win) this.#win.webContents.send(this.#AppEvents.ReadyToShow, { time: Date.now(), }); }); /** * Resize * @param {string[]} events */ const resizeWindowEvent = (events) => { if (this.#win) { for (const eventName of events) this.#win.webContents.send(eventName, { value: this.#win.getSize(), time: Date.now(), }); } }; this.#win.on('resize', () => resizeWindowEvent([this.#AppEvents.Resize])); this.#win.on('resized', () => resizeWindowEvent([this.#AppEvents.Resized])); this.#win.on('will-resize', () => resizeWindowEvent([this.#AppEvents.WillResize])); this.#win.on('maximize', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('unmaximize', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('minimize', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('restore', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('enter-full-screen', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('leave-full-screen', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('enter-html-full-screen', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); this.#win.on('leave-html-full-screen', () => resizeWindowEvent([ this.#AppEvents.WillResize, this.#AppEvents.Resize, this.#AppEvents.Resized, ])); const fullscreenEvent = () => { if (this.#win) this.#win.webContents.send(this.#AppEvents.WindowIsFullScreen, { value: this.#win.isFullScreen(), time: Date.now(), }); }; this.#win.on('enter-full-screen', fullscreenEvent); this.#win.on('leave-full-screen', fullscreenEvent); this.#win.on('enter-html-full-screen', fullscreenEvent); this.#win.on('leave-html-full-screen', fullscreenEvent); // More this.#win.on('focus', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsFocused, { value: true, time: Date.now(), }); }); this.#win.on('blur', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsFocused, { value: false, time: Date.now(), }); }); this.#win.on('show', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsVisible, { value: true, time: Date.now(), }); }); this.#win.on('hide', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsVisible, { value: false, time: Date.now(), }); }); this.#win.on('maximize', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsMaximized, { value: true, time: Date.now(), }); }); this.#win.on('unmaximize', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsMaximized, { value: false, time: Date.now(), }); }); this.#win.on('will-resize', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsMaximized, { value: this.#win.isMaximized(), time: Date.now(), }); }); this.#win.on('resize', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsMaximized, { value: this.#win.isMaximized(), time: Date.now(), }); }); this.#win.on('resized', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowIsMaximized, { value: this.#win.isMaximized(), time: Date.now(), }); }); this.#win.on('move', () => { if (this.#win && this.#win.webContents) this.#win.webContents.send(this.#AppEvents.WindowMove, { value: this.#win.getPosition(), time: Date.now(), }); }); } /** * Checks whether the internal BrowserWindow instance has been destroyed. * * This method safely verifies if the window no longer exists or has already been destroyed. * It handles edge cases where the window may be `null` or the `isDestroyed` method is unavailable. * * @returns {boolean} `true` if the window is destroyed or unavailable, otherwise `false`. */ isDestroyed() { if (!this.#win || this.#win.isDestroyed()) return true; return false; } /** * Checks whether the internal BrowserWindow instance is preparing to be destroyed. * * @returns {boolean} `true` if the window is being destroyed. */ isPreparingDestroy() { return this.#preparingDestroy; } /** * Destroys the current BrowserWindow instance. * * @returns {void} */ destroy() { this.#preparingDestroy = true; this.#events.removeAllListeners(); this.#sysEvents.removeAllListeners(); if (!this.isDestroyed()) this.#win.destroy(); } } export default TinyWinInstance;