@supabase/auth-js 2.107.0-beta.1 → 2.107.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main/GoTrueClient.d.ts +14 -68
- package/dist/main/GoTrueClient.d.ts.map +1 -1
- package/dist/main/GoTrueClient.js +116 -334
- package/dist/main/GoTrueClient.js.map +1 -1
- package/dist/main/lib/errors.d.ts +0 -24
- package/dist/main/lib/errors.d.ts.map +1 -1
- package/dist/main/lib/errors.js +1 -31
- package/dist/main/lib/errors.js.map +1 -1
- package/dist/main/lib/locks.d.ts +34 -28
- package/dist/main/lib/locks.d.ts.map +1 -1
- package/dist/main/lib/locks.js +34 -28
- package/dist/main/lib/locks.js.map +1 -1
- package/dist/main/lib/types.d.ts +27 -16
- package/dist/main/lib/types.d.ts.map +1 -1
- package/dist/main/lib/types.js.map +1 -1
- package/dist/main/lib/version.d.ts +1 -1
- package/dist/main/lib/version.d.ts.map +1 -1
- package/dist/main/lib/version.js +1 -1
- package/dist/main/lib/version.js.map +1 -1
- package/dist/module/GoTrueClient.d.ts +14 -68
- package/dist/module/GoTrueClient.d.ts.map +1 -1
- package/dist/module/GoTrueClient.js +118 -336
- package/dist/module/GoTrueClient.js.map +1 -1
- package/dist/module/lib/errors.d.ts +0 -24
- package/dist/module/lib/errors.d.ts.map +1 -1
- package/dist/module/lib/errors.js +0 -28
- package/dist/module/lib/errors.js.map +1 -1
- package/dist/module/lib/locks.d.ts +34 -28
- package/dist/module/lib/locks.d.ts.map +1 -1
- package/dist/module/lib/locks.js +34 -28
- package/dist/module/lib/locks.js.map +1 -1
- package/dist/module/lib/types.d.ts +27 -16
- package/dist/module/lib/types.d.ts.map +1 -1
- package/dist/module/lib/types.js.map +1 -1
- package/dist/module/lib/version.d.ts +1 -1
- package/dist/module/lib/version.d.ts.map +1 -1
- package/dist/module/lib/version.js +1 -1
- package/dist/module/lib/version.js.map +1 -1
- package/dist/tsconfig.module.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/GoTrueClient.ts +147 -400
- package/src/lib/errors.ts +0 -32
- package/src/lib/locks.ts +34 -29
- package/src/lib/types.ts +27 -16
- package/src/lib/version.ts +1 -1
- package/migrations/lockless-coordination.md +0 -89
package/src/lib/errors.ts
CHANGED
|
@@ -297,38 +297,6 @@ export function isAuthRetryableFetchError(error: unknown): error is AuthRetryabl
|
|
|
297
297
|
return isAuthError(error) && error.name === 'AuthRetryableFetchError'
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
/**
|
|
301
|
-
* Returned when the server rotated a refresh token successfully but the
|
|
302
|
-
* client chose not to persist the rotated tokens because the local session
|
|
303
|
-
* changed mid-flight. Usually means a concurrent `signOut` cleared storage
|
|
304
|
-
* between when the refresh started and when it came back.
|
|
305
|
-
*
|
|
306
|
-
* Set on the `error` field of the refresh result so callers can tell "we
|
|
307
|
-
* got rotated tokens but threw them away" apart from "the refresh failed."
|
|
308
|
-
* The rotated session on the server will be picked up on the next refresh
|
|
309
|
-
* via GoTrue's parent-of-active path.
|
|
310
|
-
*
|
|
311
|
-
* @example
|
|
312
|
-
* ```ts
|
|
313
|
-
* import { isAuthRefreshDiscardedError } from '@supabase/auth-js'
|
|
314
|
-
*
|
|
315
|
-
* if (isAuthRefreshDiscardedError(error)) {
|
|
316
|
-
* // Concurrent signOut/sign-in raced our refresh. Treat as a no-op.
|
|
317
|
-
* }
|
|
318
|
-
* ```
|
|
319
|
-
*/
|
|
320
|
-
export class AuthRefreshDiscardedError extends CustomAuthError {
|
|
321
|
-
constructor(
|
|
322
|
-
message = 'Refresh result discarded: session state changed mid-flight (e.g., concurrent signOut)'
|
|
323
|
-
) {
|
|
324
|
-
super(message, 'AuthRefreshDiscardedError', 409, undefined)
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export function isAuthRefreshDiscardedError(error: unknown): error is AuthRefreshDiscardedError {
|
|
329
|
-
return isAuthError(error) && error.name === 'AuthRefreshDiscardedError'
|
|
330
|
-
}
|
|
331
|
-
|
|
332
300
|
/**
|
|
333
301
|
* This error is thrown on certain methods when the password used is deemed
|
|
334
302
|
* weak. Inspect the reasons to identify what password strength rules are
|
package/src/lib/locks.ts
CHANGED
|
@@ -1,17 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lock primitives retained for backwards-compatible imports. The auth client
|
|
3
|
-
* coordinates refreshes itself (deduping in-instance callers onto a shared
|
|
4
|
-
* in-flight promise) and lets the GoTrue server resolve cross-instance races,
|
|
5
|
-
* so it does not invoke any primitive from this module. The functions still
|
|
6
|
-
* work for direct callers that need a navigator.locks-backed or in-process
|
|
7
|
-
* exclusive lock of their own.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { supportsLocalStorage } from './helpers'
|
|
11
2
|
|
|
12
3
|
/**
|
|
13
|
-
* @deprecated Debug flag for `navigatorLock` / `processLock`. The auth
|
|
14
|
-
* client ignores both, so this has no client-side effect.
|
|
15
4
|
* @experimental
|
|
16
5
|
*/
|
|
17
6
|
export const internals = {
|
|
@@ -29,9 +18,18 @@ export const internals = {
|
|
|
29
18
|
/**
|
|
30
19
|
* An error thrown when a lock cannot be acquired after some amount of time.
|
|
31
20
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
21
|
+
* Use the {@link #isAcquireTimeout} property instead of checking with `instanceof`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { LockAcquireTimeoutError } from '@supabase/auth-js'
|
|
26
|
+
*
|
|
27
|
+
* class CustomLockError extends LockAcquireTimeoutError {
|
|
28
|
+
* constructor() {
|
|
29
|
+
* super('Lock timed out')
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
35
33
|
*/
|
|
36
34
|
export abstract class LockAcquireTimeoutError extends Error {
|
|
37
35
|
public readonly isAcquireTimeout = true
|
|
@@ -42,15 +40,25 @@ export abstract class LockAcquireTimeoutError extends Error {
|
|
|
42
40
|
}
|
|
43
41
|
|
|
44
42
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
43
|
+
* Error thrown when the browser Navigator Lock API fails to acquire a lock.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* import { NavigatorLockAcquireTimeoutError } from '@supabase/auth-js'
|
|
48
|
+
*
|
|
49
|
+
* throw new NavigatorLockAcquireTimeoutError('Lock timed out')
|
|
50
|
+
* ```
|
|
48
51
|
*/
|
|
49
52
|
export class NavigatorLockAcquireTimeoutError extends LockAcquireTimeoutError {}
|
|
50
53
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
+
* Error thrown when the process-level lock helper cannot acquire a lock.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```ts
|
|
58
|
+
* import { ProcessLockAcquireTimeoutError } from '@supabase/auth-js'
|
|
59
|
+
*
|
|
60
|
+
* throw new ProcessLockAcquireTimeoutError('Lock timed out')
|
|
61
|
+
* ```
|
|
54
62
|
*/
|
|
55
63
|
export class ProcessLockAcquireTimeoutError extends LockAcquireTimeoutError {}
|
|
56
64
|
|
|
@@ -78,10 +86,12 @@ export class ProcessLockAcquireTimeoutError extends LockAcquireTimeoutError {}
|
|
|
78
86
|
* will time out after so many milliseconds. An error is
|
|
79
87
|
* a timeout if it has `isAcquireTimeout` set to true.
|
|
80
88
|
* @param fn The operation to run once the lock is acquired.
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* await navigatorLock('sync-user', 1000, async () => {
|
|
92
|
+
* await refreshSession()
|
|
93
|
+
* })
|
|
94
|
+
* ```
|
|
85
95
|
*/
|
|
86
96
|
export async function navigatorLock<R>(
|
|
87
97
|
name: string,
|
|
@@ -310,11 +320,6 @@ const PROCESS_LOCKS: { [name: string]: Promise<any> } = {}
|
|
|
310
320
|
* will time out after so many milliseconds. An error is
|
|
311
321
|
* a timeout if it has `isAcquireTimeout` set to true.
|
|
312
322
|
* @param fn The operation to run once the lock is acquired.
|
|
313
|
-
*
|
|
314
|
-
* @deprecated The auth client coordinates refreshes itself and the server
|
|
315
|
-
* resolves concurrent refresh races, so passing `{ lock: processLock }`
|
|
316
|
-
* to it has no effect. You can safely drop the import from your client setup.
|
|
317
|
-
*
|
|
318
323
|
* @example
|
|
319
324
|
* ```ts
|
|
320
325
|
* await processLock('migrate', 5000, async () => {
|
package/src/lib/types.ts
CHANGED
|
@@ -126,17 +126,12 @@ export type GoTrueClientOptions = {
|
|
|
126
126
|
/* If debug messages are emitted. Can be used to inspect the behavior of the library. If set to a function, the provided function will be used instead of `console.log()` to perform the logging. */
|
|
127
127
|
debug?: boolean | ((message: string, ...args: any[]) => void)
|
|
128
128
|
/**
|
|
129
|
-
* Provide your own locking mechanism based on the environment. By default
|
|
130
|
-
*
|
|
131
|
-
* `
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* `processLock` or Node multi-process setups).
|
|
136
|
-
*
|
|
137
|
-
* @deprecated Custom locks still work in v2.x for backwards compatibility.
|
|
138
|
-
* The legacy lock path will be removed in v3 — drop this option from your
|
|
139
|
-
* constructor options before upgrading.
|
|
129
|
+
* Provide your own locking mechanism based on the environment. By default,
|
|
130
|
+
* `navigatorLock` (Web Locks API) is used in browser environments when
|
|
131
|
+
* `persistSession` is true. Falls back to an in-process lock for non-browser
|
|
132
|
+
* environments (e.g. React Native).
|
|
133
|
+
*
|
|
134
|
+
* @experimental
|
|
140
135
|
*/
|
|
141
136
|
lock?: LockFunc
|
|
142
137
|
/**
|
|
@@ -150,14 +145,30 @@ export type GoTrueClientOptions = {
|
|
|
150
145
|
*/
|
|
151
146
|
throwOnError?: boolean
|
|
152
147
|
/**
|
|
153
|
-
* The maximum time in milliseconds to wait for acquiring
|
|
154
|
-
*
|
|
155
|
-
*
|
|
148
|
+
* The maximum time in milliseconds to wait for acquiring a cross-tab synchronization lock.
|
|
149
|
+
*
|
|
150
|
+
* When multiple browser tabs or windows use the auth client simultaneously, they coordinate
|
|
151
|
+
* via the Web Locks API to prevent race conditions during session refresh and other operations.
|
|
152
|
+
* This timeout controls how long to wait before attempting lock recovery.
|
|
153
|
+
*
|
|
154
|
+
* - **Positive value**: Wait up to this many milliseconds. If the lock is still held, attempt
|
|
155
|
+
* automatic recovery by stealing it (the previous holder is evicted, its callback continues
|
|
156
|
+
* to completion without exclusive access). This recovers from orphaned locks caused by
|
|
157
|
+
* React Strict Mode double-mount, storage API hangs, or aborted operations.
|
|
158
|
+
* - **Zero (0)**: Fail immediately if the lock is unavailable; throws `LockAcquireTimeoutError`
|
|
159
|
+
* (check `error.isAcquireTimeout === true`).
|
|
160
|
+
* - **Negative value**: Wait indefinitely — can cause permanent deadlocks if the lock is orphaned.
|
|
156
161
|
*
|
|
157
162
|
* @default 5000
|
|
158
163
|
*
|
|
159
|
-
* @
|
|
160
|
-
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const client = createClient(url, key, {
|
|
167
|
+
* auth: {
|
|
168
|
+
* lockAcquireTimeout: 5000, // 5 seconds, then steal orphaned lock
|
|
169
|
+
* },
|
|
170
|
+
* })
|
|
171
|
+
* ```
|
|
161
172
|
*/
|
|
162
173
|
lockAcquireTimeout?: number
|
|
163
174
|
|
package/src/lib/version.ts
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
// - Debugging and support (identifying which version is running)
|
|
5
5
|
// - Telemetry and logging (version reporting in errors/analytics)
|
|
6
6
|
// - Ensuring build artifacts match the published package version
|
|
7
|
-
export const version = '2.107.0-
|
|
7
|
+
export const version = '2.107.0-canary.0'
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# Lockless auth coordination
|
|
2
|
-
|
|
3
|
-
**Since:** v2.X.Y (update at release time)
|
|
4
|
-
**Action required by:** v3.0.0
|
|
5
|
-
|
|
6
|
-
`@supabase/auth-js` now coordinates session refreshes without a shared mutex by default. The legacy `lock` option is still honored when supplied, but it is deprecated and will be removed in v3.
|
|
7
|
-
|
|
8
|
-
## What changed
|
|
9
|
-
|
|
10
|
-
- **Default coordination is lockless.** A client constructed without a `lock` option no longer acquires `navigator.locks` or any in-process lock. In-tab concurrent refreshes are deduplicated via the pre-existing single-flight (`refreshingDeferred`); cross-tab refresh races are resolved by GoTrue's server-side parent-of-active mechanism on the v1 refresh-token path.
|
|
11
|
-
- **Commit guard inside `_callRefreshToken`.** The client snapshots storage before the rotated-token fetch and re-reads after. If a non-null pre-fetch snapshot was cleared between the two reads (typical case: a concurrent `signOut` ran `_removeSession`), the rotated tokens are discarded instead of being written back over the cleared storage. The discarded result resolves with `{ data: null, error: new AuthRefreshDiscardedError() }`.
|
|
12
|
-
- **New `AuthRefreshDiscardedError`** (with `isAuthRefreshDiscardedError` type guard). Surfaces through `refreshSession()` and `getSession()` results when the commit guard fires. Distinct from `AuthRetryableFetchError` (transient network) and `AuthApiError` (server rejection).
|
|
13
|
-
- **New `client.auth.dispose()`.** Tears down the auto-refresh interval, the `visibilitychange` listener, the `BroadcastChannel`, and registered `onAuthStateChange` subscribers. Idempotent. Designed for React Strict Mode and HMR cleanup hooks. In-flight `fetch` calls are not aborted — they run to completion.
|
|
14
|
-
- **`lock` and `lockAcquireTimeout` options.** Accepted and honored when supplied (legacy opt-in path); both are `@deprecated` and will be removed in v3.
|
|
15
|
-
|
|
16
|
-
## Who is affected
|
|
17
|
-
|
|
18
|
-
**Most callers: nothing to do.** If you do not pass a `lock` option to `createClient` / `GoTrueClient`, you are already on the lockless default path and will get the bug fixes and new APIs without any code change.
|
|
19
|
-
|
|
20
|
-
**Callers passing a custom `lock`** (typically React Native `processLock`, Node multi-process setups with shared AsyncStorage, or a custom lock implementation):
|
|
21
|
-
|
|
22
|
-
- v2.x (this release): your custom `lock` is still invoked exactly as before. The legacy `_acquireLock` machinery is preserved on an opt-in path gated by `settings.lock != null`. No code change required.
|
|
23
|
-
- v3.0.0 (planned): the `lock` and `lockAcquireTimeout` options will be removed entirely. Drop them from your client options before upgrading to v3.
|
|
24
|
-
|
|
25
|
-
## New APIs worth knowing
|
|
26
|
-
|
|
27
|
-
### `client.auth.dispose()`
|
|
28
|
-
|
|
29
|
-
Tears down the client's background work in one call. Safe to call repeatedly.
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const supabase = createClient(URL, KEY)
|
|
34
|
-
return () => {
|
|
35
|
-
supabase.auth.dispose()
|
|
36
|
-
}
|
|
37
|
-
}, [])
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### `AuthRefreshDiscardedError`
|
|
41
|
-
|
|
42
|
-
Returned from `refreshSession()` / `getSession()` when the commit guard discards a successfully-rotated session.
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { isAuthRefreshDiscardedError } from '@supabase/auth-js'
|
|
46
|
-
|
|
47
|
-
const { data, error } = await supabase.auth.refreshSession()
|
|
48
|
-
if (isAuthRefreshDiscardedError(error)) {
|
|
49
|
-
// A concurrent signOut cleared storage between fetch start and now.
|
|
50
|
-
// The rotated tokens were discarded; the SIGNED_OUT event already fired.
|
|
51
|
-
// Treat as a successful no-op — no need to retry.
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
## Behavior changes worth flagging
|
|
56
|
-
|
|
57
|
-
- **`_autoRefreshTokenTick` may run concurrently with `signOut` / `setSession` / `getUser`** on the lockless default path. Previously the tick used `_acquireLock(0, ...)` which skipped whenever any auth op held the lock. The lockless equivalent only skips when `refreshingDeferred` is set. The commit guard keeps storage consistent under the new concurrency. The legacy lock opt-in path retains the old skip-on-any-lock behavior.
|
|
58
|
-
- **`onAuthStateChange` async callbacks** that call `getUser`, `setSession`, or read the session from inside the callback are now safe on the default path (previously deadlocked through the lock). One residual hazard remains: calling `refreshSession` (or anything routing through `_callRefreshToken`) from inside a `TOKEN_REFRESHED` handler still deadlocks via `refreshingDeferred`. The `@deprecated` marker on the async overload is kept with its reason updated to point at this specific case.
|
|
59
|
-
- **Subscriber timing on the default path:** subscribers stay awaited; same as before. What changes is that `signOut` no longer waits for an in-flight refresh's HTTP and continuation to finish before its own fetch goes out. Both fetches now run concurrently, and the commit guard keeps storage consistent.
|
|
60
|
-
|
|
61
|
-
## Migration steps
|
|
62
|
-
|
|
63
|
-
### If you do not pass a custom `lock` (most users)
|
|
64
|
-
|
|
65
|
-
No action required for v2. No action required for v3.
|
|
66
|
-
|
|
67
|
-
### If you pass a custom `lock` (e.g., React Native `processLock`)
|
|
68
|
-
|
|
69
|
-
No action required for v2 — your lock continues to work.
|
|
70
|
-
|
|
71
|
-
For v3 readiness:
|
|
72
|
-
|
|
73
|
-
1. Remove `lock` and `lockAcquireTimeout` from your `createClient` / `GoTrueClient` options before upgrading to v3.
|
|
74
|
-
2. If you depended on cross-process serialization (e.g., Node multi-process with shared AsyncStorage), validate that the lockless coordination (in-tab single-flight + server parent-of-active) is sufficient for your runtime. The default is safe for the cases the lock was originally added to handle (cross-tab refresh races), since the server resolves them.
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
// Before (v2.x, still works):
|
|
78
|
-
const supabase = createClient(URL, KEY, {
|
|
79
|
-
auth: { lock: processLock, lockAcquireTimeout: 5000 },
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
// After (v3-ready):
|
|
83
|
-
const supabase = createClient(URL, KEY)
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## Reference
|
|
87
|
-
|
|
88
|
-
- Server-side parent-of-active mechanism: `internal/tokens/service.go:376-385` in the [supabase/auth](https://github.com/supabase/auth) repo (v1 branch, the `*models.RefreshToken` type assertion). When a request arrives with a revoked refresh token whose child is the currently-active token, the server returns the active token instead of rejecting — both tabs receive the same rotated token under DB row locking.
|
|
89
|
-
- `lib/locks.ts` exports (`navigatorLock`, `processLock`, `LockAcquireTimeoutError`, `NavigatorLockAcquireTimeoutError`, `ProcessLockAcquireTimeoutError`, `internals`) remain available for direct imports, marked `@deprecated`. Direct callers who use these exports outside the `GoTrueClient` constructor option are unaffected.
|