@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.
- package/build/components/identity/index.d.ts +2 -0
- package/build/components/identity/index.js +2 -0
- package/build/components/identity/xprn-account-list.d.ts +48 -0
- package/build/components/identity/xprn-account-list.js +106 -0
- package/build/components/identity/xprn-identity-proof-gate.d.ts +67 -0
- package/build/components/identity/xprn-identity-proof-gate.js +77 -0
- package/build/components/identity/xprn-identity.d.ts +4 -0
- package/build/components/identity/xprn-identity.js +5 -4
- package/build/components/identity/xprn-session-name.d.ts +23 -2
- package/build/components/identity/xprn-session-name.js +48 -3
- package/build/components/ui/dropdown.d.ts +3 -1
- package/build/components/ui/dropdown.js +4 -1
- package/build/components/xprn-transaction.js +3 -1
- package/build/global.css +118 -0
- package/build/providers/XPRNProvider.d.ts +60 -26
- package/build/providers/XPRNProvider.js +324 -267
- package/build/services/identity-proof/create-identity-proof.js +1 -0
- package/build/services/identity-proof/index.d.ts +5 -1
- package/build/services/identity-proof/index.js +3 -0
- package/build/services/identity-proof/token-utils.d.ts +48 -0
- package/build/services/identity-proof/token-utils.js +85 -0
- package/build/services/identity-proof/types.d.ts +25 -2
- package/build/services/identity-proof/use-identity-proof.js +5 -3
- package/build/services/identity-proof/validate-identity-proof.d.ts +51 -0
- package/build/services/identity-proof/validate-identity-proof.js +93 -0
- package/build/services/identity-proof/verify-identity-proof.d.ts +4 -4
- package/build/services/identity-proof/verify-identity-proof.js +15 -3
- package/build/utils/auth-storage.d.ts +126 -0
- package/build/utils/auth-storage.js +216 -0
- package/build/utils/index.d.ts +1 -0
- package/build/utils/index.js +1 -0
- 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,
|
|
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
|
-
|
|
15
|
+
identityProof: null,
|
|
16
|
+
identityProofStatus: "idle",
|
|
17
|
+
needsIdentityProof: false,
|
|
18
|
+
isIdentityProofRequired: false,
|
|
19
|
+
isIdentityProofEnabled: false,
|
|
15
20
|
connect: () => { },
|
|
16
|
-
disconnect: () => { },
|
|
17
|
-
|
|
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
|
-
|
|
29
|
+
authenticate: () => { },
|
|
30
30
|
});
|
|
31
31
|
export const XPRNProvider = ({ children, config, }) => {
|
|
32
|
-
//
|
|
33
|
-
const [
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
const
|
|
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
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
//
|
|
140
|
+
// Fetch profile for new session
|
|
141
|
+
const api = new ApiClass(config.apiMode === "testnet" ? "proton-test" : "proton");
|
|
107
142
|
try {
|
|
108
|
-
await
|
|
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 (
|
|
111
|
-
console.error("
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("Failed to fetch profile after session switch:", error);
|
|
112
154
|
}
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
199
|
+
isKyc: profileRes.kyc.length > 0,
|
|
174
200
|
};
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
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
|
|
192
|
-
const disconnect = useCallback(async (
|
|
193
|
-
|
|
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
|
-
|
|
201
|
-
|
|
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
|
|
206
|
-
|
|
207
|
-
|
|
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 (!
|
|
218
|
-
fail(new Error("
|
|
244
|
+
if (!identityProofConfig) {
|
|
245
|
+
fail(new Error("Identity proof not configured"));
|
|
219
246
|
return;
|
|
220
247
|
}
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
const proof = await createIdentityProof(
|
|
242
|
-
signal: abortController.signal,
|
|
243
|
-
});
|
|
255
|
+
setIdentityProofStatus("signing");
|
|
256
|
+
const proof = await createIdentityProof(session);
|
|
244
257
|
// Step 2: Verify with backend
|
|
245
|
-
|
|
246
|
-
const
|
|
247
|
-
// Step 3: Update
|
|
248
|
-
const
|
|
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
|
-
|
|
264
|
+
token: verifyRes.token,
|
|
265
|
+
data: verifyRes,
|
|
252
266
|
};
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
261
|
-
updateStatus("success");
|
|
262
|
-
success(authRes);
|
|
276
|
+
success(identityProofData);
|
|
263
277
|
}
|
|
264
278
|
catch (error) {
|
|
265
|
-
|
|
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
|
-
|
|
276
|
-
if (authAbortControllersRef.current.get(targetActor) === abortController) {
|
|
277
|
-
authAbortControllersRef.current.delete(targetActor);
|
|
278
|
-
}
|
|
283
|
+
isAuthenticatingRef.current = false;
|
|
279
284
|
}
|
|
280
|
-
}, [config.
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
341
|
+
catch (error) {
|
|
342
|
+
console.error("[XPRNProvider] Token validation error:", error);
|
|
343
|
+
setIdentityProofStatus("error");
|
|
344
|
+
return false;
|
|
298
345
|
}
|
|
299
|
-
}, [
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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;
|
|
380
|
+
onProfileRef.current = undefined;
|
|
317
381
|
}
|
|
318
382
|
}, [profile]);
|
|
319
|
-
// Session restoration
|
|
383
|
+
// Session restoration on mount
|
|
320
384
|
useEffect(() => {
|
|
321
385
|
if (isRestoringRef.current)
|
|
322
386
|
return;
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
//
|
|
394
|
+
// Config
|
|
395
|
+
config,
|
|
396
|
+
// Session state
|
|
338
397
|
session,
|
|
339
398
|
link,
|
|
340
399
|
profile,
|
|
341
400
|
rpc: jsonRpcRef.current,
|
|
342
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
|
|
423
|
+
config,
|
|
362
424
|
session,
|
|
363
425
|
link,
|
|
364
426
|
profile,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
427
|
+
identityProof,
|
|
428
|
+
identityProofStatus,
|
|
429
|
+
needsIdentityProof,
|
|
430
|
+
isIdentityProofRequired,
|
|
431
|
+
isIdentityProofEnabled,
|
|
369
432
|
connect,
|
|
370
433
|
disconnect,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
};
|