@adventurelabs/scout-core 2.0.8 → 2.0.9

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/README.md CHANGED
@@ -35,6 +35,8 @@ barrel.
35
35
  `ScoutRefreshProvider` accepts an existing browser Supabase client and otherwise
36
36
  creates one stable client. Its automatic refresh uses a fresh IndexedDB cache
37
37
  without another request; manual and reconnect refreshes still query the API.
38
+ Returning to a visible tab revalidates herd data and refreshes JWT mints that
39
+ are near expiry, covering browsers that suspend background timers.
38
40
  Offline PWAs can pass a persisted `offlineUserId` to restore that user's herd
39
41
  modules and JWT mints before online authentication is revalidated. Cached mints
40
42
  remain available when expired; pass `false` as the fourth argument to
@@ -355,14 +355,25 @@ export function useScoutRefresh(options = {}, identityOptions = {}) {
355
355
  useEffect(() => {
356
356
  if (!autoRefresh || typeof window === "undefined")
357
357
  return;
358
- const handleOnline = () => {
358
+ const revalidate = () => {
359
+ if (!navigator.onLine)
360
+ return;
359
361
  const now = Date.now();
360
362
  if (now - lastQueryAtRef.current >= onlineRefetchMinIntervalMs) {
361
363
  void handleRefresh({ force: true });
362
364
  }
363
365
  };
364
- window.addEventListener("online", handleOnline);
365
- return () => window.removeEventListener("online", handleOnline);
366
+ const handleVisibilityChange = () => {
367
+ if (document.visibilityState === "visible") {
368
+ revalidate();
369
+ }
370
+ };
371
+ window.addEventListener("online", revalidate);
372
+ document.addEventListener("visibilitychange", handleVisibilityChange);
373
+ return () => {
374
+ window.removeEventListener("online", revalidate);
375
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
376
+ };
366
377
  }, [autoRefresh, handleRefresh, onlineRefetchMinIntervalMs]);
367
378
  const clearCache = useCallback(async () => {
368
379
  try {
@@ -82,6 +82,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
82
82
  const [mint, setMint] = useState(null);
83
83
  const [inFlight, setInFlight] = useState(false);
84
84
  const [error, setError] = useState(null);
85
+ const [offline, setOffline] = useState(isBrowserOffline);
85
86
  const refreshIdRef = useRef(0);
86
87
  const authUserIdRef = useRef(null);
87
88
  const cacheHydrationIdRef = useRef(0);
@@ -101,7 +102,7 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
101
102
  console.error("[ScoutRefreshProvider] User validation callback failed:", validationError);
102
103
  }
103
104
  }, [onUserValidated]);
104
- const status = useMemo(() => derive_jwt_mint_status(enabled, mint, inFlight, error), [enabled, mint, inFlight, error]);
105
+ const status = useMemo(() => derive_jwt_mint_status(enabled, mint, inFlight, error, offline), [enabled, mint, inFlight, error, offline]);
105
106
  const refreshMint = useCallback(async (force = true) => {
106
107
  if (!enabled || isBrowserOffline())
107
108
  return;
@@ -312,15 +313,36 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
312
313
  }
313
314
  });
314
315
  const handleOnline = () => {
316
+ setOffline(false);
315
317
  void refreshMint(false);
316
318
  };
319
+ const handleOffline = () => {
320
+ setOffline(true);
321
+ };
322
+ const handleVisibilityChange = () => {
323
+ if (document.visibilityState !== "visible")
324
+ return;
325
+ const currentlyOffline = isBrowserOffline();
326
+ setOffline(currentlyOffline);
327
+ if (currentlyOffline)
328
+ return;
329
+ const currentMint = mintRef.current;
330
+ if (!currentMint ||
331
+ shouldRefreshMint(currentMint, ttlSec, refreshBeforeExpirySec)) {
332
+ void refreshMint(false);
333
+ }
334
+ };
317
335
  window.addEventListener("online", handleOnline);
336
+ window.addEventListener("offline", handleOffline);
337
+ document.addEventListener("visibilitychange", handleVisibilityChange);
318
338
  return () => {
319
339
  cancelled = true;
320
340
  cacheHydrationIdRef.current++;
321
341
  refreshIdRef.current++;
322
342
  unsubscribeAuth();
323
343
  window.removeEventListener("online", handleOnline);
344
+ window.removeEventListener("offline", handleOffline);
345
+ document.removeEventListener("visibilitychange", handleVisibilityChange);
324
346
  };
325
347
  }, [
326
348
  enabled,
@@ -328,6 +350,8 @@ function useJwtMintLifecycle({ enabled, supabase, identity, cacheKey, ttlSec, re
328
350
  offlineUserId,
329
351
  validatedUserId,
330
352
  cacheKey,
353
+ ttlSec,
354
+ refreshBeforeExpirySec,
331
355
  refreshMint,
332
356
  updateMint,
333
357
  ]);
@@ -7,4 +7,5 @@ export declare enum EnumJwtMintStatus {
7
7
  }
8
8
  export declare function derive_jwt_mint_status(enabled: boolean, mint: {
9
9
  token: string;
10
- } | null | undefined, inFlight: boolean, error: string | null): EnumJwtMintStatus;
10
+ exp?: number;
11
+ } | null | undefined, inFlight: boolean, error: string | null, allowExpired?: boolean): EnumJwtMintStatus;
@@ -6,17 +6,18 @@ export var EnumJwtMintStatus;
6
6
  EnumJwtMintStatus["READY"] = "ready";
7
7
  EnumJwtMintStatus["ERROR"] = "error";
8
8
  })(EnumJwtMintStatus || (EnumJwtMintStatus = {}));
9
- export function derive_jwt_mint_status(enabled, mint, inFlight, error) {
9
+ export function derive_jwt_mint_status(enabled, mint, inFlight, error, allowExpired = false) {
10
10
  if (!enabled) {
11
11
  return EnumJwtMintStatus.IDLE;
12
12
  }
13
- if (inFlight && !mint?.token) {
14
- return EnumJwtMintStatus.LOADING;
13
+ const expired = mint?.exp != null && mint.exp <= Math.floor(Date.now() / 1000);
14
+ const hasUsableMint = Boolean(mint?.token) && (allowExpired || !expired);
15
+ if (inFlight) {
16
+ return hasUsableMint
17
+ ? EnumJwtMintStatus.REFRESHING
18
+ : EnumJwtMintStatus.LOADING;
15
19
  }
16
- if (inFlight && mint?.token) {
17
- return EnumJwtMintStatus.REFRESHING;
18
- }
19
- if (mint?.token) {
20
+ if (hasUsableMint) {
20
21
  return EnumJwtMintStatus.READY;
21
22
  }
22
23
  if (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adventurelabs/scout-core",
3
- "version": "2.0.8",
3
+ "version": "2.0.9",
4
4
  "description": "Core utilities and helpers for Adventure Labs Scout applications",
5
5
  "exports": {
6
6
  "./client": {