diff --git a/src/lib/browser.ts b/src/lib/browser.ts deleted file mode 100644 index 8393887..0000000 --- a/src/lib/browser.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Checks if the current environment is a browser. - * - * This function verifies whether the code is being executed in a browser environment. - * It checks for the presence of the `window` object, which is unique to browsers. - * - * @returns {boolean} - Returns `true` if the current environment is a browser, otherwise `false`. - */ -export const isBrowser = () => { - return typeof window !== 'undefined' && typeof window.document !== 'undefined'; -}; diff --git a/src/lib/extension-load-queue.ts b/src/lib/extension-load-queue.ts index f0d89cd..0a4fe24 100644 --- a/src/lib/extension-load-queue.ts +++ b/src/lib/extension-load-queue.ts @@ -1,149 +1,63 @@ -/** - * This file provides utilities for managing and processing load queues in a browser environment. - * - * It includes: - * - A system for creating and managing load queues identified by unique symbols. - * - A script store that holds callbacks associated with controllers. - * - A React hook for using and managing controllers within a script store. - * - * Example usage: - * - * const MY_QUEUE_SYMBOL = Symbol.for('my-queue'); - * const MY_STORE_SYMBOL = Symbol.for('my-controllers-store'); - * - * const myStore = getScriptStore(MY_STORE_SYMBOL); - * - * createLoadQueue({ - * store: myStore, - * createController: () => new MyController(), - * queueKey: MY_QUEUE_SYMBOL, - * }); - * - * const controller = useController(myStore); - */ - import {useEffect, useState} from 'react'; -import {isBrowser} from './browser'; - export type ControllerLoadedCallback = (controller: T) => void; -export const QUEUES_SYMBOL = Symbol.for('extension-load-queues'); -export const SINGLE_QUEUE_SYMBOL = Symbol.for('single-queue'); +export const QUEUE_SYMBOL = Symbol.for('queue'); export type ScriptStore = ControllerLoadedCallback[]; -/** - * Interface for creating a load queue. - */ export interface CreateLoadQueueArgs { - // The store where callbacks are saved store: ScriptStore; - - // Function that creates a controller createController: () => T; - - // Flag to check if the queue is already created isQueueCreated?: boolean; - - // Callback to handle queue creation onQueueCreated?: (created: boolean) => void; - - // Symbol key for identifying the queue - queueKey?: symbol; } -/** - * Retrieves or initializes a script store for the given symbol key. - * - * @param {symbol} storeKey - The key associated with the script store. - * @returns {ScriptStore} The script store associated with the given key. - */ -export const getScriptStore = (storeKey: symbol): ScriptStore => { - if (isBrowser()) { - // Initialize the store if it does not exist or check that it is an array - if (!window[storeKey]) { - window[storeKey] = []; - } else if (!Array.isArray(window[storeKey])) { - throw new Error(`Expected window[${String(storeKey)}] to be an array`); - } +// TODO: this function is a very frequently used utility, +// so we should place it in a separate top-level file and document it. +export const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined'; - return window[storeKey] as ScriptStore; +// TODO: window casts are weird — fix encounter any issues with declaration merging +export const getScriptStore = (key: symbol): ScriptStore => { + if (isBrowser()) { + (window as Window)[key] = (window as Window)[key] || []; + return (window as Window)[key]; } else { throw new Error('This functionality should be employed on the client-side.'); } }; -/** - * Ensures that the queues are initialized as an object in the global window. - * - * @returns {void} - */ -const ensureQueuesSymbolInitialized = (): void => { +export const getQueueStore = () => { if (isBrowser()) { - if (typeof window[QUEUES_SYMBOL] !== 'object' || window[QUEUES_SYMBOL] === null) { - window[QUEUES_SYMBOL] = {}; - } - } else { - throw new Error('This functionality should be employed on the client-side.'); + (window as Window)[QUEUE_SYMBOL] = (window as Window)[QUEUE_SYMBOL] || false; + return (window as Window)[QUEUE_SYMBOL]; } -}; -/** - * Retrieves the status of whether the queue associated with the queueKey is created. - * - * @param {symbol} queueKey - The key associated with the queue. - * @returns {boolean} Whether the queue is created. - */ -export const getQueueStore = (queueKey: symbol): boolean => { - ensureQueuesSymbolInitialized(); - return window[QUEUES_SYMBOL]?.[queueKey] || false; + return null; }; -/** - * Returns a function to mark the queue as created for a given queueKey. - * - * @param {symbol} queueKey - The key associated with the queue. - * @returns {function(boolean): void} A function that takes a boolean `created` - * and marks the queue as created. - */ -export const createHandleQueueCreated = (queueKey: symbol): ((created: boolean) => void) => { - ensureQueuesSymbolInitialized(); - return (created: boolean) => { - window[QUEUES_SYMBOL][queueKey] = created; - }; +export const handleQueueCreated = (created: boolean) => { + (window as Window)[QUEUE_SYMBOL] = created; }; -/** - * Creates and manages a load queue. - * - * @param {CreateLoadQueueArgs} args - The arguments required to create a load queue. - * @returns {void} - */ export const createLoadQueue = ({ store, createController, - queueKey = SINGLE_QUEUE_SYMBOL, - isQueueCreated = getQueueStore(queueKey), - onQueueCreated = createHandleQueueCreated(queueKey), -}: CreateLoadQueueArgs): void => { + isQueueCreated = getQueueStore(), + onQueueCreated = handleQueueCreated, +}: CreateLoadQueueArgs) => { if (!store || isQueueCreated) { return; } - - // Mark the queue as created onQueueCreated(true); const controller = createController(); const queue = store.splice(0, store.length); - // Override the store.push method to add callbacks to the queue and start processing store.push = function (...args) { args.forEach((callback) => { queue.push(callback); - - // Start processing the queue unqueue(); }); @@ -152,14 +66,12 @@ export const createLoadQueue = ({ let processing = false; - // Function to start processing the next callback in the queue function unqueue() { if (!processing) { next(); } } - // Function to process callbacks in the queue one by one async function next(): Promise { processing = true; @@ -169,44 +81,27 @@ export const createLoadQueue = ({ return next(); } - // Mark the processing as complete processing = false; } - // Start the queue processing unqueue(); }; -/** - * A no-operation function used as a default cleanup function. - * - * @returns {void} - */ -const noop = (): void => {}; - -/** - * React hook to manage and use a controller with a script store. - * - * @param {ScriptStore} store - The store where the controller is managed. - * @returns {T | null} The current controller or null if not available. - */ -export function useController(store: ScriptStore): T | null { +const noop = () => {}; + +export function useController(store: ScriptStore) { const [controller, setController] = useState(null); useEffect(() => { if (store) { - store.push(setController); // Add setController to the store + store.push(setController); return () => { const index = store.indexOf(setController); if (index > -1) { - // Remove setController when unmounting store.splice(index, 1); } }; - } else { - // eslint-disable-next-line no-console - console.warn('Store is not provided to useController'); // Replace console.warn with a logging function if necessary } return noop; @@ -217,7 +112,8 @@ export function useController(store: ScriptStore): T | null { declare global { interface Window { - [key: symbol]: ScriptStore; - [QUEUES_SYMBOL]: Record; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: symbol]: any; // TODO: sub-optimal, fix any + QUEUE_SYMBOL: boolean; } }