Skip to content

Commit

Permalink
feat: wrap navigator.locks.request with plain promise to help zone.js (
Browse files Browse the repository at this point in the history
…#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:
- supabase/supabase-js#936
- #830
  • Loading branch information
hf authored Dec 6, 2024
1 parent f808e7a commit 2e6e07c
Showing 1 changed file with 60 additions and 51 deletions.
111 changes: 60 additions & 51 deletions src/lib/locks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,68 +78,77 @@ export async function navigatorLock<R>(

// 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()
}
}
}
}
)
)
}

Expand Down

0 comments on commit 2e6e07c

Please sign in to comment.