"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.processLock = exports.navigatorLock = exports.ProcessLockAcquireTimeoutError = exports.NavigatorLockAcquireTimeoutError = exports.LockAcquireTimeoutError = exports.internals = void 0; const helpers_1 = require("./helpers"); /** * @experimental */ exports.internals = { /** * @experimental */ debug: !!(globalThis && (0, helpers_1.supportsLocalStorage)() && globalThis.localStorage && globalThis.localStorage.getItem('supabase.gotrue-js.locks.debug') === 'true'), }; /** * An error thrown when a lock cannot be acquired after some amount of time. * * Use the {@link #isAcquireTimeout} property instead of checking with `instanceof`. */ class LockAcquireTimeoutError extends Error { constructor(message) { super(message); this.isAcquireTimeout = true; } } exports.LockAcquireTimeoutError = LockAcquireTimeoutError; class NavigatorLockAcquireTimeoutError extends LockAcquireTimeoutError { } exports.NavigatorLockAcquireTimeoutError = NavigatorLockAcquireTimeoutError; class ProcessLockAcquireTimeoutError extends LockAcquireTimeoutError { } exports.ProcessLockAcquireTimeoutError = ProcessLockAcquireTimeoutError; /** * Implements a global exclusive lock using the Navigator LockManager API. It * is available on all browsers released after 2022-03-15 with Safari being the * last one to release support. If the API is not available, this function will * throw. Make sure you check availablility before configuring {@link * GoTrueClient}. * * You can turn on debugging by setting the `supabase.gotrue-js.locks.debug` * local storage item to `true`. * * Internals: * * Since the LockManager API does not preserve stack traces for the async * function passed in the `request` method, a trick is used where acquiring the * lock releases a previously started promise to run the operation in the `fn` * function. The lock waits for that promise to finish (with or without error), * while the function will finally wait for the result anyway. * * @param name Name of the lock to be acquired. * @param acquireTimeout If negative, no timeout. If 0 an error is thrown if * the lock can't be acquired without waiting. If positive, the lock acquire * will time out after so many milliseconds. An error is * a timeout if it has `isAcquireTimeout` set to true. * @param fn The operation to run once the lock is acquired. */ async function navigatorLock(name, acquireTimeout, fn) { if (exports.internals.debug) { console.log('@supabase/gotrue-js: navigatorLock: acquire lock', name, acquireTimeout); } const abortController = new globalThis.AbortController(); if (acquireTimeout > 0) { setTimeout(() => { abortController.abort(); if (exports.internals.debug) { console.log('@supabase/gotrue-js: navigatorLock acquire timed out', name); } }, acquireTimeout); } // MDN article: https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request // Wrapping navigator.locks.request() with a plain Promise is done as some // libraries like zone.js patch the Promise object to track the execution // context. However, it appears that most browsers use an internal promise // implementation when using the navigator.locks.request() API causing them // to lose context and emit confusing log messages or break certain features. // This wrapping is believed to help zone.js track the execution context // better. return await Promise.resolve().then(() => globalThis.navigator.locks.request(name, acquireTimeout === 0 ? { mode: 'exclusive', ifAvailable: true, } : { mode: 'exclusive', signal: abortController.signal, }, async (lock) => { if (lock) { if (exports.internals.debug) { console.log('@supabase/gotrue-js: navigatorLock: acquired', name, lock.name); } try { return await fn(); } finally { if (exports.internals.debug) { console.log('@supabase/gotrue-js: navigatorLock: released', name, lock.name); } } } else { if (acquireTimeout === 0) { if (exports.internals.debug) { console.log('@supabase/gotrue-js: navigatorLock: not immediately available', name); } throw new NavigatorLockAcquireTimeoutError(`Acquiring an exclusive Navigator LockManager lock "${name}" immediately failed`); } else { if (exports.internals.debug) { try { const result = await globalThis.navigator.locks.query(); console.log('@supabase/gotrue-js: Navigator LockManager state', JSON.stringify(result, null, ' ')); } catch (e) { console.warn('@supabase/gotrue-js: Error when querying Navigator LockManager state', e); } } // Browser is not following the Navigator LockManager spec, it // returned a null lock when we didn't use ifAvailable. So we can // pretend the lock is acquired in the name of backward compatibility // and user experience and just run the function. console.warn('@supabase/gotrue-js: Navigator LockManager returned a null lock when using #request without ifAvailable set to true, it appears this browser is not following the LockManager spec https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request'); return await fn(); } } })); } exports.navigatorLock = navigatorLock; const PROCESS_LOCKS = {}; /** * Implements a global exclusive lock that works only in the current process. * Useful for environments like React Native or other non-browser * single-process (i.e. no concept of "tabs") environments. * * Use {@link #navigatorLock} in browser environments. * * @param name Name of the lock to be acquired. * @param acquireTimeout If negative, no timeout. If 0 an error is thrown if * the lock can't be acquired without waiting. If positive, the lock acquire * will time out after so many milliseconds. An error is * a timeout if it has `isAcquireTimeout` set to true. * @param fn The operation to run once the lock is acquired. */ async function processLock(name, acquireTimeout, fn) { var _a; const previousOperation = (_a = PROCESS_LOCKS[name]) !== null && _a !== void 0 ? _a : Promise.resolve(); const currentOperation = Promise.race([ previousOperation.catch(() => { // ignore error of previous operation that we're waiting to finish return null; }), acquireTimeout >= 0 ? new Promise((_, reject) => { setTimeout(() => { reject(new ProcessLockAcquireTimeoutError(`Acquring process lock with name "${name}" timed out`)); }, acquireTimeout); }) : null, ].filter((x) => x)) .catch((e) => { if (e && e.isAcquireTimeout) { throw e; } return null; }) .then(async () => { // previous operations finished and we didn't get a race on the acquire // timeout, so the current operation can finally start return await fn(); }); PROCESS_LOCKS[name] = currentOperation.catch(async (e) => { if (e && e.isAcquireTimeout) { // if the current operation timed out, it doesn't mean that the previous // operation finished, so we need contnue waiting for it to finish await previousOperation; return null; } throw e; }); // finally wait for the current operation to finish successfully, with an // error or with an acquire timeout error return await currentOperation; } exports.processLock = processLock; //# sourceMappingURL=locks.js.map