@rockerone/xprnkit 0.3.3 → 0.3.5

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.
Files changed (32) hide show
  1. package/build/components/identity/index.d.ts +2 -0
  2. package/build/components/identity/index.js +2 -0
  3. package/build/components/identity/xprn-account-list.d.ts +48 -0
  4. package/build/components/identity/xprn-account-list.js +106 -0
  5. package/build/components/identity/xprn-identity-proof-gate.d.ts +67 -0
  6. package/build/components/identity/xprn-identity-proof-gate.js +77 -0
  7. package/build/components/identity/xprn-identity.d.ts +4 -0
  8. package/build/components/identity/xprn-identity.js +5 -4
  9. package/build/components/identity/xprn-session-name.d.ts +23 -2
  10. package/build/components/identity/xprn-session-name.js +48 -3
  11. package/build/components/ui/dropdown.d.ts +3 -1
  12. package/build/components/ui/dropdown.js +4 -1
  13. package/build/components/xprn-transaction.js +3 -1
  14. package/build/global.css +118 -0
  15. package/build/providers/XPRNProvider.d.ts +60 -26
  16. package/build/providers/XPRNProvider.js +324 -267
  17. package/build/services/identity-proof/create-identity-proof.js +1 -0
  18. package/build/services/identity-proof/index.d.ts +5 -1
  19. package/build/services/identity-proof/index.js +3 -0
  20. package/build/services/identity-proof/token-utils.d.ts +48 -0
  21. package/build/services/identity-proof/token-utils.js +85 -0
  22. package/build/services/identity-proof/types.d.ts +25 -2
  23. package/build/services/identity-proof/use-identity-proof.js +5 -3
  24. package/build/services/identity-proof/validate-identity-proof.d.ts +51 -0
  25. package/build/services/identity-proof/validate-identity-proof.js +93 -0
  26. package/build/services/identity-proof/verify-identity-proof.d.ts +4 -4
  27. package/build/services/identity-proof/verify-identity-proof.js +15 -3
  28. package/build/utils/auth-storage.d.ts +126 -0
  29. package/build/utils/auth-storage.js +216 -0
  30. package/build/utils/index.d.ts +1 -0
  31. package/build/utils/index.js +1 -0
  32. package/package.json +2 -1
@@ -2,132 +2,165 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { ApiClass } from "@proton/api";
4
4
  import ConnectWallet from "@proton/web-sdk";
5
- import React, { useCallback, useContext, useEffect, useMemo, useReducer, useRef, } from "react";
5
+ import React, { useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
6
6
  import { JsonRpc } from "@proton/js";
7
- import { parseTransactionErrorMessage } from "../utils";
8
- import { createIdentityProof, verifyIdentityProof, } from "../services/identity-proof";
7
+ import { sessionStorage, parseTransactionErrorMessage } from "../utils";
8
+ import { createIdentityProof, verifyIdentityProof, validateIdentityProof, parseToken, isTokenExpired, } from "../services/identity-proof";
9
9
  const XPRNContext = React.createContext({
10
+ config: null,
10
11
  session: null,
11
12
  link: null,
12
13
  profile: null,
13
14
  rpc: null,
14
- txErrorsStack: null,
15
+ identityProof: null,
16
+ identityProofStatus: "idle",
17
+ needsIdentityProof: false,
18
+ isIdentityProofRequired: false,
19
+ isIdentityProofEnabled: false,
15
20
  connect: () => { },
16
- disconnect: () => { },
17
- addTransactionError() { },
21
+ disconnect: async () => { },
22
+ requestIdentityProof: () => { },
23
+ listStoredSessions: () => [],
24
+ switchToSession: async () => { },
25
+ addTransactionError: () => { },
26
+ // Legacy
18
27
  authentication: null,
19
- authenticate: () => { },
20
- getActiveSession: () => null,
21
- listSessions: () => [],
22
- setActiveSession: () => { },
23
- removeSession: async () => { },
24
- getAllProfiles: () => [],
25
- switchSession: () => { },
26
- getSessionById: () => null,
27
- getSessionByActor: () => null,
28
28
  authStatus: "idle",
29
- getAuthStatus: () => "idle",
29
+ authenticate: () => { },
30
30
  });
31
31
  export const XPRNProvider = ({ children, config, }) => {
32
- // Force update mechanism - only triggers re-render when active session changes
33
- const [version, forceUpdate] = useReducer((x) => x + 1, 0);
34
- // Internal refs - no re-renders when mutated
35
- const sessionsRef = useRef(new Map());
36
- const activeSessionIdRef = useRef(null);
32
+ // Simple state for single active session
33
+ const [session, setSession] = useState(null);
34
+ const [link, setLink] = useState(null);
35
+ const [profile, setProfile] = useState(null);
36
+ const [identityProof, setIdentityProof] = useState(null);
37
+ const [identityProofStatus, setIdentityProofStatus] = useState("idle");
38
+ // Compute identity proof config (supports both new and legacy config)
39
+ const identityProofConfig = useMemo(() => {
40
+ if (config.identityProof) {
41
+ return config.identityProof;
42
+ }
43
+ // Legacy support
44
+ if (config.authenticationUrl) {
45
+ return {
46
+ createUrl: config.authenticationUrl,
47
+ enforceOnConnect: config.enforceAuthentication,
48
+ };
49
+ }
50
+ return null;
51
+ }, [config.identityProof, config.authenticationUrl, config.enforceAuthentication]);
52
+ // Helper booleans
53
+ const isIdentityProofEnabled = identityProofConfig !== null;
54
+ const isIdentityProofRequired = identityProofConfig?.required ?? false;
55
+ const needsIdentityProof = isIdentityProofEnabled && session !== null && identityProof === null;
56
+ // Internal refs
37
57
  const jsonRpcRef = useRef(new JsonRpc(config.endpoints));
38
58
  const errorsStackRef = useRef([]);
39
59
  const onSessionRef = useRef();
40
60
  const onProfileRef = useRef();
41
61
  const isRestoringRef = useRef(false);
42
- // Authentication refs - for duplicate prevention and cancellation
43
- const authStatusRef = useRef(new Map());
44
- const authenticatingRef = useRef(new Set());
45
- const authAbortControllersRef = useRef(new Map());
46
- // Derived values - recomputed only when version changes (active session affected)
47
- const { activeSession, session, link, profile, authentication, authStatus } = useMemo(() => {
48
- const active = activeSessionIdRef.current
49
- ? sessionsRef.current.get(activeSessionIdRef.current) ?? null
50
- : null;
51
- const status = activeSessionIdRef.current
52
- ? authStatusRef.current.get(activeSessionIdRef.current) ?? "idle"
53
- : "idle";
54
- return {
55
- activeSession: active,
56
- session: active?.session ?? null,
57
- link: active?.link ?? null,
58
- profile: active?.profile ?? null,
59
- authentication: active?.authentication ?? null,
60
- authStatus: status,
61
- };
62
- }, [version]);
63
- // Get auth status for any actor
64
- const getAuthStatus = useCallback((actor) => {
65
- const target = actor || activeSessionIdRef.current;
66
- return target ? authStatusRef.current.get(target) ?? "idle" : "idle";
67
- }, []);
68
- // Session management methods - stable callbacks using refs
69
- const getActiveSession = useCallback(() => {
70
- return activeSessionIdRef.current
71
- ? sessionsRef.current.get(activeSessionIdRef.current) ?? null
72
- : null;
73
- }, []);
74
- const listSessions = useCallback(() => {
75
- return Array.from(sessionsRef.current.values());
76
- }, []);
77
- const setActiveSession = useCallback((actor) => {
78
- if (sessionsRef.current.has(actor)) {
79
- activeSessionIdRef.current = actor;
80
- // Store in localStorage for persistence
81
- if (typeof window !== "undefined") {
82
- localStorage.setItem("xprn_active_session_actor", actor);
62
+ const isAuthenticatingRef = useRef(false);
63
+ const isSwitchingSessionRef = useRef(false);
64
+ // List stored sessions from proton-web-sdk localStorage
65
+ const listStoredSessions = useCallback(() => {
66
+ return sessionStorage.getLinkList(config.requesterAccount);
67
+ }, [config.requesterAccount]);
68
+ // Switch to a different stored session using link.restoreSession
69
+ const switchToSession = useCallback(async (auth, chainId) => {
70
+ if (!link) {
71
+ console.warn("Cannot switch session: No link available. Call connect() first.");
72
+ return;
73
+ }
74
+ // Mark that we're switching sessions to prevent useEffect interference
75
+ isSwitchingSessionRef.current = true;
76
+ // Parse auth string (format: "actor@permission")
77
+ const [actor, permission] = auth.split('@');
78
+ const authObj = { actor, permission };
79
+ // Use link.restoreSession to restore the session
80
+ // Both Link and ProtonWebLink support this method (with different return types)
81
+ const restoredSession = await link.restoreSession(config.requesterAccount, authObj, chainId);
82
+ if (!restoredSession) {
83
+ throw new Error(`Failed to restore session for ${auth} on chain ${chainId}`);
84
+ }
85
+ // Clear identity proof state immediately for the new session
86
+ setSession(restoredSession);
87
+ // Check if the new session has a stored identity proof token
88
+ const storedEntry = sessionStorage.get(config.requesterAccount, { actor: actor, permission: permission }, chainId);
89
+ if (storedEntry?.identityProofToken && identityProofConfig?.validationUrl) {
90
+ // New session has a stored token - validate it
91
+ setIdentityProofStatus("validating");
92
+ try {
93
+ const tokenClaims = parseToken(storedEntry.identityProofToken);
94
+ if (tokenClaims && !isTokenExpired(tokenClaims)) {
95
+ // Token exists and not expired - validate with backend
96
+ const result = await validateIdentityProof({
97
+ validationUrl: identityProofConfig.validationUrl,
98
+ token: storedEntry.identityProofToken,
99
+ headers: identityProofConfig.headers,
100
+ timeout: identityProofConfig.timeout,
101
+ });
102
+ if (result.valid) {
103
+ const newToken = result.token || storedEntry.identityProofToken;
104
+ setIdentityProof({
105
+ actor: actor,
106
+ publicKey: "",
107
+ token: newToken,
108
+ });
109
+ setIdentityProofStatus("success");
110
+ // Update token in storage if refreshed
111
+ if (result.token && result.token !== storedEntry.identityProofToken) {
112
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor: actor, permission: permission }, chainId, result.token);
113
+ }
114
+ }
115
+ else {
116
+ // Token invalid - clear it
117
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor: actor, permission: permission }, chainId, null);
118
+ setIdentityProof(null);
119
+ setIdentityProofStatus("expired");
120
+ }
121
+ }
122
+ else {
123
+ // Token expired or invalid - clear it
124
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor: actor, permission: permission }, chainId, null);
125
+ setIdentityProof(null);
126
+ setIdentityProofStatus("expired");
127
+ }
128
+ }
129
+ catch (error) {
130
+ console.error("Failed to validate token during session switch:", error);
131
+ setIdentityProof(null);
132
+ setIdentityProofStatus("error");
83
133
  }
84
- // Trigger re-render for consumers
85
- forceUpdate();
86
134
  }
87
135
  else {
88
- console.warn(`Session with actor ${actor} not found`);
89
- }
90
- }, [forceUpdate]);
91
- const switchSession = useCallback((actor) => {
92
- setActiveSession(actor);
93
- }, [setActiveSession]);
94
- const getSessionById = useCallback((actor) => {
95
- return sessionsRef.current.get(actor) ?? null;
96
- }, []);
97
- const getSessionByActor = useCallback((actor) => {
98
- return sessionsRef.current.get(actor) ?? null;
99
- }, []);
100
- const removeSession = useCallback(async (actor) => {
101
- const sessionToRemove = sessionsRef.current.get(actor);
102
- if (!sessionToRemove) {
103
- console.warn(`Session with actor ${actor} not found`);
104
- return;
136
+ // No stored token for this session - clear identity proof state
137
+ setIdentityProof(null);
138
+ setIdentityProofStatus("idle");
105
139
  }
106
- // Remove session from the link
140
+ // Fetch profile for new session
141
+ const api = new ApiClass(config.apiMode === "testnet" ? "proton-test" : "proton");
107
142
  try {
108
- await sessionToRemove.link.removeSession(config.requesterAccount, sessionToRemove.session.auth, sessionToRemove.session.chainId);
143
+ const profileRes = await api.getProtonAvatar(actor);
144
+ if (profileRes) {
145
+ setProfile({
146
+ displayName: profileRes.name,
147
+ avatar: profileRes.avatar,
148
+ isKyc: profileRes.kyc.length > 0,
149
+ });
150
+ }
109
151
  }
110
- catch (e) {
111
- console.error("Error removing session from link:", e);
152
+ catch (error) {
153
+ console.error("Failed to fetch profile after session switch:", error);
112
154
  }
113
- // Remove from sessions map (mutate ref directly)
114
- sessionsRef.current.delete(actor);
115
- // If this was the active session, clear it and trigger re-render
116
- if (activeSessionIdRef.current === actor) {
117
- activeSessionIdRef.current = null;
118
- if (typeof window !== "undefined") {
119
- localStorage.removeItem("xprn_active_session_actor");
120
- }
121
- forceUpdate();
155
+ // Trigger onSession callback if set
156
+ if (onSessionRef.current) {
157
+ onSessionRef.current(restoredSession, link);
158
+ onSessionRef.current = undefined;
122
159
  }
123
- }, [config.requesterAccount, forceUpdate]);
124
- const getAllProfiles = useCallback(() => {
125
- return Array.from(sessionsRef.current.values())
126
- .map(s => s.profile)
127
- .filter((p) => p !== null);
128
- }, []);
160
+ // Mark session switch as complete
161
+ isSwitchingSessionRef.current = false;
162
+ }, [link, config.requesterAccount, config.apiMode, identityProofConfig]);
129
163
  const connect = useCallback((restoreSession, onSession, onProfile) => {
130
- console.log("restoreSession", restoreSession);
131
164
  ConnectWallet({
132
165
  linkOptions: {
133
166
  endpoints: config.endpoints,
@@ -143,241 +176,265 @@ export const XPRNProvider = ({ children, config, }) => {
143
176
  onSessionRef.current = onSession;
144
177
  onProfileRef.current = onProfile;
145
178
  if (res.link && res.session) {
179
+ // Update state with new session
180
+ setLink(res.link);
181
+ setSession(res.session);
182
+ // Clear identity proof state for new session - it will be restored if stored token exists
183
+ setIdentityProof(null);
184
+ setIdentityProofStatus("idle");
146
185
  const actor = res.session.auth.actor.toString();
147
- // Create initial session object with null profile
148
- const newSession = {
149
- actor,
150
- session: res.session,
151
- link: res.link,
152
- profile: null,
153
- authentication: null,
154
- };
155
- // Add to sessions map (mutate ref directly)
156
- sessionsRef.current.set(actor, newSession);
157
- // Auto-switch to newly connected session
158
- activeSessionIdRef.current = actor;
159
- if (typeof window !== "undefined") {
160
- localStorage.setItem("xprn_active_session_actor", actor);
186
+ // Trigger onSession callback
187
+ if (onSession) {
188
+ onSession(res.session, res.link);
161
189
  }
162
- // Trigger re-render since active session changed
163
- forceUpdate();
164
190
  // Fetch profile data
165
- const api = new ApiClass(config.apiMode && config.apiMode == "testnet"
166
- ? "proton-test"
167
- : "proton");
191
+ const api = new ApiClass(config.apiMode === "testnet" ? "proton-test" : "proton");
192
+ const permission = res.session.auth.permission.toString();
193
+ const chainId = res.session.chainId.toString();
168
194
  api.getProtonAvatar(actor).then(profileRes => {
169
195
  if (profileRes) {
170
196
  const xprProfile = {
171
197
  displayName: profileRes.name,
172
198
  avatar: `${profileRes.avatar}`,
173
- isKyc: profileRes.kyc.length > 0 || false,
199
+ isKyc: profileRes.kyc.length > 0,
174
200
  };
175
- // Update session with profile (mutate ref)
176
- const existingSession = sessionsRef.current.get(actor);
177
- if (existingSession) {
178
- sessionsRef.current.set(actor, {
179
- ...existingSession,
180
- profile: xprProfile,
181
- });
182
- }
183
- // Only trigger re-render if this is still the active session
184
- if (activeSessionIdRef.current === actor) {
185
- forceUpdate();
201
+ setProfile(xprProfile);
202
+ // Save session data to storage, preserving existing token if present
203
+ const existingEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
204
+ sessionStorage.save(config.requesterAccount, {
205
+ auth: { actor, permission },
206
+ chainId,
207
+ profile: xprProfile,
208
+ identityProofToken: existingEntry?.identityProofToken ?? null,
209
+ });
210
+ if (onProfile) {
211
+ onProfile(xprProfile);
186
212
  }
187
213
  }
188
214
  });
189
215
  }
190
216
  });
191
- }, [config, forceUpdate]);
192
- const disconnect = useCallback(async (actor) => {
193
- // If actor is provided, disconnect that specific session
194
- // Otherwise, disconnect the active session
195
- const targetActor = actor || activeSessionIdRef.current;
196
- if (!targetActor) {
217
+ }, [config]);
218
+ const disconnect = useCallback(async () => {
219
+ if (!session || !link) {
197
220
  console.warn("No session to disconnect");
198
221
  return;
199
222
  }
200
- await removeSession(targetActor);
201
- }, [removeSession]);
223
+ try {
224
+ await link.removeSession(config.requesterAccount, session.auth, session.chainId);
225
+ }
226
+ catch (e) {
227
+ console.error("Error removing session:", e);
228
+ }
229
+ // Clear state
230
+ setSession(null);
231
+ setProfile(null);
232
+ setIdentityProof(null);
233
+ setIdentityProofStatus("idle");
234
+ // Note: Keep link reference for potential session switching
235
+ }, [session, link, config.requesterAccount]);
202
236
  const addTxError = useCallback((message) => {
203
237
  errorsStackRef.current.push(parseTransactionErrorMessage(message));
204
238
  }, []);
205
- const authenticate = useCallback(async (success, fail, actor) => {
206
- // Use provided actor or active session
207
- const targetActor = actor || activeSessionIdRef.current;
208
- if (!targetActor) {
209
- fail(new Error("No session available for authentication"));
210
- return;
211
- }
212
- const targetSession = sessionsRef.current.get(targetActor);
213
- if (!targetSession) {
214
- fail(new Error(`Session not found for actor: ${targetActor}`));
239
+ const requestIdentityProof = useCallback(async (success, fail) => {
240
+ if (!session) {
241
+ fail(new Error("No session available for identity proof"));
215
242
  return;
216
243
  }
217
- if (!config.authenticationUrl) {
218
- fail(new Error("Authentication URL not configured"));
244
+ if (!identityProofConfig) {
245
+ fail(new Error("Identity proof not configured"));
219
246
  return;
220
247
  }
221
- // Prevent duplicate requests
222
- if (authenticatingRef.current.has(targetActor)) {
223
- fail(new Error(`Authentication already in progress for ${targetActor}`));
248
+ if (isAuthenticatingRef.current) {
249
+ fail(new Error("Identity proof already in progress"));
224
250
  return;
225
251
  }
226
- // Cancel any previous request for this actor
227
- authAbortControllersRef.current.get(targetActor)?.abort();
228
- const abortController = new AbortController();
229
- authAbortControllersRef.current.set(targetActor, abortController);
230
- authenticatingRef.current.add(targetActor);
231
- // Update status
232
- const updateStatus = (status) => {
233
- authStatusRef.current.set(targetActor, status);
234
- if (activeSessionIdRef.current === targetActor) {
235
- forceUpdate();
236
- }
237
- };
252
+ isAuthenticatingRef.current = true;
238
253
  try {
239
254
  // Step 1: Sign with wallet
240
- updateStatus("signing");
241
- const proof = await createIdentityProof(targetSession.session, {
242
- signal: abortController.signal,
243
- });
255
+ setIdentityProofStatus("signing");
256
+ const proof = await createIdentityProof(session);
244
257
  // Step 2: Verify with backend
245
- updateStatus("verifying");
246
- const authRes = await verifyIdentityProof(proof, { authenticationUrl: config.authenticationUrl }, { signal: abortController.signal });
247
- // Step 3: Update session with authentication data
248
- const authData = {
258
+ setIdentityProofStatus("verifying");
259
+ const verifyRes = await verifyIdentityProof(proof, { createUrl: identityProofConfig.createUrl, headers: identityProofConfig.headers });
260
+ // Step 3: Update state with identity proof data
261
+ const identityProofData = {
249
262
  publicKey: proof.signer.publicKey,
250
263
  actor: proof.signer.actor,
251
- data: authRes,
264
+ token: verifyRes.token,
265
+ data: verifyRes,
252
266
  };
253
- const existingSession = sessionsRef.current.get(targetActor);
254
- if (existingSession) {
255
- sessionsRef.current.set(targetActor, {
256
- ...existingSession,
257
- authentication: authData,
258
- });
267
+ setIdentityProof(identityProofData);
268
+ setIdentityProofStatus("success");
269
+ // Save token to storage
270
+ if (verifyRes.token) {
271
+ const actor = session.auth.actor.toString();
272
+ const permission = session.auth.permission.toString();
273
+ const chainId = session.chainId.toString();
274
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, verifyRes.token);
259
275
  }
260
- // Success
261
- updateStatus("success");
262
- success(authRes);
276
+ success(identityProofData);
263
277
  }
264
278
  catch (error) {
265
- // Handle abort silently
266
- if (error instanceof Error && error.name === "AbortError") {
267
- updateStatus("idle");
268
- return;
269
- }
270
- // Handle other errors
271
- updateStatus("error");
279
+ setIdentityProofStatus("error");
272
280
  fail(error);
273
281
  }
274
282
  finally {
275
- authenticatingRef.current.delete(targetActor);
276
- if (authAbortControllersRef.current.get(targetActor) === abortController) {
277
- authAbortControllersRef.current.delete(targetActor);
278
- }
283
+ isAuthenticatingRef.current = false;
279
284
  }
280
- }, [config.authenticationUrl, forceUpdate]);
281
- // Handle authentication for active session
282
- useEffect(() => {
283
- if (session && activeSessionIdRef.current) {
284
- const currentSession = sessionsRef.current.get(activeSessionIdRef.current);
285
- if (currentSession && !currentSession.authentication) {
286
- if (config.enforceAuthentication && config.authenticationUrl) {
287
- authenticate(() => {
288
- console.log(`[XPRNProvider] Auto-authenticated: ${activeSessionIdRef.current}`);
289
- }, error => {
290
- console.error("[XPRNProvider] Auto-authentication failed:", error);
291
- });
285
+ }, [session, identityProofConfig, config.requesterAccount]);
286
+ // Legacy alias
287
+ const authenticate = requestIdentityProof;
288
+ // Validate stored token silently
289
+ const validateStoredToken = useCallback(async (actor, permission, chainId) => {
290
+ if (!identityProofConfig?.validationUrl) {
291
+ return false;
292
+ }
293
+ // Get stored token from session storage
294
+ const storedEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
295
+ const storedToken = storedEntry?.identityProofToken;
296
+ if (!storedToken) {
297
+ return false;
298
+ }
299
+ // Parse token to check if it's expired
300
+ const tokenClaims = parseToken(storedToken);
301
+ if (!tokenClaims) {
302
+ return false;
303
+ }
304
+ // Check if token is expired
305
+ if (isTokenExpired(tokenClaims)) {
306
+ // Clear expired token from storage
307
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, null);
308
+ return false;
309
+ }
310
+ // Validate token with backend
311
+ setIdentityProofStatus("validating");
312
+ try {
313
+ const result = await validateIdentityProof({
314
+ validationUrl: identityProofConfig.validationUrl,
315
+ token: storedToken,
316
+ headers: identityProofConfig.headers,
317
+ timeout: identityProofConfig.timeout,
318
+ });
319
+ if (result.valid) {
320
+ // Token is valid - update state
321
+ const newToken = result.token || storedToken;
322
+ setIdentityProof({
323
+ actor,
324
+ publicKey: "", // We don't have public key from stored token
325
+ token: newToken,
326
+ });
327
+ setIdentityProofStatus("success");
328
+ // Update token in storage if refreshed
329
+ if (result.token && result.token !== storedToken) {
330
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, result.token);
292
331
  }
332
+ return true;
333
+ }
334
+ else {
335
+ // Clear invalid token from storage
336
+ sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, null);
337
+ setIdentityProofStatus("expired");
338
+ return false;
293
339
  }
294
340
  }
295
- if (session && onSessionRef.current) {
296
- onSessionRef.current(session);
297
- onSessionRef.current = undefined; // Reset the ref
341
+ catch (error) {
342
+ console.error("[XPRNProvider] Token validation error:", error);
343
+ setIdentityProofStatus("error");
344
+ return false;
298
345
  }
299
- }, [
300
- session,
301
- authenticate,
302
- config.enforceAuthentication,
303
- config.authenticationUrl,
304
- ]);
305
- // Cleanup abort controllers on unmount
346
+ }, [identityProofConfig, config.requesterAccount]);
347
+ // Handle token restoration and auto-authentication when session is established
348
+ // Note: This effect is skipped when switchToSession is handling the identity proof state
306
349
  useEffect(() => {
307
- return () => {
308
- authAbortControllersRef.current.forEach(controller => controller.abort());
309
- authAbortControllersRef.current.clear();
310
- };
311
- }, []);
350
+ // Skip if we're in the middle of a session switch (switchToSession handles this)
351
+ if (isSwitchingSessionRef.current)
352
+ return;
353
+ if (!session || identityProof)
354
+ return;
355
+ if (!identityProofConfig)
356
+ return;
357
+ const actor = session.auth.actor.toString();
358
+ const permission = session.auth.permission.toString();
359
+ const chainId = session.chainId.toString();
360
+ // Check if there's a stored token and try to validate it
361
+ const storedEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
362
+ if (storedEntry?.identityProofToken) {
363
+ // Try to validate stored token silently
364
+ validateStoredToken(actor, permission, chainId).then(isValid => {
365
+ if (!isValid && (identityProofConfig.enforceOnConnect || identityProofConfig.required)) {
366
+ // Token invalid and identity proof is required - trigger full auth flow
367
+ requestIdentityProof(() => { }, () => { });
368
+ }
369
+ });
370
+ }
371
+ else if (identityProofConfig.enforceOnConnect) {
372
+ // No stored token but enforceOnConnect is enabled - trigger auth flow
373
+ requestIdentityProof(() => { }, () => { });
374
+ }
375
+ }, [session, identityProof, identityProofConfig, config.requesterAccount, validateStoredToken, requestIdentityProof]);
312
376
  // Handle profile callbacks
313
377
  useEffect(() => {
314
378
  if (profile && onProfileRef.current) {
315
379
  onProfileRef.current(profile);
316
- onProfileRef.current = undefined; // Reset the ref
380
+ onProfileRef.current = undefined;
317
381
  }
318
382
  }, [profile]);
319
- // Session restoration - only restore active session (runs once on mount)
383
+ // Session restoration on mount
320
384
  useEffect(() => {
321
385
  if (isRestoringRef.current)
322
386
  return;
323
- if (sessionsRef.current.size === 0 && config.restoreSession) {
324
- // Check if there's a stored active session
325
- if (typeof window !== "undefined") {
326
- const storedActiveActor = localStorage.getItem("xprn_active_session_actor");
327
- if (storedActiveActor) {
328
- isRestoringRef.current = true;
329
- // Try to restore the session
330
- connect(true);
331
- }
332
- }
387
+ if (!session && config.restoreSession) {
388
+ isRestoringRef.current = true;
389
+ connect(true);
333
390
  }
334
- }, [config.restoreSession, connect]);
391
+ }, [config.restoreSession, connect, session]);
335
392
  const providerValue = useMemo(() => {
336
393
  return {
337
- // Backward compatibility - active session values (reactive via version)
394
+ // Config
395
+ config,
396
+ // Session state
338
397
  session,
339
398
  link,
340
399
  profile,
341
400
  rpc: jsonRpcRef.current,
342
- addTransactionError: addTxError,
401
+ // Identity proof state
402
+ identityProof,
403
+ identityProofStatus,
404
+ // Helper booleans
405
+ needsIdentityProof,
406
+ isIdentityProofRequired,
407
+ isIdentityProofEnabled,
408
+ // Core methods
343
409
  connect,
344
410
  disconnect,
345
- authenticate,
346
- authentication,
347
- // Multi-session management methods (stable references)
348
- getActiveSession,
349
- listSessions,
350
- setActiveSession,
351
- removeSession,
352
- getAllProfiles,
353
- switchSession,
354
- getSessionById,
355
- getSessionByActor,
356
- // Authentication status
357
- authStatus,
358
- getAuthStatus,
411
+ requestIdentityProof,
412
+ // Session switching
413
+ listStoredSessions,
414
+ switchToSession,
415
+ // Utility
416
+ addTransactionError: addTxError,
417
+ // Legacy aliases
418
+ authentication: identityProof,
419
+ authStatus: identityProofStatus,
420
+ authenticate: requestIdentityProof,
359
421
  };
360
422
  }, [
361
- // Reactive values (change when active session changes)
423
+ config,
362
424
  session,
363
425
  link,
364
426
  profile,
365
- authentication,
366
- authStatus,
367
- // Stable callbacks (only change when their specific deps change)
368
- addTxError,
427
+ identityProof,
428
+ identityProofStatus,
429
+ needsIdentityProof,
430
+ isIdentityProofRequired,
431
+ isIdentityProofEnabled,
369
432
  connect,
370
433
  disconnect,
371
- authenticate,
372
- getActiveSession,
373
- listSessions,
374
- setActiveSession,
375
- removeSession,
376
- getAllProfiles,
377
- switchSession,
378
- getSessionById,
379
- getSessionByActor,
380
- getAuthStatus,
434
+ requestIdentityProof,
435
+ listStoredSessions,
436
+ switchToSession,
437
+ addTxError,
381
438
  ]);
382
439
  return (_jsx(XPRNContext.Provider, { value: providerValue, children: children }));
383
440
  };