@adventurelabs/scout-core 1.4.75 → 1.4.78

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.
@@ -6,6 +6,10 @@ import { createBrowserClient } from "@supabase/ssr";
6
6
  import { CLIENT_ABILITIES_TOKEN_TTL_SEC, } from "../types/client_abilities_token";
7
7
  import { EnumWebResponse } from "../types/requests";
8
8
  import { get_client_abilities_jwt_public_keys, mint_client_abilities_token, verify_client_abilities_token, } from "../helpers/client_abilities_token";
9
+ import { scoutCache } from "../helpers/cache";
10
+ import { get_pubsub_jwt_public_keys, mint_pubsub_token, verify_pubsub_token, } from "../helpers/pubsub_token";
11
+ import { PUBSUB_TOKEN_TTL_SEC } from "../types/pubsub_token";
12
+ import { derive_jwt_mint_status, } from "../types/jwt_mint";
9
13
  const ScoutRefreshContext = createContext(null);
10
14
  const DEFAULT_REFRESH_BEFORE_EXPIRY_SEC = 120;
11
15
  const DEFAULT_PUBLIC_KEYS_CACHE_MS = 10 * 60 * 1000;
@@ -25,102 +29,123 @@ function useScoutRefreshContext() {
25
29
  export function useSupabase() {
26
30
  return useScoutRefreshContext().supabase;
27
31
  }
28
- /** Verified client abilities JWT (minted on a schedule by ScoutRefreshProvider). */
29
32
  export function useClientAbilitiesMint() {
30
33
  return useScoutRefreshContext().abilities;
31
34
  }
35
+ export function usePubsubTokenMint() {
36
+ return useScoutRefreshContext().pubsub;
37
+ }
32
38
  function resolveAbilityParams(abilityParams) {
33
39
  return {
34
40
  mintClientAbilitiesToken: abilityParams?.mintClientAbilitiesToken ?? true,
41
+ mintPubsubToken: abilityParams?.mintPubsubToken ?? false,
35
42
  clientAbilitiesTokenTtlSec: abilityParams?.clientAbilitiesTokenTtlSec ??
36
43
  CLIENT_ABILITIES_TOKEN_TTL_SEC,
44
+ pubsubTokenTtlSec: abilityParams?.pubsubTokenTtlSec ?? PUBSUB_TOKEN_TTL_SEC,
37
45
  refreshBeforeExpirySec: abilityParams?.refreshBeforeExpirySec ??
38
46
  DEFAULT_REFRESH_BEFORE_EXPIRY_SEC,
39
47
  publicKeysCacheTtlMs: abilityParams?.publicKeysCacheTtlMs ?? DEFAULT_PUBLIC_KEYS_CACHE_MS,
40
48
  };
41
49
  }
42
- function useClientAbilitiesMintLifecycle(supabase, config) {
43
- const { mintClientAbilitiesToken: enabled, clientAbilitiesTokenTtlSec: ttlSec, refreshBeforeExpirySec, publicKeysCacheTtlMs, } = config;
44
- const [mint, setMint] = useState(null);
45
- const [isLoading, setIsLoading] = useState(false);
46
- const [error, setError] = useState(null);
47
- const inFlightRef = useRef(false);
48
- const publicKeysRef = useRef(null);
49
- const publicKeysAtRef = useRef(0);
50
- const loadPublicKeys = useCallback(async (force = false) => {
50
+ function useCachedPublicKeys(supabase, publicKeysCacheTtlMs, fetchKeys) {
51
+ const keysRef = useRef(null);
52
+ const keysAtRef = useRef(0);
53
+ return useCallback(async (force = false) => {
51
54
  const now = Date.now();
52
55
  if (!force &&
53
- publicKeysRef.current?.length &&
54
- now - publicKeysAtRef.current < publicKeysCacheTtlMs) {
55
- return publicKeysRef.current;
56
+ keysRef.current?.length &&
57
+ now - keysAtRef.current < publicKeysCacheTtlMs) {
58
+ return keysRef.current;
56
59
  }
57
- const { status, data, msg } = await get_client_abilities_jwt_public_keys(supabase);
60
+ const { status, data, msg } = await fetchKeys(supabase);
58
61
  if (status !== EnumWebResponse.SUCCESS || !data?.length) {
59
- throw new Error(msg ?? "get_client_abilities_jwt_public_keys returned no keys");
62
+ throw new Error(msg ?? "jwt public keys RPC returned no keys");
60
63
  }
61
- publicKeysRef.current = data;
62
- publicKeysAtRef.current = now;
64
+ keysRef.current = data;
65
+ keysAtRef.current = now;
63
66
  return data;
64
- }, [supabase, publicKeysCacheTtlMs]);
67
+ }, [supabase, publicKeysCacheTtlMs, fetchKeys]);
68
+ }
69
+ function useJwtMintLifecycle({ enabled, supabase, cacheKey, ttlSec, refreshBeforeExpirySec, loadPublicKeys, mintToken, verifyToken, }) {
70
+ const [mint, setMint] = useState(null);
71
+ const [inFlight, setInFlight] = useState(false);
72
+ const [error, setError] = useState(null);
73
+ const inFlightRef = useRef(false);
74
+ const status = useMemo(() => derive_jwt_mint_status(enabled, mint, inFlight, error), [enabled, mint, inFlight, error]);
65
75
  const refreshMint = useCallback(async () => {
66
76
  if (!enabled || inFlightRef.current) {
67
77
  return;
68
78
  }
69
79
  inFlightRef.current = true;
70
- setIsLoading(true);
80
+ setInFlight(true);
71
81
  setError(null);
72
82
  try {
73
83
  const { data: { session }, } = await supabase.auth.getSession();
74
84
  if (!session) {
75
85
  setMint(null);
86
+ await scoutCache.clearJwtMint(cacheKey);
76
87
  return;
77
88
  }
78
- const mintResponse = await mint_client_abilities_token(supabase);
89
+ const mintResponse = await mintToken(supabase);
79
90
  if (mintResponse.status !== EnumWebResponse.SUCCESS ||
80
91
  !mintResponse.data) {
81
- setMint(null);
82
92
  setError(mintResponse.msg ?? "mint failed");
83
93
  return;
84
94
  }
85
95
  let keys = await loadPublicKeys();
86
96
  let verified;
87
97
  try {
88
- verified = await verify_client_abilities_token(mintResponse.data, keys);
98
+ verified = await verifyToken(mintResponse.data, keys);
89
99
  }
90
100
  catch {
91
101
  keys = await loadPublicKeys(true);
92
- verified = await verify_client_abilities_token(mintResponse.data, keys);
102
+ verified = await verifyToken(mintResponse.data, keys);
93
103
  }
94
104
  setMint(verified);
105
+ try {
106
+ await scoutCache.setJwtMint(cacheKey, verified);
107
+ }
108
+ catch (cacheError) {
109
+ console.warn(`[ScoutRefreshProvider] ${cacheKey} cache save failed:`, cacheError);
110
+ }
95
111
  }
96
112
  catch (e) {
97
- setMint(null);
98
- setError(e instanceof Error ? e.message : "client abilities mint failed");
113
+ setError(e instanceof Error ? e.message : "mint failed");
99
114
  }
100
115
  finally {
101
116
  inFlightRef.current = false;
102
- setIsLoading(false);
117
+ setInFlight(false);
103
118
  }
104
- }, [enabled, supabase, loadPublicKeys]);
119
+ }, [enabled, supabase, cacheKey, loadPublicKeys, mintToken, verifyToken]);
105
120
  useEffect(() => {
106
121
  if (!enabled) {
107
122
  setMint(null);
108
123
  setError(null);
109
124
  return;
110
125
  }
126
+ let cancelled = false;
127
+ void scoutCache.getJwtMint(cacheKey).then((cached) => {
128
+ if (!cancelled && cached.data) {
129
+ setMint(cached.data);
130
+ }
131
+ });
111
132
  void refreshMint();
112
133
  const { data: { subscription }, } = supabase.auth.onAuthStateChange((event) => {
113
134
  if (event === "SIGNED_OUT") {
114
135
  setMint(null);
115
136
  setError(null);
137
+ void scoutCache.clearJwtMint(cacheKey);
116
138
  return;
117
139
  }
118
140
  if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
119
141
  void refreshMint();
120
142
  }
121
143
  });
122
- return () => subscription.unsubscribe();
123
- }, [enabled, supabase, refreshMint]);
144
+ return () => {
145
+ cancelled = true;
146
+ subscription.unsubscribe();
147
+ };
148
+ }, [enabled, supabase, cacheKey, refreshMint]);
124
149
  useEffect(() => {
125
150
  if (!enabled || !mint) {
126
151
  return;
@@ -134,7 +159,35 @@ function useClientAbilitiesMintLifecycle(supabase, config) {
134
159
  }, delayMs);
135
160
  return () => clearTimeout(timer);
136
161
  }, [enabled, mint, ttlSec, refreshBeforeExpirySec, refreshMint]);
137
- return useMemo(() => ({ mint, isLoading, error, refreshMint }), [mint, isLoading, error, refreshMint]);
162
+ return useMemo(() => ({ mint, status, error, refreshMint }), [mint, status, error, refreshMint]);
163
+ }
164
+ function useClientAbilitiesMintLifecycle(supabase, config) {
165
+ const { mintClientAbilitiesToken: enabled, clientAbilitiesTokenTtlSec: ttlSec, refreshBeforeExpirySec, publicKeysCacheTtlMs, } = config;
166
+ const loadPublicKeys = useCachedPublicKeys(supabase, publicKeysCacheTtlMs, get_client_abilities_jwt_public_keys);
167
+ return useJwtMintLifecycle({
168
+ enabled,
169
+ supabase,
170
+ cacheKey: "client_abilities",
171
+ ttlSec,
172
+ refreshBeforeExpirySec,
173
+ loadPublicKeys,
174
+ mintToken: mint_client_abilities_token,
175
+ verifyToken: verify_client_abilities_token,
176
+ });
177
+ }
178
+ function usePubsubTokenMintLifecycle(supabase, config) {
179
+ const { mintPubsubToken: enabled, pubsubTokenTtlSec: ttlSec, refreshBeforeExpirySec, publicKeysCacheTtlMs, } = config;
180
+ const loadPublicKeys = useCachedPublicKeys(supabase, publicKeysCacheTtlMs, get_pubsub_jwt_public_keys);
181
+ return useJwtMintLifecycle({
182
+ enabled,
183
+ supabase,
184
+ cacheKey: "pubsub",
185
+ ttlSec,
186
+ refreshBeforeExpirySec,
187
+ loadPublicKeys,
188
+ mintToken: mint_pubsub_token,
189
+ verifyToken: verify_pubsub_token,
190
+ });
138
191
  }
139
192
  export function ScoutRefreshProvider({ children, abilityParams, ...refreshOptions }) {
140
193
  const urlRef = useRef(process.env.NEXT_PUBLIC_SUPABASE_URL || "");
@@ -143,6 +196,7 @@ export function ScoutRefreshProvider({ children, abilityParams, ...refreshOption
143
196
  const abilitiesConfig = useMemo(() => resolveAbilityParams(abilityParams), [abilityParams]);
144
197
  useScoutRefresh({ ...refreshOptions, supabase });
145
198
  const abilities = useClientAbilitiesMintLifecycle(supabase, abilitiesConfig);
146
- const value = useMemo(() => ({ supabase, abilities }), [supabase, abilities]);
199
+ const pubsub = usePubsubTokenMintLifecycle(supabase, abilitiesConfig);
200
+ const value = useMemo(() => ({ supabase, abilities, pubsub }), [supabase, abilities, pubsub]);
147
201
  return (_jsx(ScoutRefreshContext.Provider, { value: value, children: children }));
148
202
  }