187 lines
8.2 KiB
JavaScript
187 lines
8.2 KiB
JavaScript
"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
|