@adventurelabs/scout-core 2.0.1 → 2.0.2

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.
@@ -10,6 +10,14 @@ const TABLE_MARKERS = [
10
10
  ["operating_context_equipment_requirements", "equipment_item_id"],
11
11
  ["operating_context_regions", "is_exclusion"],
12
12
  ["operating_context_risks", "probability_before"],
13
+ ["contact_types", "num_required"],
14
+ ["operating_context_point_of_interest_types", "system_name"],
15
+ ["operating_permissions", "description"],
16
+ ["compliance_resource_types", "system_name"],
17
+ ["compliance_resources", "compliance_resource_type_id"],
18
+ ["operating_context_points_of_interest", "point_of_interest_type_id"],
19
+ ["operating_context_contacts", "contact_id"],
20
+ ["herd_operating_permissions", "operating_permission_id"],
13
21
  ];
14
22
  function discriminate(update) {
15
23
  if (!update)
@@ -1,3 +1,4 @@
1
+ import { clearScoutClientState } from "../helpers/cache";
1
2
  import type { SupabaseClient } from "@supabase/supabase-js";
2
3
  import type { Database } from "../types/supabase";
3
4
  export interface UseScoutRefreshOptions {
@@ -14,10 +15,10 @@ export interface ScoutRefreshRequest {
14
15
  /**
15
16
  * Refreshes Scout state from cache and the API.
16
17
  *
17
- * Manual refreshes force an API request by default. Automatic mount refreshes
18
- * finish from a fresh cache without requesting the API.
18
+ * Automatic refreshes hydrate from cache before revalidating online.
19
19
  */
20
20
  export declare function useScoutRefresh(options?: UseScoutRefreshOptions): {
21
21
  handleRefresh: ({ force }?: ScoutRefreshRequest) => Promise<void>;
22
22
  clearCache: () => Promise<void>;
23
+ clearScoutClientState: typeof clearScoutClientState;
23
24
  };
@@ -1,28 +1,69 @@
1
1
  "use client";
2
2
  import { useEffect, useCallback, useRef, useMemo, startTransition, } from "react";
3
3
  import { useAppDispatch } from "../store/hooks";
4
- import { EnumScoutStateStatus, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiServerProcessingDuration, setHerdModulesApiTotalRequestDuration, setUserApiDuration, setDataProcessingDuration, setCacheLoadDuration, setUser, setDataSource, setDataSourceInfo, } from "../store/scout";
4
+ import { EnumScoutStateStatus, setHerdModules, setStatus, setHerdModulesLoadingState, setHerdModulesLoadedInMs, setHerdModulesApiServerProcessingDuration, setHerdModulesApiTotalRequestDuration, setUserApiDuration, setDataProcessingDuration, setCacheLoadDuration, setUser, setDataSource, setDataSourceInfo, resetScoutState, } from "../store/scout";
5
+ import { scoutApi } from "../store/api";
5
6
  import { EnumHerdModulesLoadingState, } from "../types/herd_module";
6
7
  import { load_herd_modules_query } from "../helpers/herd_modules.queries";
7
- import { scoutCache } from "../helpers/cache";
8
+ import { clearScoutClientState, scoutCache } from "../helpers/cache";
8
9
  import { EnumDataSource } from "../types/data_source";
9
10
  import { EnumWebResponse } from "../types/requests";
10
11
  import { createScoutBrowserClient } from "../supabase/client";
12
+ import { subscribeToSupabaseAuth } from "../helpers/supabase_auth";
11
13
  function dispatchInTransition(dispatch, action) {
12
14
  startTransition(() => {
13
15
  dispatch(action);
14
16
  });
15
17
  }
18
+ async function loadAuthenticatedUser(supabase, offline) {
19
+ const startTime = Date.now();
20
+ if (offline) {
21
+ const { data: { session }, } = await supabase.auth.getSession();
22
+ if (!session?.user) {
23
+ throw new Error("No authenticated user");
24
+ }
25
+ return { user: session.user, duration: Date.now() - startTime };
26
+ }
27
+ let lastError = new Error("Unable to load authenticated user");
28
+ for (let attempt = 0; attempt < 2; attempt++) {
29
+ try {
30
+ const { data } = await supabase.auth.getUser();
31
+ if (!data.user) {
32
+ throw new Error("Invalid user response");
33
+ }
34
+ return { user: data.user, duration: Date.now() - startTime };
35
+ }
36
+ catch (error) {
37
+ lastError =
38
+ error instanceof Error ? error : new Error("User request failed");
39
+ }
40
+ }
41
+ throw lastError;
42
+ }
43
+ async function recoverScoutCache(error, operation, userId, retry) {
44
+ if (!(error instanceof Error) ||
45
+ (!error.message.includes("object store") &&
46
+ !error.message.includes("NotFoundError"))) {
47
+ return;
48
+ }
49
+ try {
50
+ await scoutCache.resetDatabase(userId);
51
+ await retry?.();
52
+ }
53
+ catch (resetError) {
54
+ console.error(`[useScoutRefresh] ${operation} reset and retry failed:`, resetError);
55
+ }
56
+ }
16
57
  /**
17
58
  * Refreshes Scout state from cache and the API.
18
59
  *
19
- * Manual refreshes force an API request by default. Automatic mount refreshes
20
- * finish from a fresh cache without requesting the API.
60
+ * Automatic refreshes hydrate from cache before revalidating online.
21
61
  */
22
62
  export function useScoutRefresh(options = {}) {
23
63
  const { supabase: supabaseOption, autoRefresh = true, onRefreshComplete, cacheFirst = true, cacheTtlMs = 24 * 60 * 60 * 1000, onlineRefetchMinIntervalMs = 15 * 1000, } = options;
24
64
  const dispatch = useAppDispatch();
25
- const refreshInProgressRef = useRef(false);
65
+ const refreshIdRef = useRef(0);
66
+ const activeUserIdRef = useRef(null);
26
67
  const lastQueryAtRef = useRef(0);
27
68
  const onRefreshCompleteRef = useRef(onRefreshComplete);
28
69
  useEffect(() => {
@@ -34,62 +75,56 @@ export function useScoutRefresh(options = {}) {
34
75
  }
35
76
  return createScoutBrowserClient();
36
77
  }, [supabaseOption]);
37
- const handleIndexedDbError = useCallback(async (error, operation, retryFn) => {
38
- if (error instanceof Error &&
39
- (error.message.includes("object store") ||
40
- error.message.includes("NotFoundError"))) {
41
- try {
42
- await scoutCache.resetDatabase();
43
- if (retryFn) {
44
- await retryFn();
45
- }
46
- }
47
- catch (resetError) {
48
- console.error(`[useScoutRefresh] ${operation} reset and retry failed:`, resetError);
49
- }
50
- }
51
- }, []);
78
+ const resetClientState = useCallback(() => {
79
+ dispatch(resetScoutState());
80
+ dispatch(scoutApi.util.resetApiState());
81
+ }, [dispatch]);
52
82
  const handleRefresh = useCallback(async ({ force = true } = {}) => {
53
- if (refreshInProgressRef.current) {
54
- return;
55
- }
56
- refreshInProgressRef.current = true;
83
+ const refreshId = ++refreshIdRef.current;
84
+ const isCurrent = () => refreshIdRef.current === refreshId;
57
85
  const startTime = Date.now();
58
86
  const isOffline = typeof navigator === "undefined" || !navigator.onLine;
59
- const fetchUserWithOneRetry = async () => {
60
- const startTotal = Date.now();
61
- let lastError;
62
- for (let attempt = 0; attempt < 2; attempt++) {
63
- try {
64
- const { data } = await supabase.auth.getUser();
65
- if (!data.user) {
66
- throw new Error("Invalid user response");
67
- }
68
- const totalMs = Date.now() - startTotal;
69
- dispatch(setUserApiDuration(totalMs));
70
- dispatchInTransition(dispatch, setUser(data.user));
71
- return data.user;
72
- }
73
- catch (e) {
74
- lastError = e;
75
- }
76
- }
77
- throw lastError;
87
+ let cachedHerdModules = null;
88
+ const complete = (loadingState) => {
89
+ if (!isCurrent())
90
+ return;
91
+ dispatch(setHerdModulesLoadingState(loadingState));
92
+ dispatch(setHerdModulesLoadedInMs(Date.now() - startTime));
93
+ dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
94
+ onRefreshCompleteRef.current?.();
78
95
  };
79
96
  try {
80
97
  dispatch(setStatus(EnumScoutStateStatus.LOADING));
81
98
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.LOADING));
82
- let cachedHerdModules = null;
83
- let cachedContentHash;
84
- if (cacheFirst) {
99
+ const { user, duration } = await loadAuthenticatedUser(supabase, isOffline);
100
+ if (!isCurrent())
101
+ return;
102
+ const userId = user.id;
103
+ if (activeUserIdRef.current &&
104
+ activeUserIdRef.current !== userId) {
105
+ resetClientState();
106
+ }
107
+ activeUserIdRef.current = userId;
108
+ dispatch(setUserApiDuration(duration));
109
+ dispatchInTransition(dispatch, setUser(user));
110
+ let cacheReady = false;
111
+ try {
112
+ await scoutCache.setScope(userId);
113
+ cacheReady = true;
114
+ }
115
+ catch (cacheError) {
116
+ console.warn("[useScoutRefresh] Cache unavailable:", cacheError);
117
+ }
118
+ if (cacheFirst && cacheReady && (!force || isOffline)) {
85
119
  const cacheStartTime = Date.now();
86
120
  try {
87
- const cacheResult = await scoutCache.getHerdModules();
121
+ const cacheResult = await scoutCache.getHerdModules(userId);
122
+ if (!isCurrent() || activeUserIdRef.current !== userId)
123
+ return;
88
124
  const cacheLoadDuration = Date.now() - cacheStartTime;
89
125
  dispatch(setCacheLoadDuration(cacheLoadDuration));
90
- if (cacheResult.data && cacheResult.data.length > 0) {
126
+ if (cacheResult.data !== null) {
91
127
  cachedHerdModules = cacheResult.data;
92
- cachedContentHash = cacheResult.metadata?.etag;
93
128
  dispatch(setDataSource(EnumDataSource.CACHE));
94
129
  dispatch(setDataSourceInfo({
95
130
  source: EnumDataSource.CACHE,
@@ -99,68 +134,35 @@ export function useScoutRefresh(options = {}) {
99
134
  }));
100
135
  dispatchInTransition(dispatch, setHerdModules({
101
136
  modules: cachedHerdModules,
102
- contentHash: cachedContentHash,
103
137
  }));
104
138
  dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
105
- if (!cacheResult.isStale && !force) {
106
- const totalDuration = Date.now() - startTime;
107
- dispatch(setHerdModulesLoadedInMs(totalDuration));
108
- dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
109
- onRefreshCompleteRef.current?.();
110
- return;
111
- }
112
139
  }
113
140
  }
114
141
  catch (cacheError) {
115
142
  console.warn("[useScoutRefresh] Cache load failed:", cacheError);
116
- await handleIndexedDbError(cacheError, "cache load");
143
+ await recoverScoutCache(cacheError, "cache load", userId);
117
144
  }
118
145
  }
119
146
  if (isOffline) {
120
- if (cachedHerdModules && cachedHerdModules.length > 0) {
121
- dispatch(setDataSource(EnumDataSource.CACHE));
122
- dispatch(setDataSourceInfo({
123
- source: EnumDataSource.CACHE,
124
- timestamp: Date.now(),
125
- }));
126
- dispatchInTransition(dispatch, setHerdModules({
127
- modules: cachedHerdModules,
128
- contentHash: cachedContentHash,
129
- }));
130
- dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
131
- }
132
- else {
133
- dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.UNSUCCESSFULLY_LOADED));
147
+ if (cachedHerdModules === null) {
134
148
  dispatch(setDataSource(EnumDataSource.UNKNOWN));
135
149
  dispatch(setDataSourceInfo({
136
150
  source: EnumDataSource.UNKNOWN,
137
151
  timestamp: Date.now(),
138
152
  }));
139
153
  }
140
- dispatch(setHerdModulesLoadedInMs(Date.now() - startTime));
141
- dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
142
- onRefreshCompleteRef.current?.();
154
+ complete(cachedHerdModules !== null
155
+ ? EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED
156
+ : EnumHerdModulesLoadingState.UNSUCCESSFULLY_LOADED);
143
157
  return;
144
158
  }
145
- const userPromise = fetchUserWithOneRetry();
146
- let herdModulesResult;
147
- try {
148
- herdModulesResult = await (async () => {
149
- const start = Date.now();
150
- const result = await load_herd_modules_query(supabase);
151
- const totalDuration = Date.now() - start;
152
- const serverDuration = result.server_processing_time_ms || totalDuration;
153
- return { result, totalDuration, serverDuration };
154
- })();
155
- }
156
- catch (e) {
157
- await userPromise.catch(() => { });
158
- throw e;
159
- }
160
- await userPromise;
161
- const herdModulesResponse = herdModulesResult.result;
162
- const herdModulesServerDuration = herdModulesResult.serverDuration;
163
- const herdModulesTotalDuration = herdModulesResult.totalDuration;
159
+ const queryStart = Date.now();
160
+ const herdModulesResponse = await load_herd_modules_query(supabase);
161
+ if (!isCurrent() || activeUserIdRef.current !== userId)
162
+ return;
163
+ const herdModulesTotalDuration = Date.now() - queryStart;
164
+ const herdModulesServerDuration = herdModulesResponse.server_processing_time_ms ||
165
+ herdModulesTotalDuration;
164
166
  dispatch(setHerdModulesApiServerProcessingDuration(herdModulesServerDuration));
165
167
  dispatch(setHerdModulesApiTotalRequestDuration(herdModulesTotalDuration));
166
168
  if (herdModulesResponse.status !== EnumWebResponse.SUCCESS ||
@@ -169,56 +171,90 @@ export function useScoutRefresh(options = {}) {
169
171
  }
170
172
  const freshHerdModules = herdModulesResponse.data;
171
173
  const freshContentHash = herdModulesResponse.content_hash;
174
+ if (cacheReady) {
175
+ try {
176
+ await scoutCache.setHerdModules(freshHerdModules, cacheTtlMs, freshContentHash, userId);
177
+ }
178
+ catch (cacheError) {
179
+ console.warn("[useScoutRefresh] Cache save failed:", cacheError);
180
+ await recoverScoutCache(cacheError, "cache save", userId, async () => {
181
+ await scoutCache.setHerdModules(freshHerdModules, cacheTtlMs, freshContentHash, userId);
182
+ });
183
+ }
184
+ }
185
+ if (!isCurrent() || activeUserIdRef.current !== userId)
186
+ return;
187
+ const dataProcessingStartTime = Date.now();
172
188
  dispatch(setDataSource(EnumDataSource.DATABASE));
173
189
  dispatch(setDataSourceInfo({
174
190
  source: EnumDataSource.DATABASE,
175
191
  timestamp: Date.now(),
176
192
  }));
177
- try {
178
- await scoutCache.setHerdModules(freshHerdModules, cacheTtlMs, freshContentHash);
179
- }
180
- catch (cacheError) {
181
- console.warn("[useScoutRefresh] Cache save failed:", cacheError);
182
- await handleIndexedDbError(cacheError, "cache save", async () => {
183
- await scoutCache.setHerdModules(freshHerdModules, cacheTtlMs, freshContentHash);
184
- });
185
- }
186
- const dataProcessingStartTime = Date.now();
187
193
  dispatchInTransition(dispatch, setHerdModules({
188
194
  modules: freshHerdModules,
189
195
  contentHash: freshContentHash,
190
196
  }));
191
- dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
192
197
  const dataProcessingDuration = Date.now() - dataProcessingStartTime;
193
198
  dispatch(setDataProcessingDuration(dataProcessingDuration));
194
- const loadingDuration = Date.now() - startTime;
195
- dispatch(setHerdModulesLoadedInMs(loadingDuration));
196
- dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
197
- onRefreshCompleteRef.current?.();
199
+ complete(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED);
198
200
  }
199
201
  catch (error) {
200
- const loadingDuration = Date.now() - startTime;
202
+ if (!isCurrent())
203
+ return;
201
204
  console.error("Error refreshing scout data:", error);
202
- dispatch(setDataSource(EnumDataSource.UNKNOWN));
203
- dispatch(setDataSourceInfo({
204
- source: EnumDataSource.UNKNOWN,
205
- timestamp: Date.now(),
206
- }));
207
- dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.UNSUCCESSFULLY_LOADED));
208
- dispatch(setHerdModulesLoadedInMs(loadingDuration));
209
- dispatch(setStatus(EnumScoutStateStatus.DONE_LOADING));
210
- onRefreshCompleteRef.current?.();
205
+ if (cachedHerdModules !== null) {
206
+ dispatch(setDataSource(EnumDataSource.CACHE));
207
+ complete(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED);
208
+ }
209
+ else {
210
+ dispatch(setDataSource(EnumDataSource.UNKNOWN));
211
+ dispatch(setDataSourceInfo({
212
+ source: EnumDataSource.UNKNOWN,
213
+ timestamp: Date.now(),
214
+ }));
215
+ complete(EnumHerdModulesLoadingState.UNSUCCESSFULLY_LOADED);
216
+ }
211
217
  }
212
218
  finally {
213
- refreshInProgressRef.current = false;
214
- lastQueryAtRef.current = Date.now();
219
+ if (isCurrent()) {
220
+ lastQueryAtRef.current = Date.now();
221
+ }
215
222
  }
216
- }, [dispatch, supabase, cacheFirst, cacheTtlMs, handleIndexedDbError]);
223
+ }, [
224
+ dispatch,
225
+ supabase,
226
+ cacheFirst,
227
+ cacheTtlMs,
228
+ resetClientState,
229
+ ]);
217
230
  useEffect(() => {
218
231
  if (autoRefresh) {
219
232
  void handleRefresh({ force: false });
220
233
  }
221
234
  }, [autoRefresh, handleRefresh]);
235
+ useEffect(() => {
236
+ return subscribeToSupabaseAuth(supabase, (event, session) => {
237
+ if (event === "SIGNED_OUT") {
238
+ const previousUserId = activeUserIdRef.current ?? scoutCache.getScopeUserId();
239
+ refreshIdRef.current++;
240
+ activeUserIdRef.current = null;
241
+ resetClientState();
242
+ void clearScoutClientState(previousUserId ?? undefined).catch((error) => {
243
+ console.warn("[useScoutRefresh] Sign-out cache clear failed:", error);
244
+ });
245
+ return;
246
+ }
247
+ const nextUserId = session?.user?.id;
248
+ if (event === "SIGNED_IN" &&
249
+ nextUserId &&
250
+ nextUserId !== activeUserIdRef.current) {
251
+ refreshIdRef.current++;
252
+ activeUserIdRef.current = nextUserId;
253
+ resetClientState();
254
+ void handleRefresh({ force: true });
255
+ }
256
+ });
257
+ }, [supabase, handleRefresh, resetClientState]);
222
258
  useEffect(() => {
223
259
  if (!autoRefresh || typeof window === "undefined")
224
260
  return;
@@ -233,7 +269,7 @@ export function useScoutRefresh(options = {}) {
233
269
  }, [autoRefresh, handleRefresh, onlineRefetchMinIntervalMs]);
234
270
  const clearCache = useCallback(async () => {
235
271
  try {
236
- await scoutCache.clearHerdModules();
272
+ await scoutCache.clearHerdModules(activeUserIdRef.current ?? undefined);
237
273
  }
238
274
  catch (error) {
239
275
  console.error("[useScoutRefresh] Failed to clear cache:", error);
@@ -242,5 +278,6 @@ export function useScoutRefresh(options = {}) {
242
278
  return {
243
279
  handleRefresh,
244
280
  clearCache,
281
+ clearScoutClientState,
245
282
  };
246
283
  }
@@ -78,23 +78,31 @@ function useJwtMintLifecycle({ enabled, supabase, cacheKey, ttlSec, refreshBefor
78
78
  const [mint, setMint] = useState(null);
79
79
  const [inFlight, setInFlight] = useState(false);
80
80
  const [error, setError] = useState(null);
81
- const inFlightRef = useRef(false);
81
+ const refreshIdRef = useRef(0);
82
+ const authUserIdRef = useRef(null);
82
83
  const status = useMemo(() => derive_jwt_mint_status(enabled, mint, inFlight, error), [enabled, mint, inFlight, error]);
83
84
  const refreshMint = useCallback(async () => {
84
- if (!enabled || inFlightRef.current) {
85
+ if (!enabled)
85
86
  return;
86
- }
87
- inFlightRef.current = true;
87
+ const refreshId = ++refreshIdRef.current;
88
+ const isCurrent = () => refreshIdRef.current === refreshId;
88
89
  setInFlight(true);
89
90
  setError(null);
90
91
  try {
91
92
  const { data: { session }, } = await supabase.auth.getSession();
93
+ if (!isCurrent())
94
+ return;
92
95
  if (!session) {
96
+ authUserIdRef.current = null;
93
97
  setMint(null);
94
- await scoutCache.clearJwtMint(cacheKey);
95
98
  return;
96
99
  }
100
+ const userId = session.user.id;
101
+ authUserIdRef.current = userId;
102
+ await scoutCache.setScope(userId);
97
103
  const mintResponse = await mintToken(supabase);
104
+ if (!isCurrent() || authUserIdRef.current !== userId)
105
+ return;
98
106
  if (mintResponse.status !== EnumWebResponse.SUCCESS ||
99
107
  !mintResponse.data) {
100
108
  setError(mintResponse.msg ?? "mint failed");
@@ -109,20 +117,25 @@ function useJwtMintLifecycle({ enabled, supabase, cacheKey, ttlSec, refreshBefor
109
117
  keys = await loadPublicKeys(true);
110
118
  verified = await verifyToken(mintResponse.data, keys);
111
119
  }
120
+ if (!isCurrent() || authUserIdRef.current !== userId)
121
+ return;
112
122
  setMint(verified);
113
123
  try {
114
- await scoutCache.setJwtMint(cacheKey, verified);
124
+ await scoutCache.setJwtMint(cacheKey, verified, userId);
115
125
  }
116
126
  catch (cacheError) {
117
127
  console.warn(`[ScoutRefreshProvider] ${cacheKey} cache save failed:`, cacheError);
118
128
  }
119
129
  }
120
130
  catch (e) {
121
- setError(e instanceof Error ? e.message : "mint failed");
131
+ if (isCurrent()) {
132
+ setError(e instanceof Error ? e.message : "mint failed");
133
+ }
122
134
  }
123
135
  finally {
124
- inFlightRef.current = false;
125
- setInFlight(false);
136
+ if (isCurrent()) {
137
+ setInFlight(false);
138
+ }
126
139
  }
127
140
  }, [enabled, supabase, cacheKey, loadPublicKeys, mintToken, verifyToken]);
128
141
  useEffect(() => {
@@ -135,16 +148,21 @@ function useJwtMintLifecycle({ enabled, supabase, cacheKey, ttlSec, refreshBefor
135
148
  let signedOut = false;
136
149
  const initializeMint = async () => {
137
150
  try {
138
- const [cached, sessionResult] = await Promise.all([
139
- scoutCache.getJwtMint(cacheKey),
140
- supabase.auth.getSession(),
141
- ]);
151
+ const sessionResult = await supabase.auth.getSession();
142
152
  if (cancelled || signedOut) {
143
153
  return;
144
154
  }
145
- if (!sessionResult.data.session) {
155
+ if (!sessionResult.data.session?.user?.id) {
146
156
  setMint(null);
147
- await scoutCache.clearJwtMint(cacheKey);
157
+ return;
158
+ }
159
+ const userId = sessionResult.data.session.user.id;
160
+ authUserIdRef.current = userId;
161
+ await scoutCache.setScope(userId);
162
+ const cached = await scoutCache.getJwtMint(cacheKey, userId);
163
+ if (cancelled ||
164
+ signedOut ||
165
+ authUserIdRef.current !== userId) {
148
166
  return;
149
167
  }
150
168
  if (cached.data) {
@@ -162,16 +180,20 @@ function useJwtMintLifecycle({ enabled, supabase, cacheKey, ttlSec, refreshBefor
162
180
  }
163
181
  };
164
182
  void initializeMint();
165
- const unsubscribeAuth = subscribeToSupabaseAuth(supabase, (event) => {
183
+ const unsubscribeAuth = subscribeToSupabaseAuth(supabase, (event, session) => {
166
184
  if (event === "SIGNED_OUT") {
167
185
  signedOut = true;
186
+ authUserIdRef.current = null;
187
+ refreshIdRef.current++;
168
188
  setMint(null);
169
189
  setError(null);
170
- void scoutCache.clearJwtMint(cacheKey);
190
+ setInFlight(false);
171
191
  return;
172
192
  }
173
193
  if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
174
194
  signedOut = false;
195
+ authUserIdRef.current = session?.user?.id ?? null;
196
+ refreshIdRef.current++;
175
197
  void refreshMint();
176
198
  }
177
199
  });
@@ -1,4 +1,3 @@
1
- import { type Slice } from "@reduxjs/toolkit";
2
1
  import { IUser } from "../types/db";
3
2
  import { IHerdModule, EnumHerdModulesLoadingState } from "../types/herd_module";
4
3
  import { EnumDataSource, IDataSourceInfo } from "../types/data_source";
@@ -32,7 +31,6 @@ export interface ScoutState {
32
31
  export interface RootState {
33
32
  scout: ScoutState;
34
33
  }
35
- export declare const scoutSlice: Slice<ScoutState>;
36
- export declare const setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setStatus: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setLoadingPerformance: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setHerdModulesApiServerProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setHerdModulesApiTotalRequestDuration: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setCacheLoadDuration: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, updateSessionSummariesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setUser: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, addDevice: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, addPlan: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>, setActiveHerdGpsTrackersConnectivity: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<`${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPayload<any, `${string}/${string}`> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, never, any> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, never> | import("@reduxjs/toolkit").ActionCreatorWithPreparedPayload<any[], any, `${string}/${string}`, any, any>;
34
+ export declare const resetScoutState: import("@reduxjs/toolkit").ActionCreatorWithoutPayload<"scout/resetScoutState">, setHerdModules: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModules">, setStatus: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setStatus">, setHerdModulesLoadingState: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadingState">, setLoadingPerformance: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setLoadingPerformance">, setHerdModulesLoadedInMs: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesLoadedInMs">, setHerdModulesApiServerProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiServerProcessingDuration">, setHerdModulesApiTotalRequestDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setHerdModulesApiTotalRequestDuration">, setUserApiDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUserApiDuration">, setDataProcessingDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataProcessingDuration">, setCacheLoadDuration: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setCacheLoadDuration">, setActiveHerdId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdId">, setActiveDeviceId: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveDeviceId">, setDataSource: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSource">, setDataSourceInfo: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setDataSourceInfo">, updateSessionSummariesForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateSessionSummariesForHerdModule">, appendPlansToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/appendPlansToHerdModule">, setUser: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setUser">, addNewDeviceToHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addNewDeviceToHerdModule">, updateDeviceForHerdModule: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDeviceForHerdModule">, addDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addDevice">, deleteDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deleteDevice">, updateDevice: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updateDevice">, addPlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/addPlan">, deletePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/deletePlan">, updatePlan: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/updatePlan">, setActiveHerdGpsTrackersConnectivity: import("@reduxjs/toolkit").ActionCreatorWithPayload<any, "scout/setActiveHerdGpsTrackersConnectivity">;
37
35
  declare const _default: import("redux").Reducer<ScoutState>;
38
36
  export default _default;