@adventurelabs/scout-core 2.0.9 → 2.0.11

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.
@@ -60,6 +60,7 @@ export function useScoutRefresh(options = {}, identityOptions = {}) {
60
60
  const { validatedUserId, onUserValidated } = identityOptions;
61
61
  const dispatch = useAppDispatch();
62
62
  const refreshIdRef = useRef(0);
63
+ const refreshInProgressIdRef = useRef(null);
63
64
  const activeUserIdRef = useRef(null);
64
65
  const validatedUserIdRef = useRef(null);
65
66
  const providedValidatedUserIdRef = useRef(validatedUserId);
@@ -91,7 +92,10 @@ export function useScoutRefresh(options = {}, identityOptions = {}) {
91
92
  }, [dispatch]);
92
93
  const cacheIdentityRef = useRef({ supabase, offlineUserId });
93
94
  const handleRefresh = useCallback(async ({ force = true } = {}) => {
95
+ if (refreshInProgressIdRef.current !== null && !force)
96
+ return;
94
97
  const refreshId = ++refreshIdRef.current;
98
+ refreshInProgressIdRef.current = refreshId;
95
99
  const isCurrent = () => refreshIdRef.current === refreshId;
96
100
  const startTime = Date.now();
97
101
  const isOffline = typeof navigator === "undefined" || !navigator.onLine;
@@ -268,6 +272,9 @@ export function useScoutRefresh(options = {}, identityOptions = {}) {
268
272
  }
269
273
  }
270
274
  finally {
275
+ if (refreshInProgressIdRef.current === refreshId) {
276
+ refreshInProgressIdRef.current = null;
277
+ }
271
278
  if (isCurrent()) {
272
279
  lastQueryAtRef.current = Date.now();
273
280
  }
@@ -84,6 +84,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
84
84
  const [error, setError] = useState(null);
85
85
  const [offline, setOffline] = useState(isBrowserOffline);
86
86
  const refreshIdRef = useRef(0);
87
+ const refreshInProgressRef = useRef(false);
87
88
  const authUserIdRef = useRef(null);
88
89
  const cacheHydrationIdRef = useRef(0);
89
90
  const signedOutRef = useRef(false);
@@ -106,11 +107,12 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
106
107
  const refreshMint = useCallback(async (force = true) => {
107
108
  if (!enabled || isBrowserOffline())
108
109
  return;
110
+ if (refreshInProgressRef.current && !force)
111
+ return;
112
+ refreshInProgressRef.current = true;
109
113
  cacheHydrationIdRef.current++;
110
114
  const refreshId = ++refreshIdRef.current;
111
115
  const isCurrent = () => refreshIdRef.current === refreshId;
112
- setInFlight(true);
113
- setError(null);
114
116
  try {
115
117
  const { data, error: authError } = await supabase.auth.getUser();
116
118
  if (authError) {
@@ -143,6 +145,8 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
143
145
  !shouldRefreshMint(currentMint, ttlSec, refreshBeforeExpirySec)) {
144
146
  return;
145
147
  }
148
+ setInFlight(true);
149
+ setError(null);
146
150
  const mintResponse = await mintToken(supabase);
147
151
  if (!isCurrent() || authUserIdRef.current !== userId)
148
152
  return;
@@ -177,6 +181,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
177
181
  }
178
182
  finally {
179
183
  if (isCurrent()) {
184
+ refreshInProgressRef.current = false;
180
185
  setInFlight(false);
181
186
  }
182
187
  }
@@ -202,10 +207,12 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
202
207
  authUserIdRef.current = null;
203
208
  cacheHydrationIdRef.current++;
204
209
  refreshIdRef.current++;
210
+ refreshInProgressRef.current = false;
205
211
  updateMint(null);
206
212
  setInFlight(false);
207
213
  }
208
214
  if (!enabled) {
215
+ refreshInProgressRef.current = false;
209
216
  updateMint(null);
210
217
  setError(null);
211
218
  setInFlight(false);
@@ -220,6 +227,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
220
227
  authUserIdRef.current = null;
221
228
  cacheHydrationIdRef.current++;
222
229
  refreshIdRef.current++;
230
+ refreshInProgressRef.current = false;
223
231
  updateMint(null);
224
232
  setError(null);
225
233
  setInFlight(false);
@@ -274,7 +282,12 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
274
282
  authUserIdRef.current !== userId) {
275
283
  return;
276
284
  }
277
- updateMint(cached.data);
285
+ if (cached.data && !cached.isStale) {
286
+ updateMint(cached.data);
287
+ }
288
+ else if (!mintRef.current) {
289
+ updateMint(cached.data);
290
+ }
278
291
  }
279
292
  catch (cacheError) {
280
293
  console.warn(`[ScoutRefreshProvider] ${cacheKey} cache load failed:`, cacheError);
@@ -288,27 +301,47 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
288
301
  }
289
302
  };
290
303
  void initializeMint();
304
+ const refreshCurrentMintIfDue = () => {
305
+ const currentMint = mintRef.current;
306
+ if (!currentMint ||
307
+ shouldRefreshMint(currentMint, ttlSec, refreshBeforeExpirySec)) {
308
+ void refreshMint(false);
309
+ }
310
+ };
291
311
  const unsubscribeAuth = subscribeToSupabaseAuth(supabase, (event, session) => {
292
312
  if (event === "SIGNED_OUT") {
293
313
  signedOutRef.current = true;
294
314
  authUserIdRef.current = null;
295
315
  cacheHydrationIdRef.current++;
296
316
  refreshIdRef.current++;
317
+ refreshInProgressRef.current = false;
297
318
  updateMint(null);
298
319
  setError(null);
299
320
  setInFlight(false);
300
321
  return;
301
322
  }
302
323
  if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
324
+ const wasSignedOut = signedOutRef.current;
303
325
  signedOutRef.current = false;
304
- refreshIdRef.current++;
305
- setInFlight(false);
306
326
  const userId = session?.user?.id;
307
- if (userId) {
327
+ if (!userId) {
328
+ refreshCurrentMintIfDue();
329
+ return;
330
+ }
331
+ const knownUserId = authUserIdRef.current;
332
+ const userChanged = wasSignedOut ||
333
+ (knownUserId !== null && knownUserId !== userId);
334
+ if (userChanged) {
335
+ cacheHydrationIdRef.current++;
336
+ refreshIdRef.current++;
337
+ refreshInProgressRef.current = false;
308
338
  void initializeMint(userId, true);
309
339
  }
340
+ else if (knownUserId === null || !mintRef.current) {
341
+ void initializeMint(userId);
342
+ }
310
343
  else {
311
- void refreshMint();
344
+ refreshCurrentMintIfDue();
312
345
  }
313
346
  }
314
347
  });
@@ -326,11 +359,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
326
359
  setOffline(currentlyOffline);
327
360
  if (currentlyOffline)
328
361
  return;
329
- const currentMint = mintRef.current;
330
- if (!currentMint ||
331
- shouldRefreshMint(currentMint, ttlSec, refreshBeforeExpirySec)) {
332
- void refreshMint(false);
333
- }
362
+ refreshCurrentMintIfDue();
334
363
  };
335
364
  window.addEventListener("online", handleOnline);
336
365
  window.addEventListener("offline", handleOffline);
@@ -339,6 +368,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
339
368
  cancelled = true;
340
369
  cacheHydrationIdRef.current++;
341
370
  refreshIdRef.current++;
371
+ refreshInProgressRef.current = false;
342
372
  unsubscribeAuth();
343
373
  window.removeEventListener("online", handleOnline);
344
374
  window.removeEventListener("offline", handleOffline);
@@ -364,7 +394,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
364
394
  const refreshAtSec = Math.max(expSec - refreshBeforeExpirySec, mint.iat + 1);
365
395
  const delayMs = Math.max((refreshAtSec - nowSec) * 1000, 1000);
366
396
  const timer = setTimeout(() => {
367
- void refreshMint();
397
+ void refreshMint(false);
368
398
  }, delayMs);
369
399
  return () => clearTimeout(timer);
370
400
  }, [enabled, mint, ttlSec, refreshBeforeExpirySec, refreshMint]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "2.0.9",
3
+ "version": "2.0.11",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "exports": {
6
6
  "./client": {