@supabase/auth-js 2.107.0-beta.0 → 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
|
@@ -25,16 +25,10 @@ const DEFAULT_OPTIONS = {
|
|
|
25
25
|
debug: false,
|
|
26
26
|
hasCustomAuthorizationHeader: false,
|
|
27
27
|
throwOnError: false,
|
|
28
|
-
lockAcquireTimeout: 5000, // 5 seconds
|
|
28
|
+
lockAcquireTimeout: 5000, // 5 seconds
|
|
29
29
|
skipAutoInitialize: false,
|
|
30
30
|
experimental: {},
|
|
31
31
|
};
|
|
32
|
-
/**
|
|
33
|
-
* No-op lock used internally as a placeholder. Kept so older test setups that
|
|
34
|
-
* inject this exact reference do not break; new code never sees it because
|
|
35
|
-
* `this.lock` stays `null` when no custom lock is supplied (lockless path).
|
|
36
|
-
* TODO(v3): remove with the legacy lock path.
|
|
37
|
-
*/
|
|
38
32
|
async function lockNoOp(name, acquireTimeout, fn) {
|
|
39
33
|
return await fn();
|
|
40
34
|
}
|
|
@@ -88,7 +82,7 @@ class GoTrueClient {
|
|
|
88
82
|
* ```
|
|
89
83
|
*/
|
|
90
84
|
constructor(options) {
|
|
91
|
-
var _a, _b, _c;
|
|
85
|
+
var _a, _b, _c, _d;
|
|
92
86
|
/**
|
|
93
87
|
* @experimental
|
|
94
88
|
*/
|
|
@@ -99,14 +93,6 @@ class GoTrueClient {
|
|
|
99
93
|
this.autoRefreshTickTimeout = null;
|
|
100
94
|
this.visibilityChangedCallback = null;
|
|
101
95
|
this.refreshingDeferred = null;
|
|
102
|
-
/**
|
|
103
|
-
* Monotonic counter incremented at the top of `_removeSession`, before any
|
|
104
|
-
* `await`. The commit guard inside `_callRefreshToken` captures this value
|
|
105
|
-
* before `_saveSession` and re-checks it after, so a `signOut` that
|
|
106
|
-
* interleaves inside `_saveSession`'s storage-write awaits is still caught
|
|
107
|
-
* (the post-fetch storage snapshot alone misses that window).
|
|
108
|
-
*/
|
|
109
|
-
this._sessionRemovalEpoch = 0;
|
|
110
96
|
/**
|
|
111
97
|
* Keeps track of the async client initialization.
|
|
112
98
|
* When null or not yet resolved the auth state is `unknown`
|
|
@@ -117,13 +103,6 @@ class GoTrueClient {
|
|
|
117
103
|
this.detectSessionInUrl = true;
|
|
118
104
|
this.hasCustomAuthorizationHeader = false;
|
|
119
105
|
this.suppressGetSessionWarning = false;
|
|
120
|
-
/**
|
|
121
|
-
* Custom lock function passed via `settings.lock`. When non-null, every auth
|
|
122
|
-
* operation runs inside `_acquireLock`. When null (the default), the client
|
|
123
|
-
* uses its lockless coordination (refresh single-flight + commit guard).
|
|
124
|
-
* TODO(v3): remove along with the legacy lock path.
|
|
125
|
-
*/
|
|
126
|
-
this.lock = null;
|
|
127
106
|
this.lockAcquired = false;
|
|
128
107
|
this.pendingInLock = [];
|
|
129
108
|
/**
|
|
@@ -158,22 +137,21 @@ class GoTrueClient {
|
|
|
158
137
|
this.url = settings.url;
|
|
159
138
|
this.headers = settings.headers;
|
|
160
139
|
this.fetch = (0, helpers_1.resolveFetch)(settings.fetch);
|
|
140
|
+
this.lock = settings.lock || lockNoOp;
|
|
161
141
|
this.detectSessionInUrl = settings.detectSessionInUrl;
|
|
162
142
|
this.flowType = settings.flowType;
|
|
163
143
|
this.hasCustomAuthorizationHeader = settings.hasCustomAuthorizationHeader;
|
|
164
144
|
this.throwOnError = settings.throwOnError;
|
|
165
|
-
// Always wire `lockAcquireTimeout` even on the lockless path: consumers
|
|
166
|
-
// (including supabase-js tests) read it off the client to verify option
|
|
167
|
-
// flow-through.
|
|
168
145
|
this.lockAcquireTimeout = settings.lockAcquireTimeout;
|
|
169
|
-
|
|
170
|
-
// compatibility with callers passing a custom `lock` (typically React
|
|
171
|
-
// Native `processLock` or Node multi-process setups). When `settings.lock`
|
|
172
|
-
// is null the client uses its lockless coordination — no `navigator.locks`
|
|
173
|
-
// by default, no implicit `processLock`.
|
|
174
|
-
if (settings.lock != null) {
|
|
146
|
+
if (settings.lock) {
|
|
175
147
|
this.lock = settings.lock;
|
|
176
148
|
}
|
|
149
|
+
else if (this.persistSession && (0, helpers_1.isBrowser)() && ((_c = globalThis === null || globalThis === void 0 ? void 0 : globalThis.navigator) === null || _c === void 0 ? void 0 : _c.locks)) {
|
|
150
|
+
this.lock = locks_1.navigatorLock;
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
this.lock = lockNoOp;
|
|
154
|
+
}
|
|
177
155
|
if (!this.jwks) {
|
|
178
156
|
this.jwks = { keys: [] };
|
|
179
157
|
this.jwks_cached_at = Number.MIN_SAFE_INTEGER;
|
|
@@ -232,7 +210,7 @@ class GoTrueClient {
|
|
|
232
210
|
catch (e) {
|
|
233
211
|
console.error('Failed to create a new BroadcastChannel, multi-tab state changes will not be available', e);
|
|
234
212
|
}
|
|
235
|
-
(
|
|
213
|
+
(_d = this.broadcastChannel) === null || _d === void 0 ? void 0 : _d.addEventListener('message', async (event) => {
|
|
236
214
|
this._debug('received broadcast notification from other tab or client', event);
|
|
237
215
|
try {
|
|
238
216
|
await this._notifyAllSubscribers(event.data.event, event.data.session, false); // broadcast = false so we don't get an endless loop of messages
|
|
@@ -290,13 +268,9 @@ class GoTrueClient {
|
|
|
290
268
|
return await this.initializePromise;
|
|
291
269
|
}
|
|
292
270
|
this.initializePromise = (async () => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
return await this._initialize();
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
return await this._initialize();
|
|
271
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
272
|
+
return await this._initialize();
|
|
273
|
+
});
|
|
300
274
|
})();
|
|
301
275
|
return await this.initializePromise;
|
|
302
276
|
}
|
|
@@ -1147,13 +1121,9 @@ class GoTrueClient {
|
|
|
1147
1121
|
*/
|
|
1148
1122
|
async exchangeCodeForSession(authCode) {
|
|
1149
1123
|
await this.initializePromise;
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return this._exchangeCodeForSession(authCode);
|
|
1154
|
-
});
|
|
1155
|
-
}
|
|
1156
|
-
return this._exchangeCodeForSession(authCode);
|
|
1124
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
1125
|
+
return this._exchangeCodeForSession(authCode);
|
|
1126
|
+
});
|
|
1157
1127
|
}
|
|
1158
1128
|
/**
|
|
1159
1129
|
* Signs in a user by verifying a message signed by the user's private key.
|
|
@@ -2057,13 +2027,9 @@ class GoTrueClient {
|
|
|
2057
2027
|
*/
|
|
2058
2028
|
async reauthenticate() {
|
|
2059
2029
|
await this.initializePromise;
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
return await this._reauthenticate();
|
|
2064
|
-
});
|
|
2065
|
-
}
|
|
2066
|
-
return await this._reauthenticate();
|
|
2030
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2031
|
+
return await this._reauthenticate();
|
|
2032
|
+
});
|
|
2067
2033
|
}
|
|
2068
2034
|
async _reauthenticate() {
|
|
2069
2035
|
try {
|
|
@@ -2206,7 +2172,7 @@ class GoTrueClient {
|
|
|
2206
2172
|
* - If the session's access token is expired or is about to expire, this method will use the refresh token to refresh the session.
|
|
2207
2173
|
* - When using in a browser, or you've called `startAutoRefresh()` in your environment (React Native, etc.) this function always returns a valid access token without refreshing the session itself, as this is done in the background. This function returns very fast.
|
|
2208
2174
|
* - **IMPORTANT SECURITY NOTICE:** If using an insecure storage medium, such as cookies or request headers, the user object returned by this function **must not be trusted**. Always verify the JWT using `getClaims()` or your own JWT verification library to securely establish the user's identity and access. You can also use `getUser()` to fetch the user object directly from the Auth server for this purpose.
|
|
2209
|
-
* -
|
|
2175
|
+
* - When using in a browser, this function is synchronized across all tabs using the [LockManager](https://developer.mozilla.org/en-US/docs/Web/API/LockManager) API. In other environments make sure you've defined a proper `lock` property, if necessary, to make sure there are no race conditions while the session is being refreshed.
|
|
2210
2176
|
*
|
|
2211
2177
|
* @example Get the session data
|
|
2212
2178
|
* ```js
|
|
@@ -2273,24 +2239,15 @@ class GoTrueClient {
|
|
|
2273
2239
|
*/
|
|
2274
2240
|
async getSession() {
|
|
2275
2241
|
await this.initializePromise;
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
return this._useSession(async (result) => {
|
|
2280
|
-
return result;
|
|
2281
|
-
});
|
|
2242
|
+
const result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2243
|
+
return this._useSession(async (result) => {
|
|
2244
|
+
return result;
|
|
2282
2245
|
});
|
|
2283
|
-
}
|
|
2284
|
-
return await this._useSession(async (result) => {
|
|
2285
|
-
return result;
|
|
2286
2246
|
});
|
|
2247
|
+
return result;
|
|
2287
2248
|
}
|
|
2288
2249
|
/**
|
|
2289
2250
|
* Acquires a global lock based on the storage key.
|
|
2290
|
-
*
|
|
2291
|
-
* TODO(v3): remove along with the legacy lock path. Only called when
|
|
2292
|
-
* `this.lock` is non-null (custom lock supplied via constructor). The
|
|
2293
|
-
* default lockless path bypasses this entirely.
|
|
2294
2251
|
*/
|
|
2295
2252
|
async _acquireLock(acquireTimeout, fn) {
|
|
2296
2253
|
this._debug('#_acquireLock', 'begin', acquireTimeout);
|
|
@@ -2346,17 +2303,15 @@ class GoTrueClient {
|
|
|
2346
2303
|
}
|
|
2347
2304
|
}
|
|
2348
2305
|
/**
|
|
2349
|
-
* Use instead of {@link #getSession} inside the library.
|
|
2350
|
-
*
|
|
2351
|
-
*
|
|
2306
|
+
* Use instead of {@link #getSession} inside the library. It is
|
|
2307
|
+
* semantically usually what you want, as getting a session involves some
|
|
2308
|
+
* processing afterwards that requires only one client operating on the
|
|
2309
|
+
* session at once across multiple tabs or processes.
|
|
2352
2310
|
*/
|
|
2353
2311
|
async _useSession(fn) {
|
|
2354
2312
|
this._debug('#_useSession', 'begin');
|
|
2355
2313
|
try {
|
|
2356
|
-
//
|
|
2357
|
-
// idempotent, and the only write path inside it (refresh) is
|
|
2358
|
-
// single-flighted downstream by `refreshingDeferred` in
|
|
2359
|
-
// `_callRefreshToken`. No serialization is needed at this layer.
|
|
2314
|
+
// the use of __loadSession here is the only correct use of the function!
|
|
2360
2315
|
const result = await this.__loadSession();
|
|
2361
2316
|
return await fn(result);
|
|
2362
2317
|
}
|
|
@@ -2371,8 +2326,7 @@ class GoTrueClient {
|
|
|
2371
2326
|
*/
|
|
2372
2327
|
async __loadSession() {
|
|
2373
2328
|
this._debug('#__loadSession()', 'begin');
|
|
2374
|
-
if (
|
|
2375
|
-
// TODO(v3): remove. Only meaningful on the legacy lock path.
|
|
2329
|
+
if (!this.lockAcquired) {
|
|
2376
2330
|
this._debug('#__loadSession()', 'used outside of an acquired lock!', new Error().stack);
|
|
2377
2331
|
}
|
|
2378
2332
|
try {
|
|
@@ -2515,16 +2469,9 @@ class GoTrueClient {
|
|
|
2515
2469
|
return await this._getUser(jwt);
|
|
2516
2470
|
}
|
|
2517
2471
|
await this.initializePromise;
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2522
|
-
return await this._getUser();
|
|
2523
|
-
});
|
|
2524
|
-
}
|
|
2525
|
-
else {
|
|
2526
|
-
result = await this._getUser();
|
|
2527
|
-
}
|
|
2472
|
+
const result = await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2473
|
+
return await this._getUser();
|
|
2474
|
+
});
|
|
2528
2475
|
if (result.data.user) {
|
|
2529
2476
|
this.suppressGetSessionWarning = true;
|
|
2530
2477
|
}
|
|
@@ -2685,13 +2632,9 @@ class GoTrueClient {
|
|
|
2685
2632
|
*/
|
|
2686
2633
|
async updateUser(attributes, options = {}) {
|
|
2687
2634
|
await this.initializePromise;
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
return await this._updateUser(attributes, options);
|
|
2692
|
-
});
|
|
2693
|
-
}
|
|
2694
|
-
return await this._updateUser(attributes, options);
|
|
2635
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2636
|
+
return await this._updateUser(attributes, options);
|
|
2637
|
+
});
|
|
2695
2638
|
}
|
|
2696
2639
|
async _updateUser(attributes, options = {}) {
|
|
2697
2640
|
try {
|
|
@@ -2860,13 +2803,9 @@ class GoTrueClient {
|
|
|
2860
2803
|
*/
|
|
2861
2804
|
async setSession(currentSession) {
|
|
2862
2805
|
await this.initializePromise;
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
return await this._setSession(currentSession);
|
|
2867
|
-
});
|
|
2868
|
-
}
|
|
2869
|
-
return await this._setSession(currentSession);
|
|
2806
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2807
|
+
return await this._setSession(currentSession);
|
|
2808
|
+
});
|
|
2870
2809
|
}
|
|
2871
2810
|
async _setSession(currentSession) {
|
|
2872
2811
|
try {
|
|
@@ -3044,13 +2983,9 @@ class GoTrueClient {
|
|
|
3044
2983
|
*/
|
|
3045
2984
|
async refreshSession(currentSession) {
|
|
3046
2985
|
await this.initializePromise;
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
return await this._refreshSession(currentSession);
|
|
3051
|
-
});
|
|
3052
|
-
}
|
|
3053
|
-
return await this._refreshSession(currentSession);
|
|
2986
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
2987
|
+
return await this._refreshSession(currentSession);
|
|
2988
|
+
});
|
|
3054
2989
|
}
|
|
3055
2990
|
async _refreshSession(currentSession) {
|
|
3056
2991
|
try {
|
|
@@ -3188,7 +3123,7 @@ class GoTrueClient {
|
|
|
3188
3123
|
if (typeof this.detectSessionInUrl === 'function') {
|
|
3189
3124
|
return this.detectSessionInUrl(new URL(window.location.href), params);
|
|
3190
3125
|
}
|
|
3191
|
-
return Boolean(params.access_token || params.error_description);
|
|
3126
|
+
return Boolean(params.access_token || params.error || params.error_description || params.error_code);
|
|
3192
3127
|
}
|
|
3193
3128
|
/**
|
|
3194
3129
|
* Checks if the current URL and backing storage contain parameters given by a PKCE flow
|
|
@@ -3240,13 +3175,9 @@ class GoTrueClient {
|
|
|
3240
3175
|
*/
|
|
3241
3176
|
async signOut(options = { scope: 'global' }) {
|
|
3242
3177
|
await this.initializePromise;
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
return await this._signOut(options);
|
|
3247
|
-
});
|
|
3248
|
-
}
|
|
3249
|
-
return await this._signOut(options);
|
|
3178
|
+
return await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3179
|
+
return await this._signOut(options);
|
|
3180
|
+
});
|
|
3250
3181
|
}
|
|
3251
3182
|
async _signOut({ scope } = { scope: 'global' }) {
|
|
3252
3183
|
return await this._useSession(async (result) => {
|
|
@@ -3282,8 +3213,18 @@ class GoTrueClient {
|
|
|
3282
3213
|
* - Subscribes to important events occurring on the user's session.
|
|
3283
3214
|
* - Use on the frontend/client. It is less useful on the server.
|
|
3284
3215
|
* - Events are emitted across tabs to keep your application's UI up-to-date. Some events can fire very frequently, based on the number of tabs open. Use a quick and efficient callback function, and defer or debounce as many operations as you can to be performed outside of the callback.
|
|
3285
|
-
* -
|
|
3286
|
-
*
|
|
3216
|
+
* - **Important:** A callback can be an `async` function and it runs synchronously during the processing of the changes causing the event. You can easily create a dead-lock by using `await` on a call to another method of the Supabase library.
|
|
3217
|
+
* - Avoid using `async` functions as callbacks.
|
|
3218
|
+
* - Limit the number of `await` calls in `async` callbacks.
|
|
3219
|
+
* - Do not use other Supabase functions in the callback function. If you must, dispatch the functions once the callback has finished executing. Use this as a quick way to achieve this:
|
|
3220
|
+
* ```js
|
|
3221
|
+
* supabase.auth.onAuthStateChange((event, session) => {
|
|
3222
|
+
* setTimeout(async () => {
|
|
3223
|
+
* // await on other Supabase function here
|
|
3224
|
+
* // this runs right after the callback has finished
|
|
3225
|
+
* }, 0)
|
|
3226
|
+
* })
|
|
3227
|
+
* ```
|
|
3287
3228
|
* - Emitted events:
|
|
3288
3229
|
* - `INITIAL_SESSION`
|
|
3289
3230
|
* - Emitted right after the Supabase client is constructed and the initial session from storage is loaded.
|
|
@@ -3466,15 +3407,9 @@ class GoTrueClient {
|
|
|
3466
3407
|
this.stateChangeEmitters.set(id, subscription);
|
|
3467
3408
|
(async () => {
|
|
3468
3409
|
await this.initializePromise;
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
this._emitInitialSession(id);
|
|
3473
|
-
});
|
|
3474
|
-
}
|
|
3475
|
-
else {
|
|
3476
|
-
await this._emitInitialSession(id);
|
|
3477
|
-
}
|
|
3410
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
3411
|
+
this._emitInitialSession(id);
|
|
3412
|
+
});
|
|
3478
3413
|
})();
|
|
3479
3414
|
return { data: { subscription } };
|
|
3480
3415
|
}
|
|
@@ -3816,10 +3751,7 @@ class GoTrueClient {
|
|
|
3816
3751
|
* @param refreshToken A valid refresh token that was returned on login.
|
|
3817
3752
|
*/
|
|
3818
3753
|
async _refreshAccessToken(refreshToken) {
|
|
3819
|
-
|
|
3820
|
-
// fragment of the token in the debug tag, even when `debug: true` is
|
|
3821
|
-
// enabled (logs may be forwarded to third-party services).
|
|
3822
|
-
const debugName = `#_refreshAccessToken()`;
|
|
3754
|
+
const debugName = `#_refreshAccessToken(${refreshToken.substring(0, 5)}...)`;
|
|
3823
3755
|
this._debug(debugName, 'begin');
|
|
3824
3756
|
try {
|
|
3825
3757
|
const startedAt = Date.now();
|
|
@@ -3926,18 +3858,10 @@ class GoTrueClient {
|
|
|
3926
3858
|
if (this.autoRefreshToken && currentSession.refresh_token) {
|
|
3927
3859
|
const { error } = await this._callRefreshToken(currentSession.refresh_token);
|
|
3928
3860
|
if (error) {
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
this._debug(debugName, 'refresh discarded by commit guard', error);
|
|
3934
|
-
}
|
|
3935
|
-
else {
|
|
3936
|
-
console.error(error);
|
|
3937
|
-
if (!(0, errors_1.isAuthRetryableFetchError)(error)) {
|
|
3938
|
-
this._debug(debugName, 'refresh failed with a non-retryable error, removing the session', error);
|
|
3939
|
-
await this._removeSession();
|
|
3940
|
-
}
|
|
3861
|
+
console.error(error);
|
|
3862
|
+
if (!(0, errors_1.isAuthRetryableFetchError)(error)) {
|
|
3863
|
+
this._debug(debugName, 'refresh failed with a non-retryable error, removing the session', error);
|
|
3864
|
+
await this._removeSession();
|
|
3941
3865
|
}
|
|
3942
3866
|
}
|
|
3943
3867
|
}
|
|
@@ -3986,69 +3910,16 @@ class GoTrueClient {
|
|
|
3986
3910
|
if (this.refreshingDeferred) {
|
|
3987
3911
|
return this.refreshingDeferred.promise;
|
|
3988
3912
|
}
|
|
3989
|
-
|
|
3990
|
-
// fragment of the token in the debug tag, even when `debug: true` is
|
|
3991
|
-
// enabled (logs may be forwarded to third-party services).
|
|
3992
|
-
const debugName = `#_callRefreshToken()`;
|
|
3913
|
+
const debugName = `#_callRefreshToken(${refreshToken.substring(0, 5)}...)`;
|
|
3993
3914
|
this._debug(debugName, 'begin');
|
|
3994
3915
|
try {
|
|
3995
3916
|
this.refreshingDeferred = new helpers_1.Deferred();
|
|
3996
|
-
// Snapshot storage before the fetch. The commit guard discards the
|
|
3997
|
-
// rotated tokens only when a non-null pre-fetch snapshot changed under
|
|
3998
|
-
// us — typical case: a concurrent `signOut` ran `_removeSession`, or
|
|
3999
|
-
// another tab's refresh rewrote the slot. Callers passing
|
|
4000
|
-
// externally-sourced tokens (SSR cookie handoff, multi-account
|
|
4001
|
-
// switching, `setSession`/`refreshSession({ refresh_token })`) may
|
|
4002
|
-
// start from a null snapshot OR from a non-null snapshot whose
|
|
4003
|
-
// refresh_token differs from the one they're hydrating; in both
|
|
4004
|
-
// cases the guard fires only when storage was *modified between
|
|
4005
|
-
// snapshots*, not when the input token disagrees with what's stored.
|
|
4006
|
-
const storedAtStart = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
4007
3917
|
const { data, error } = await this._refreshAccessToken(refreshToken);
|
|
4008
3918
|
if (error)
|
|
4009
3919
|
throw error;
|
|
4010
3920
|
if (!data.session)
|
|
4011
3921
|
throw new errors_1.AuthSessionMissingError();
|
|
4012
|
-
const storedAfter = (await (0, helpers_1.getItemAsync)(this.storage, this.storageKey));
|
|
4013
|
-
const storageChangedUnderUs = storedAtStart !== null &&
|
|
4014
|
-
(storedAfter === null || storedAfter.refresh_token !== storedAtStart.refresh_token);
|
|
4015
|
-
if (storageChangedUnderUs) {
|
|
4016
|
-
this._debug(debugName, 'commit guard: storage changed since refresh started, discarding rotated tokens', {
|
|
4017
|
-
// Presence indicators only — never log refresh token fragments,
|
|
4018
|
-
// even partial. Logs may be forwarded to third-party services.
|
|
4019
|
-
startedWith: 'present',
|
|
4020
|
-
nowHolds: storedAfter ? 'replaced' : 'cleared',
|
|
4021
|
-
});
|
|
4022
|
-
const discarded = {
|
|
4023
|
-
data: null,
|
|
4024
|
-
error: new errors_1.AuthRefreshDiscardedError(),
|
|
4025
|
-
};
|
|
4026
|
-
this.refreshingDeferred.resolve(discarded);
|
|
4027
|
-
return discarded;
|
|
4028
|
-
}
|
|
4029
|
-
// Second leg of the commit guard: close the TOCTOU window between the
|
|
4030
|
-
// synchronous `storageChangedUnderUs` check and the actual storage
|
|
4031
|
-
// writes inside `_saveSession`. A concurrent `signOut → _removeSession`
|
|
4032
|
-
// can land inside `_saveSession`'s `await setItemAsync(...)` yields and
|
|
4033
|
-
// clear storage just before we overwrite it. Capture the epoch BEFORE
|
|
4034
|
-
// the save and re-check after; if it advanced, undo the write directly
|
|
4035
|
-
// (do NOT call `_removeSession` — that would emit a duplicate
|
|
4036
|
-
// SIGNED_OUT for the concurrent signOut that already fired one).
|
|
4037
|
-
const epochBeforeSave = this._sessionRemovalEpoch;
|
|
4038
3922
|
await this._saveSession(data.session);
|
|
4039
|
-
if (this._sessionRemovalEpoch !== epochBeforeSave) {
|
|
4040
|
-
this._debug(debugName, 'commit guard (post-save): _removeSession ran during _saveSession, undoing write');
|
|
4041
|
-
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
|
|
4042
|
-
if (this.userStorage) {
|
|
4043
|
-
await (0, helpers_1.removeItemAsync)(this.userStorage, this.storageKey + '-user');
|
|
4044
|
-
}
|
|
4045
|
-
const discarded = {
|
|
4046
|
-
data: null,
|
|
4047
|
-
error: new errors_1.AuthRefreshDiscardedError(),
|
|
4048
|
-
};
|
|
4049
|
-
this.refreshingDeferred.resolve(discarded);
|
|
4050
|
-
return discarded;
|
|
4051
|
-
}
|
|
4052
3923
|
await this._notifyAllSubscribers('TOKEN_REFRESHED', data.session);
|
|
4053
3924
|
const result = { data: data.session, error: null };
|
|
4054
3925
|
this.refreshingDeferred.resolve(result);
|
|
@@ -4142,11 +4013,6 @@ class GoTrueClient {
|
|
|
4142
4013
|
}
|
|
4143
4014
|
}
|
|
4144
4015
|
async _removeSession() {
|
|
4145
|
-
// Bump synchronously, BEFORE any `await`, so that `_callRefreshToken`'s
|
|
4146
|
-
// post-save check sees the increment whenever this method has started —
|
|
4147
|
-
// even if it hasn't finished. Pairs with the epoch check in
|
|
4148
|
-
// `_callRefreshToken`. See `_sessionRemovalEpoch` field doc.
|
|
4149
|
-
this._sessionRemovalEpoch += 1;
|
|
4150
4016
|
this._debug('#_removeSession()');
|
|
4151
4017
|
this.suppressGetSessionWarning = false;
|
|
4152
4018
|
await (0, helpers_1.removeItemAsync)(this.storage, this.storageKey);
|
|
@@ -4317,122 +4183,47 @@ class GoTrueClient {
|
|
|
4317
4183
|
this._removeVisibilityChangedCallback();
|
|
4318
4184
|
await this._stopAutoRefresh();
|
|
4319
4185
|
}
|
|
4320
|
-
/**
|
|
4321
|
-
* Tears down the client's background work: stops the auto-refresh interval,
|
|
4322
|
-
* removes the `visibilitychange` listener, closes the cross-tab
|
|
4323
|
-
* `BroadcastChannel`, and clears registered `onAuthStateChange` subscribers.
|
|
4324
|
-
*
|
|
4325
|
-
* Call this from cleanup hooks when the client is being replaced before
|
|
4326
|
-
* its JS realm is destroyed. React Strict Mode and HMR are the common
|
|
4327
|
-
* cases. Any in-flight `fetch` calls continue to completion and may still
|
|
4328
|
-
* write to storage; dispose doesn't abort them or erase storage.
|
|
4329
|
-
*
|
|
4330
|
-
* Lifecycle caveat: because in-flight refreshes are not aborted, a
|
|
4331
|
-
* disposed instance can still persist a rotated session to storage after
|
|
4332
|
-
* `dispose()` returns. A subsequent `createClient` against the same
|
|
4333
|
-
* `storageKey` will pick up that session on its next read. If you need
|
|
4334
|
-
* strict isolation between client lifecycles, await any pending auth
|
|
4335
|
-
* operation before calling `dispose()` (or change the `storageKey` for
|
|
4336
|
-
* the replacement client).
|
|
4337
|
-
*
|
|
4338
|
-
* Safe to call repeatedly.
|
|
4339
|
-
*
|
|
4340
|
-
* @category Auth
|
|
4341
|
-
*
|
|
4342
|
-
* @example Cleanup on React unmount
|
|
4343
|
-
* ```ts
|
|
4344
|
-
* useEffect(() => {
|
|
4345
|
-
* const client = createClient(...)
|
|
4346
|
-
* return () => { client.auth.dispose() }
|
|
4347
|
-
* }, [])
|
|
4348
|
-
* ```
|
|
4349
|
-
*/
|
|
4350
|
-
async dispose() {
|
|
4351
|
-
var _a;
|
|
4352
|
-
this._removeVisibilityChangedCallback();
|
|
4353
|
-
await this._stopAutoRefresh();
|
|
4354
|
-
(_a = this.broadcastChannel) === null || _a === void 0 ? void 0 : _a.close();
|
|
4355
|
-
this.broadcastChannel = null;
|
|
4356
|
-
this.stateChangeEmitters.clear();
|
|
4357
|
-
}
|
|
4358
4186
|
/**
|
|
4359
4187
|
* Runs the auto refresh token tick.
|
|
4360
4188
|
*/
|
|
4361
4189
|
async _autoRefreshTokenTick() {
|
|
4362
4190
|
this._debug('#_autoRefreshTokenTick()', 'begin');
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
// of queuing behind a long-running operation.
|
|
4368
|
-
try {
|
|
4369
|
-
await this._acquireLock(0, async () => {
|
|
4191
|
+
try {
|
|
4192
|
+
await this._acquireLock(0, async () => {
|
|
4193
|
+
try {
|
|
4194
|
+
const now = Date.now();
|
|
4370
4195
|
try {
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
|
|
4381
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
});
|
|
4385
|
-
}
|
|
4386
|
-
catch (e) {
|
|
4387
|
-
console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
|
|
4388
|
-
}
|
|
4196
|
+
return await this._useSession(async (result) => {
|
|
4197
|
+
const { data: { session }, } = result;
|
|
4198
|
+
if (!session || !session.refresh_token || !session.expires_at) {
|
|
4199
|
+
this._debug('#_autoRefreshTokenTick()', 'no session');
|
|
4200
|
+
return;
|
|
4201
|
+
}
|
|
4202
|
+
// session will expire in this many ticks (or has already expired if <= 0)
|
|
4203
|
+
const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / constants_1.AUTO_REFRESH_TICK_DURATION_MS);
|
|
4204
|
+
this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${constants_1.AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${constants_1.AUTO_REFRESH_TICK_THRESHOLD} ticks`);
|
|
4205
|
+
if (expiresInTicks <= constants_1.AUTO_REFRESH_TICK_THRESHOLD) {
|
|
4206
|
+
await this._callRefreshToken(session.refresh_token);
|
|
4207
|
+
}
|
|
4208
|
+
});
|
|
4389
4209
|
}
|
|
4390
|
-
|
|
4391
|
-
|
|
4210
|
+
catch (e) {
|
|
4211
|
+
console.error('Auto refresh tick failed with error. This is likely a transient error.', e);
|
|
4392
4212
|
}
|
|
4393
|
-
});
|
|
4394
|
-
}
|
|
4395
|
-
catch (e) {
|
|
4396
|
-
if (e instanceof locks_1.LockAcquireTimeoutError) {
|
|
4397
|
-
this._debug('auto refresh token tick lock not available');
|
|
4398
4213
|
}
|
|
4399
|
-
|
|
4400
|
-
|
|
4214
|
+
finally {
|
|
4215
|
+
this._debug('#_autoRefreshTokenTick()', 'end');
|
|
4401
4216
|
}
|
|
4402
|
-
}
|
|
4403
|
-
return;
|
|
4404
|
-
}
|
|
4405
|
-
// Lockless default: skip if a refresh is already in flight.
|
|
4406
|
-
// `_callRefreshToken` also dedupes via the same field; this is just a
|
|
4407
|
-
// fast-path skip to avoid an unnecessary storage read.
|
|
4408
|
-
if (this.refreshingDeferred !== null) {
|
|
4409
|
-
this._debug('#_autoRefreshTokenTick()', 'refresh already in flight, skipping');
|
|
4410
|
-
return;
|
|
4217
|
+
});
|
|
4411
4218
|
}
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
await this._useSession(async (result) => {
|
|
4416
|
-
const { data: { session }, } = result;
|
|
4417
|
-
if (!session || !session.refresh_token || !session.expires_at) {
|
|
4418
|
-
this._debug('#_autoRefreshTokenTick()', 'no session');
|
|
4419
|
-
return;
|
|
4420
|
-
}
|
|
4421
|
-
// session will expire in this many ticks (or has already expired if <= 0)
|
|
4422
|
-
const expiresInTicks = Math.floor((session.expires_at * 1000 - now) / constants_1.AUTO_REFRESH_TICK_DURATION_MS);
|
|
4423
|
-
this._debug('#_autoRefreshTokenTick()', `access token expires in ${expiresInTicks} ticks, a tick lasts ${constants_1.AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${constants_1.AUTO_REFRESH_TICK_THRESHOLD} ticks`);
|
|
4424
|
-
if (expiresInTicks <= constants_1.AUTO_REFRESH_TICK_THRESHOLD) {
|
|
4425
|
-
await this._callRefreshToken(session.refresh_token);
|
|
4426
|
-
}
|
|
4427
|
-
});
|
|
4219
|
+
catch (e) {
|
|
4220
|
+
if (e instanceof locks_1.LockAcquireTimeoutError) {
|
|
4221
|
+
this._debug('auto refresh token tick lock not available');
|
|
4428
4222
|
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4223
|
+
else {
|
|
4224
|
+
throw e;
|
|
4431
4225
|
}
|
|
4432
4226
|
}
|
|
4433
|
-
finally {
|
|
4434
|
-
this._debug('#_autoRefreshTokenTick()', 'end');
|
|
4435
|
-
}
|
|
4436
4227
|
}
|
|
4437
4228
|
/**
|
|
4438
4229
|
* Registers callbacks on the browser / platform, which in-turn run
|
|
@@ -4481,26 +4272,18 @@ class GoTrueClient {
|
|
|
4481
4272
|
if (!calledFromInitialize) {
|
|
4482
4273
|
// called when the visibility has changed, i.e. the browser
|
|
4483
4274
|
// transitioned from hidden -> visible so we need to see if the session
|
|
4484
|
-
// should be recovered
|
|
4275
|
+
// should be recovered immediately... but to do that we need to acquire
|
|
4276
|
+
// the lock first asynchronously
|
|
4485
4277
|
await this.initializePromise;
|
|
4486
|
-
|
|
4487
|
-
// TODO(v3): remove legacy lock path
|
|
4488
|
-
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4489
|
-
if (document.visibilityState !== 'visible') {
|
|
4490
|
-
this._debug(methodName, 'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting');
|
|
4491
|
-
return;
|
|
4492
|
-
}
|
|
4493
|
-
await this._recoverAndRefresh();
|
|
4494
|
-
});
|
|
4495
|
-
}
|
|
4496
|
-
else {
|
|
4278
|
+
await this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4497
4279
|
if (document.visibilityState !== 'visible') {
|
|
4498
|
-
this._debug(methodName, 'visibilityState is no longer visible,
|
|
4280
|
+
this._debug(methodName, 'acquired the lock to recover the session, but the browser visibilityState is no longer visible, aborting');
|
|
4281
|
+
// visibility has changed while waiting for the lock, abort
|
|
4499
4282
|
return;
|
|
4500
4283
|
}
|
|
4501
4284
|
// recover the session
|
|
4502
4285
|
await this._recoverAndRefresh();
|
|
4503
|
-
}
|
|
4286
|
+
});
|
|
4504
4287
|
}
|
|
4505
4288
|
}
|
|
4506
4289
|
else if (document.visibilityState === 'hidden') {
|
|
@@ -4596,7 +4379,7 @@ class GoTrueClient {
|
|
|
4596
4379
|
}
|
|
4597
4380
|
}
|
|
4598
4381
|
async _verify(params) {
|
|
4599
|
-
|
|
4382
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4600
4383
|
try {
|
|
4601
4384
|
return await this._useSession(async (result) => {
|
|
4602
4385
|
var _a;
|
|
@@ -4630,15 +4413,10 @@ class GoTrueClient {
|
|
|
4630
4413
|
}
|
|
4631
4414
|
throw error;
|
|
4632
4415
|
}
|
|
4633
|
-
};
|
|
4634
|
-
if (this.lock != null) {
|
|
4635
|
-
// TODO(v3): remove legacy lock path
|
|
4636
|
-
return this._acquireLock(this.lockAcquireTimeout, run);
|
|
4637
|
-
}
|
|
4638
|
-
return run();
|
|
4416
|
+
});
|
|
4639
4417
|
}
|
|
4640
4418
|
async _challenge(params) {
|
|
4641
|
-
|
|
4419
|
+
return this._acquireLock(this.lockAcquireTimeout, async () => {
|
|
4642
4420
|
try {
|
|
4643
4421
|
return await this._useSession(async (result) => {
|
|
4644
4422
|
var _a;
|
|
@@ -4678,17 +4456,14 @@ class GoTrueClient {
|
|
|
4678
4456
|
}
|
|
4679
4457
|
throw error;
|
|
4680
4458
|
}
|
|
4681
|
-
};
|
|
4682
|
-
if (this.lock != null) {
|
|
4683
|
-
// TODO(v3): remove legacy lock path
|
|
4684
|
-
return this._acquireLock(this.lockAcquireTimeout, run);
|
|
4685
|
-
}
|
|
4686
|
-
return run();
|
|
4459
|
+
});
|
|
4687
4460
|
}
|
|
4688
4461
|
/**
|
|
4689
4462
|
* {@see GoTrueMFAApi#challengeAndVerify}
|
|
4690
4463
|
*/
|
|
4691
4464
|
async _challengeAndVerify(params) {
|
|
4465
|
+
// both _challenge and _verify independently acquire the lock, so no need
|
|
4466
|
+
// to acquire it here
|
|
4692
4467
|
const { data: challengeData, error: challengeError } = await this._challenge({
|
|
4693
4468
|
factorId: params.factorId,
|
|
4694
4469
|
});
|
|
@@ -4706,6 +4481,7 @@ class GoTrueClient {
|
|
|
4706
4481
|
*/
|
|
4707
4482
|
async _listFactors() {
|
|
4708
4483
|
var _a;
|
|
4484
|
+
// use #getUser instead of #_getUser as the former acquires a lock
|
|
4709
4485
|
const { data: { user }, error: userError, } = await this.getUser();
|
|
4710
4486
|
if (userError) {
|
|
4711
4487
|
return { data: null, error: userError };
|
|
@@ -5054,8 +4830,14 @@ class GoTrueClient {
|
|
|
5054
4830
|
}
|
|
5055
4831
|
const { header, payload, signature, raw: { header: rawHeader, payload: rawPayload }, } = (0, helpers_1.decodeJWT)(token);
|
|
5056
4832
|
if (!(options === null || options === void 0 ? void 0 : options.allowExpired)) {
|
|
5057
|
-
// Reject expired JWTs should only happen if jwt argument was passed
|
|
5058
|
-
|
|
4833
|
+
// Reject expired JWTs should only happen if jwt argument was passed.
|
|
4834
|
+
// Rethrow as AuthInvalidJwtError so the outer catch converts it to { data, error }.
|
|
4835
|
+
try {
|
|
4836
|
+
(0, helpers_1.validateExp)(payload.exp);
|
|
4837
|
+
}
|
|
4838
|
+
catch (e) {
|
|
4839
|
+
throw new errors_1.AuthInvalidJwtError(e instanceof Error ? e.message : 'JWT validation failed');
|
|
4840
|
+
}
|
|
5059
4841
|
}
|
|
5060
4842
|
const signingKey = !header.alg ||
|
|
5061
4843
|
header.alg.startsWith('HS') ||
|