From 2e6e07c21a561ca13d5e74b69609c2cc93f104f4 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Fri, 6 Dec 2024 12:08:39 +0100 Subject: [PATCH] feat: wrap navigator.locks.request with plain promise to help zone.js (#989) Wraps the `navigator.locks.request()` function with a plain Promise as libraries such as zone.js patch this object to track execution context. It appears that this browser API uses a native promise that's not patched, causing the tracking context to be lost. It is believed that wrapping this non-zone.js Promise returned by the browser with a promise that's patched by zone.js can help the situation. Related: - https://github.com/supabase/supabase-js/issues/936 - #830 --- src/lib/locks.ts | 111 +++++++++++++++++++++++++---------------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/src/lib/locks.ts b/src/lib/locks.ts index 6fbb05bd..6ed715a0 100644 --- a/src/lib/locks.ts +++ b/src/lib/locks.ts @@ -78,68 +78,77 @@ export async function navigatorLock( // MDN article: https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request - return await globalThis.navigator.locks.request( - name, - acquireTimeout === 0 - ? { - mode: 'exclusive', - ifAvailable: true, - } - : { - mode: 'exclusive', - signal: abortController.signal, - }, - async (lock) => { - if (lock) { - if (internals.debug) { - console.log('@supabase/gotrue-js: navigatorLock: acquired', name, lock.name) - } - - try { - return await fn() - } finally { - if (internals.debug) { - console.log('@supabase/gotrue-js: navigatorLock: released', name, lock.name) + // 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, } - } - } else { - if (acquireTimeout === 0) { + : { + mode: 'exclusive', + signal: abortController.signal, + }, + async (lock) => { + if (lock) { if (internals.debug) { - console.log('@supabase/gotrue-js: navigatorLock: not immediately available', name) + console.log('@supabase/gotrue-js: navigatorLock: acquired', name, lock.name) } - throw new NavigatorLockAcquireTimeoutError( - `Acquiring an exclusive Navigator LockManager lock "${name}" immediately failed` - ) + try { + return await fn() + } finally { + if (internals.debug) { + console.log('@supabase/gotrue-js: navigatorLock: released', name, lock.name) + } + } } else { - if (internals.debug) { - try { - const result = await globalThis.navigator.locks.query() + if (acquireTimeout === 0) { + if (internals.debug) { + console.log('@supabase/gotrue-js: navigatorLock: not immediately available', name) + } - console.log( - '@supabase/gotrue-js: Navigator LockManager state', - JSON.stringify(result, null, ' ') - ) - } catch (e: any) { - console.warn( - '@supabase/gotrue-js: Error when querying Navigator LockManager state', - e - ) + throw new NavigatorLockAcquireTimeoutError( + `Acquiring an exclusive Navigator LockManager lock "${name}" immediately failed` + ) + } else { + if (internals.debug) { + try { + const result = await globalThis.navigator.locks.query() + + console.log( + '@supabase/gotrue-js: Navigator LockManager state', + JSON.stringify(result, null, ' ') + ) + } catch (e: any) { + 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' - ) + // 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() + return await fn() + } } } - } + ) ) }