Skip to content

Commit 6d15034

Browse files
committed
wip
1 parent 9f338f4 commit 6d15034

File tree

2 files changed

+66
-6
lines changed

2 files changed

+66
-6
lines changed

packages/clerk-js/src/core/auth/__tests__/safeLock.test.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
33
import type { SafeLockReturn } from '../safeLock';
44

55
describe('SafeLock', () => {
6-
let SafeLock: typeof import('../safeLock').SafeLock;
6+
let SafeLock: (key: string) => SafeLockReturn;
77
let addEventListenerSpy: ReturnType<typeof vi.spyOn>;
88

99
beforeEach(async () => {
@@ -37,8 +37,8 @@ describe('SafeLock', () => {
3737

3838
describe('Web Locks API path', () => {
3939
it('uses Web Locks API when available in secure context', async () => {
40-
// Skip if Web Locks not available (like in jsdom without polyfill)
41-
if (!('locks' in navigator) || !navigator.locks) {
40+
// Skip if Web Locks not available or not in secure context
41+
if (!('locks' in navigator) || !navigator.locks || !isSecureContext) {
4242
return;
4343
}
4444

@@ -117,6 +117,62 @@ describe('SafeLock', () => {
117117
});
118118
});
119119

120+
describe('error handling', () => {
121+
it('propagates callback errors without double-invocation', async () => {
122+
const originalLocks = navigator.locks;
123+
const callbackError = new Error('Callback failed');
124+
const callback = vi.fn().mockRejectedValue(callbackError);
125+
126+
const mockRequest = vi.fn().mockImplementation(async (_name, _options, cb) => {
127+
return await cb();
128+
});
129+
130+
Object.defineProperty(navigator, 'locks', {
131+
value: { request: mockRequest },
132+
configurable: true,
133+
});
134+
135+
try {
136+
const lock = SafeLock('test-error-propagation');
137+
await expect(lock.acquireLockAndRun(callback)).rejects.toThrow('Callback failed');
138+
expect(callback).toHaveBeenCalledTimes(1);
139+
} finally {
140+
Object.defineProperty(navigator, 'locks', {
141+
value: originalLocks,
142+
configurable: true,
143+
});
144+
}
145+
});
146+
147+
it('invokes callback in degraded mode on AbortError (timeout)', async () => {
148+
const originalLocks = navigator.locks;
149+
const callback = vi.fn().mockResolvedValue('success');
150+
151+
const abortError = new Error('Lock request aborted');
152+
abortError.name = 'AbortError';
153+
154+
const mockRequest = vi.fn().mockRejectedValue(abortError);
155+
156+
Object.defineProperty(navigator, 'locks', {
157+
value: { request: mockRequest },
158+
configurable: true,
159+
});
160+
161+
try {
162+
const lock = SafeLock('test-abort-fallback');
163+
const result = await lock.acquireLockAndRun(callback);
164+
165+
expect(callback).toHaveBeenCalledTimes(1);
166+
expect(result).toBe('success');
167+
} finally {
168+
Object.defineProperty(navigator, 'locks', {
169+
value: originalLocks,
170+
configurable: true,
171+
});
172+
}
173+
});
174+
});
175+
120176
describe('beforeunload listener consolidation', () => {
121177
it('registers only one beforeunload listener regardless of lock count', () => {
122178
const beforeUnloadCalls = addEventListenerSpy.mock.calls.filter(call => call[0] === 'beforeunload');

packages/clerk-js/src/core/auth/safeLock.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,13 @@ export function SafeLock(key: string): SafeLockReturn {
4848
clearTimeout(lockTimeout);
4949
return await cb();
5050
});
51-
} catch {
52-
debugLogger.warn('Lock acquisition timed out, proceeding without lock (degraded mode)', { key }, 'safeLock');
53-
return await cb();
51+
} catch (error) {
52+
clearTimeout(lockTimeout);
53+
if (error instanceof Error && error.name === 'AbortError') {
54+
debugLogger.warn('Lock acquisition timed out, proceeding without lock (degraded mode)', { key }, 'safeLock');
55+
return await cb();
56+
}
57+
throw error;
5458
}
5559
}
5660

0 commit comments

Comments
 (0)