@adventurelabs/scout-core 2.0.9 → 2.0.10

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);
@@ -288,27 +296,47 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
288
296
  }
289
297
  };
290
298
  void initializeMint();
299
+ const refreshCurrentMintIfDue = () => {
300
+ const currentMint = mintRef.current;
301
+ if (!currentMint ||
302
+ shouldRefreshMint(currentMint, ttlSec, refreshBeforeExpirySec)) {
303
+ void refreshMint(false);
304
+ }
305
+ };
291
306
  const unsubscribeAuth = subscribeToSupabaseAuth(supabase, (event, session) => {
292
307
  if (event === "SIGNED_OUT") {
293
308
  signedOutRef.current = true;
294
309
  authUserIdRef.current = null;
295
310
  cacheHydrationIdRef.current++;
296
311
  refreshIdRef.current++;
312
+ refreshInProgressRef.current = false;
297
313
  updateMint(null);
298
314
  setError(null);
299
315
  setInFlight(false);
300
316
  return;
301
317
  }
302
318
  if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
319
+ const wasSignedOut = signedOutRef.current;
303
320
  signedOutRef.current = false;
304
- refreshIdRef.current++;
305
- setInFlight(false);
306
321
  const userId = session?.user?.id;
307
- if (userId) {
322
+ if (!userId) {
323
+ refreshCurrentMintIfDue();
324
+ return;
325
+ }
326
+ const knownUserId = authUserIdRef.current;
327
+ const userChanged = wasSignedOut ||
328
+ (knownUserId !== null && knownUserId !== userId);
329
+ if (userChanged) {
330
+ cacheHydrationIdRef.current++;
331
+ refreshIdRef.current++;
332
+ refreshInProgressRef.current = false;
308
333
  void initializeMint(userId, true);
309
334
  }
335
+ else if (knownUserId === null || !mintRef.current) {
336
+ void initializeMint(userId);
337
+ }
310
338
  else {
311
- void refreshMint();
339
+ refreshCurrentMintIfDue();
312
340
  }
313
341
  }
314
342
  });
@@ -326,11 +354,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
326
354
  setOffline(currentlyOffline);
327
355
  if (currentlyOffline)
328
356
  return;
329
- const currentMint = mintRef.current;
330
- if (!currentMint ||
331
- shouldRefreshMint(currentMint, ttlSec, refreshBeforeExpirySec)) {
332
- void refreshMint(false);
333
- }
357
+ refreshCurrentMintIfDue();
334
358
  };
335
359
  window.addEventListener("online", handleOnline);
336
360
  window.addEventListener("offline", handleOffline);
@@ -339,6 +363,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
339
363
  cancelled = true;
340
364
  cacheHydrationIdRef.current++;
341
365
  refreshIdRef.current++;
366
+ refreshInProgressRef.current = false;
342
367
  unsubscribeAuth();
343
368
  window.removeEventListener("online", handleOnline);
344
369
  window.removeEventListener("offline", handleOffline);
@@ -364,7 +389,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
364
389
  const refreshAtSec = Math.max(expSec - refreshBeforeExpirySec, mint.iat + 1);
365
390
  const delayMs = Math.max((refreshAtSec - nowSec) * 1000, 1000);
366
391
  const timer = setTimeout(() => {
367
- void refreshMint();
392
+ void refreshMint(false);
368
393
  }, delayMs);
369
394
  return () => clearTimeout(timer);
370
395
  }, [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.10",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "exports": {
6
6
  "./client": {