@rockerone/xprnkit 0.3.12 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +571 -834
- package/build/components/identity/xprn-account-list.d.ts +5 -5
- package/build/components/identity/xprn-account-list.js +15 -20
- package/build/components/identity/xprn-avatar.js +1 -1
- package/build/components/identity/xprn-identity-proof-gate.d.ts +5 -4
- package/build/components/identity/xprn-identity-proof-gate.js +10 -12
- package/build/components/identity/xprn-identity.d.ts +10 -0
- package/build/components/identity/xprn-identity.js +3 -3
- package/build/components/identity/xprn-session-actor.js +1 -1
- package/build/components/identity/xprn-session-name.d.ts +0 -6
- package/build/components/identity/xprn-session-name.js +6 -9
- package/build/components/swap/xprn-swap-provider.js +1 -1
- package/build/components/xprn-container.js +1 -1
- package/build/components/xprn-session.d.ts +10 -0
- package/build/components/xprn-session.js +15 -4
- package/build/components/xprn-transaction.js +5 -5
- package/build/global.css +4 -4
- package/build/hooks/index.d.ts +0 -0
- package/build/hooks/index.js +1 -0
- package/build/hooks/useIdentityProof.d.ts +2 -0
- package/build/hooks/useIdentityProof.js +14 -0
- package/build/hooks/useProfileList.d.ts +2 -0
- package/build/hooks/useProfileList.js +13 -0
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/interfaces/config.d.ts +12 -0
- package/build/interfaces/identity-proof.d.ts +28 -0
- package/build/interfaces/identity-proof.js +1 -0
- package/build/interfaces/index.d.ts +4 -0
- package/build/interfaces/index.js +4 -0
- package/build/interfaces/profile.d.ts +5 -0
- package/build/interfaces/profile.js +1 -0
- package/build/providers/xprnkit-provider.d.ts +23 -0
- package/build/providers/xprnkit-provider.js +224 -0
- package/build/services/identity-proof/authenticate-identity-proof.d.ts +2 -0
- package/build/services/identity-proof/authenticate-identity-proof.js +42 -0
- package/build/services/identity-proof/index.d.ts +3 -10
- package/build/services/identity-proof/index.js +3 -8
- package/build/services/identity-proof/request-identity-proof.d.ts +7 -0
- package/build/services/identity-proof/{create-identity-proof.js → request-identity-proof.js} +2 -17
- package/build/services/identity-proof/verify-identity-proof.d.ts +5 -22
- package/build/services/identity-proof/verify-identity-proof.js +58 -68
- package/build/utils/identity-proof-storage.d.ts +14 -0
- package/build/utils/identity-proof-storage.js +33 -0
- package/build/utils/index.d.ts +1 -1
- package/build/utils/index.js +1 -1
- package/build/utils/profile-storage.d.ts +17 -0
- package/build/utils/profile-storage.js +48 -0
- package/build/utils/storage-key.d.ts +1 -0
- package/build/utils/storage-key.js +3 -0
- package/build/utils/xprnkit-storage.d.ts +9 -0
- package/build/utils/xprnkit-storage.js +23 -0
- package/package.json +3 -2
- package/build/providers/XPRNProvider.d.ts +0 -96
- package/build/providers/XPRNProvider.js +0 -473
- package/build/services/identity-proof/create-identity-proof.d.ts +0 -23
- package/build/services/identity-proof/types.d.ts +0 -82
- package/build/services/identity-proof/use-identity-proof.d.ts +0 -38
- package/build/services/identity-proof/use-identity-proof.js +0 -145
- package/build/services/identity-proof/validate-identity-proof.d.ts +0 -51
- package/build/services/identity-proof/validate-identity-proof.js +0 -93
- package/build/utils/auth-storage.d.ts +0 -126
- package/build/utils/auth-storage.js +0 -216
- /package/build/{services/identity-proof/types.js → interfaces/config.js} +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { formatStorageKey } from "./storage-key";
|
|
2
|
+
export const XPRNKIT_STORAGE_PREFIX = "xprnkit-storage";
|
|
3
|
+
export class XPRNKitLinkStorage {
|
|
4
|
+
appIdentifier;
|
|
5
|
+
constructor(appIdentifier) {
|
|
6
|
+
this.appIdentifier = appIdentifier;
|
|
7
|
+
}
|
|
8
|
+
write(key, value) {
|
|
9
|
+
console.log('write to xprnkit-storage', key, value);
|
|
10
|
+
localStorage.setItem(formatStorageKey(XPRNKIT_STORAGE_PREFIX, this.appIdentifier, key), value);
|
|
11
|
+
return Promise.resolve();
|
|
12
|
+
}
|
|
13
|
+
read(key) {
|
|
14
|
+
console.log('read from xprnkit-storage', key);
|
|
15
|
+
const value = localStorage.getItem(formatStorageKey(XPRNKIT_STORAGE_PREFIX, this.appIdentifier, key));
|
|
16
|
+
return Promise.resolve(value);
|
|
17
|
+
}
|
|
18
|
+
remove(key) {
|
|
19
|
+
console.log('remove from xprnkit-storage', key);
|
|
20
|
+
localStorage.removeItem(formatStorageKey(XPRNKIT_STORAGE_PREFIX, this.appIdentifier, key));
|
|
21
|
+
return Promise.resolve();
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rockerone/xprnkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"style": "tailwindcss -i ./src/global.css -o ./build/global.css",
|
|
13
13
|
"bundle": "rimraf ./build && microbundle --jsx React.createElement --jsxFragment React.Fragment",
|
|
14
14
|
"dev": "microbundle watch",
|
|
15
|
-
"types:bc": "bunx abi2ts proton.wrap -r proton_wrap > ./src/interfaces/proton_wrap.ts"
|
|
15
|
+
"types:bc": "bunx abi2ts proton.wrap -r proton_wrap > ./src/interfaces/proton_wrap.ts",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
16
17
|
},
|
|
17
18
|
"files": [
|
|
18
19
|
"build",
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import type { Link, LinkSession } from "@proton/link";
|
|
2
|
-
import { ProtonWebLink } from "@proton/web-sdk";
|
|
3
|
-
import React from "react";
|
|
4
|
-
import { JsonRpc } from "@proton/js";
|
|
5
|
-
import { type StoredSessionRef } from "../utils";
|
|
6
|
-
import { type IdentityProofStatus } from "../services/identity-proof";
|
|
7
|
-
/**
|
|
8
|
-
* Identity proof data returned from authentication
|
|
9
|
-
* All fields are optional - dev can store whatever they want
|
|
10
|
-
*/
|
|
11
|
-
export type XPRNIdentityProof = {
|
|
12
|
-
actor?: string;
|
|
13
|
-
/** Public key (optional - not available for browser/WebAuth wallets) */
|
|
14
|
-
publicKey?: string;
|
|
15
|
-
/** JWT token from the identity proof verification */
|
|
16
|
-
token?: string;
|
|
17
|
-
/** Additional data from the verification response */
|
|
18
|
-
data?: any;
|
|
19
|
-
/** Allow any additional fields for custom flows */
|
|
20
|
-
[key: string]: any;
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* @deprecated Use XPRNIdentityProof instead
|
|
24
|
-
*/
|
|
25
|
-
export type XPRNAuthentication = XPRNIdentityProof;
|
|
26
|
-
export type XPRNProfile = {
|
|
27
|
-
displayName: string;
|
|
28
|
-
avatar?: string;
|
|
29
|
-
isKyc: boolean;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Identity proof configuration (optional feature)
|
|
33
|
-
*/
|
|
34
|
-
export type XPRNIdentityProofConfig = {
|
|
35
|
-
/** URL for creating identity proof (sign + verify) */
|
|
36
|
-
createUrl: string;
|
|
37
|
-
/** URL for validating existing tokens (optional) */
|
|
38
|
-
validationUrl?: string;
|
|
39
|
-
/** Time in seconds before expiration to trigger validation (default: 300) */
|
|
40
|
-
validationBuffer?: number;
|
|
41
|
-
/** Automatically re-authenticate when token expires (default: false) */
|
|
42
|
-
autoReauthenticate?: boolean;
|
|
43
|
-
/** Additional headers for identity proof requests */
|
|
44
|
-
headers?: Record<string, string>;
|
|
45
|
-
/** Request timeout in milliseconds */
|
|
46
|
-
timeout?: number;
|
|
47
|
-
};
|
|
48
|
-
export type XPRProviderConfig = {
|
|
49
|
-
chainId: string;
|
|
50
|
-
endpoints: string[];
|
|
51
|
-
dAppName: string;
|
|
52
|
-
requesterAccount: string;
|
|
53
|
-
requesterLogo?: string;
|
|
54
|
-
apiMode: "testnet" | "mainnet";
|
|
55
|
-
restoreSession?: boolean;
|
|
56
|
-
/** Identity proof configuration (optional - if not provided, identity proof is disabled) */
|
|
57
|
-
identityProof?: XPRNIdentityProofConfig;
|
|
58
|
-
/** @deprecated Use identityProof.createUrl instead */
|
|
59
|
-
authenticationUrl?: string;
|
|
60
|
-
};
|
|
61
|
-
type XPRNProviderProps = {
|
|
62
|
-
children: React.ReactNode | React.ReactNode[];
|
|
63
|
-
config: XPRProviderConfig;
|
|
64
|
-
};
|
|
65
|
-
type XPRNProviderContext = {
|
|
66
|
-
config: XPRProviderConfig | null;
|
|
67
|
-
session: LinkSession | null;
|
|
68
|
-
link: ProtonWebLink | Link | null;
|
|
69
|
-
profile: XPRNProfile | null;
|
|
70
|
-
rpc: JsonRpc | null;
|
|
71
|
-
identityProof: XPRNIdentityProof | null;
|
|
72
|
-
identityProofStatus: IdentityProofStatus;
|
|
73
|
-
/** True if identity proof is enabled (config.identityProof is defined) */
|
|
74
|
-
isIdentityProofEnabled: boolean;
|
|
75
|
-
connect: (restore?: boolean, onSession?: (session: LinkSession, link: ProtonWebLink | Link) => void, onProfile?: (profile: XPRNProfile) => void, onIdentityProof?: (proof: XPRNIdentityProof) => void, onIdentityProofError?: (error: Error) => void) => void;
|
|
76
|
-
disconnect: () => Promise<void>;
|
|
77
|
-
requestIdentityProof: (success: (res: XPRNIdentityProof) => void, fail: (e: any) => void) => void;
|
|
78
|
-
/** Set identity proof data manually (for custom auth flows) */
|
|
79
|
-
setIdentityProof: (data: XPRNIdentityProof | null) => void;
|
|
80
|
-
/** Clear identity proof and reset status to idle */
|
|
81
|
-
clearIdentityProof: () => void;
|
|
82
|
-
/** Set identity proof status manually (for custom auth flows) */
|
|
83
|
-
setIdentityProofStatus: (status: IdentityProofStatus) => void;
|
|
84
|
-
listStoredSessions: () => StoredSessionRef[];
|
|
85
|
-
switchToSession: (auth: string, chainId: string) => Promise<void>;
|
|
86
|
-
addTransactionError: (rawMessage: string) => void;
|
|
87
|
-
/** @deprecated Use identityProof instead */
|
|
88
|
-
authentication: XPRNIdentityProof | null;
|
|
89
|
-
/** @deprecated Use identityProofStatus instead */
|
|
90
|
-
authStatus: IdentityProofStatus;
|
|
91
|
-
/** @deprecated Use requestIdentityProof instead */
|
|
92
|
-
authenticate: (success: (res: any) => void, fail: (e: any) => void) => void;
|
|
93
|
-
};
|
|
94
|
-
export declare const XPRNProvider: React.FunctionComponent<XPRNProviderProps>;
|
|
95
|
-
export declare function useXPRN(): XPRNProviderContext;
|
|
96
|
-
export {};
|
|
@@ -1,473 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { ApiClass } from "@proton/api";
|
|
4
|
-
import ConnectWallet from "@proton/web-sdk";
|
|
5
|
-
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
|
|
6
|
-
import { JsonRpc } from "@proton/js";
|
|
7
|
-
import { sessionStorage, parseTransactionErrorMessage } from "../utils";
|
|
8
|
-
import { createIdentityProof, verifyIdentityProof, validateIdentityProof, parseToken, isTokenExpired, } from "../services/identity-proof";
|
|
9
|
-
const XPRNContext = React.createContext({
|
|
10
|
-
config: null,
|
|
11
|
-
session: null,
|
|
12
|
-
link: null,
|
|
13
|
-
profile: null,
|
|
14
|
-
rpc: null,
|
|
15
|
-
identityProof: null,
|
|
16
|
-
identityProofStatus: "idle",
|
|
17
|
-
isIdentityProofEnabled: false,
|
|
18
|
-
connect: () => { },
|
|
19
|
-
disconnect: async () => { },
|
|
20
|
-
requestIdentityProof: () => { },
|
|
21
|
-
setIdentityProof: () => { },
|
|
22
|
-
clearIdentityProof: () => { },
|
|
23
|
-
setIdentityProofStatus: () => { },
|
|
24
|
-
listStoredSessions: () => [],
|
|
25
|
-
switchToSession: async () => { },
|
|
26
|
-
addTransactionError: () => { },
|
|
27
|
-
// Legacy
|
|
28
|
-
authentication: null,
|
|
29
|
-
authStatus: "idle",
|
|
30
|
-
authenticate: () => { },
|
|
31
|
-
});
|
|
32
|
-
export const XPRNProvider = ({ children, config, }) => {
|
|
33
|
-
// Simple state for single active session
|
|
34
|
-
const [session, setSession] = useState(null);
|
|
35
|
-
const [link, setLink] = useState(null);
|
|
36
|
-
const [profile, setProfile] = useState(null);
|
|
37
|
-
const [identityProof, setIdentityProof] = useState(null);
|
|
38
|
-
const [identityProofStatus, setIdentityProofStatus] = useState("idle");
|
|
39
|
-
// Compute identity proof config (supports both new and legacy config)
|
|
40
|
-
const identityProofConfig = useMemo(() => {
|
|
41
|
-
if (config.identityProof) {
|
|
42
|
-
return config.identityProof;
|
|
43
|
-
}
|
|
44
|
-
// Legacy support
|
|
45
|
-
if (config.authenticationUrl) {
|
|
46
|
-
return {
|
|
47
|
-
createUrl: config.authenticationUrl,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
return null;
|
|
51
|
-
}, [config.identityProof, config.authenticationUrl]);
|
|
52
|
-
// Helper boolean
|
|
53
|
-
const isIdentityProofEnabled = identityProofConfig !== null;
|
|
54
|
-
// Internal refs
|
|
55
|
-
const jsonRpcRef = useRef(new JsonRpc(config.endpoints));
|
|
56
|
-
const errorsStackRef = useRef([]);
|
|
57
|
-
const onSessionRef = useRef();
|
|
58
|
-
const onProfileRef = useRef();
|
|
59
|
-
const onIdentityProofRef = useRef();
|
|
60
|
-
const onIdentityProofErrorRef = useRef();
|
|
61
|
-
const isRestoringRef = useRef(false);
|
|
62
|
-
const isAuthenticatingRef = useRef(false);
|
|
63
|
-
const isSwitchingSessionRef = useRef(false);
|
|
64
|
-
const sessionRef = useRef(null);
|
|
65
|
-
const linkRef = useRef(null);
|
|
66
|
-
// Queue for pending callbacks when requestIdentityProof is called while already in progress
|
|
67
|
-
const pendingCallbacksRef = useRef([]);
|
|
68
|
-
// Keep refs in sync with state
|
|
69
|
-
sessionRef.current = session;
|
|
70
|
-
linkRef.current = link;
|
|
71
|
-
// List stored sessions from proton-web-sdk localStorage
|
|
72
|
-
const listStoredSessions = useCallback(() => {
|
|
73
|
-
return sessionStorage.getLinkList(config.requesterAccount);
|
|
74
|
-
}, [config.requesterAccount]);
|
|
75
|
-
// Switch to a different stored session using link.restoreSession
|
|
76
|
-
const switchToSession = useCallback(async (auth, chainId) => {
|
|
77
|
-
if (!link) {
|
|
78
|
-
console.warn("Cannot switch session: No link available. Call connect() first.");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
// Mark that we're switching sessions to prevent useEffect interference
|
|
82
|
-
isSwitchingSessionRef.current = true;
|
|
83
|
-
// Parse auth string (format: "actor@permission")
|
|
84
|
-
const [actor, permission] = auth.split('@');
|
|
85
|
-
const authObj = { actor, permission };
|
|
86
|
-
// Use link.restoreSession to restore the session
|
|
87
|
-
// Both Link and ProtonWebLink support this method (with different return types)
|
|
88
|
-
const restoredSession = await link.restoreSession(config.requesterAccount, authObj, chainId);
|
|
89
|
-
if (!restoredSession) {
|
|
90
|
-
throw new Error(`Failed to restore session for ${auth} on chain ${chainId}`);
|
|
91
|
-
}
|
|
92
|
-
// Clear identity proof state immediately for the new session
|
|
93
|
-
setSession(restoredSession);
|
|
94
|
-
// Check if the new session has a stored identity proof token
|
|
95
|
-
const storedEntry = sessionStorage.get(config.requesterAccount, { actor: actor, permission: permission }, chainId);
|
|
96
|
-
if (storedEntry?.identityProofToken && identityProofConfig?.validationUrl) {
|
|
97
|
-
// New session has a stored token - validate it
|
|
98
|
-
setIdentityProofStatus("validating");
|
|
99
|
-
try {
|
|
100
|
-
const tokenClaims = parseToken(storedEntry.identityProofToken);
|
|
101
|
-
if (tokenClaims && !isTokenExpired(tokenClaims)) {
|
|
102
|
-
// Token exists and not expired - validate with backend
|
|
103
|
-
const result = await validateIdentityProof({
|
|
104
|
-
validationUrl: identityProofConfig.validationUrl,
|
|
105
|
-
token: storedEntry.identityProofToken,
|
|
106
|
-
headers: identityProofConfig.headers,
|
|
107
|
-
timeout: identityProofConfig.timeout,
|
|
108
|
-
});
|
|
109
|
-
if (result.valid) {
|
|
110
|
-
const newToken = result.token || storedEntry.identityProofToken;
|
|
111
|
-
setIdentityProof({
|
|
112
|
-
actor: actor,
|
|
113
|
-
token: newToken,
|
|
114
|
-
});
|
|
115
|
-
setIdentityProofStatus("success");
|
|
116
|
-
// Update token in storage if refreshed
|
|
117
|
-
if (result.token && result.token !== storedEntry.identityProofToken) {
|
|
118
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor: actor, permission: permission }, chainId, result.token);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
// Token invalid - clear it
|
|
123
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor: actor, permission: permission }, chainId, null);
|
|
124
|
-
setIdentityProof(null);
|
|
125
|
-
setIdentityProofStatus("expired");
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
// Token expired or invalid - clear it
|
|
130
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor: actor, permission: permission }, chainId, null);
|
|
131
|
-
setIdentityProof(null);
|
|
132
|
-
setIdentityProofStatus("expired");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
console.error("Failed to validate token during session switch:", error);
|
|
137
|
-
setIdentityProof(null);
|
|
138
|
-
setIdentityProofStatus("error");
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
else {
|
|
142
|
-
// No stored token for this session - clear identity proof state
|
|
143
|
-
setIdentityProof(null);
|
|
144
|
-
setIdentityProofStatus("idle");
|
|
145
|
-
}
|
|
146
|
-
// Fetch profile for new session
|
|
147
|
-
const api = new ApiClass(config.apiMode === "testnet" ? "proton-test" : "proton");
|
|
148
|
-
try {
|
|
149
|
-
const profileRes = await api.getProtonAvatar(actor);
|
|
150
|
-
if (profileRes) {
|
|
151
|
-
setProfile({
|
|
152
|
-
displayName: profileRes.name,
|
|
153
|
-
avatar: profileRes.avatar,
|
|
154
|
-
isKyc: profileRes.kyc.length > 0,
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
console.error("Failed to fetch profile after session switch:", error);
|
|
160
|
-
}
|
|
161
|
-
// Trigger onSession callback if set
|
|
162
|
-
if (onSessionRef.current) {
|
|
163
|
-
onSessionRef.current(restoredSession, link);
|
|
164
|
-
onSessionRef.current = undefined;
|
|
165
|
-
}
|
|
166
|
-
// Mark session switch as complete
|
|
167
|
-
isSwitchingSessionRef.current = false;
|
|
168
|
-
}, [link, config.requesterAccount, config.apiMode, identityProofConfig]);
|
|
169
|
-
const connect = useCallback((restoreSession, onSession, onProfile, onIdentityProof, onIdentityProofError) => {
|
|
170
|
-
ConnectWallet({
|
|
171
|
-
linkOptions: {
|
|
172
|
-
endpoints: config.endpoints,
|
|
173
|
-
restoreSession: restoreSession || false,
|
|
174
|
-
},
|
|
175
|
-
selectorOptions: {
|
|
176
|
-
appName: config.dAppName,
|
|
177
|
-
},
|
|
178
|
-
transportOptions: {
|
|
179
|
-
requestAccount: config.requesterAccount,
|
|
180
|
-
},
|
|
181
|
-
}).then(res => {
|
|
182
|
-
onSessionRef.current = onSession;
|
|
183
|
-
onProfileRef.current = onProfile;
|
|
184
|
-
onIdentityProofRef.current = onIdentityProof;
|
|
185
|
-
onIdentityProofErrorRef.current = onIdentityProofError;
|
|
186
|
-
if (res.link && res.session) {
|
|
187
|
-
// Update state with new session
|
|
188
|
-
setLink(res.link);
|
|
189
|
-
setSession(res.session);
|
|
190
|
-
// Clear identity proof state for new session - it will be restored if stored token exists
|
|
191
|
-
setIdentityProof(null);
|
|
192
|
-
setIdentityProofStatus("idle");
|
|
193
|
-
const actor = res.session.auth.actor.toString();
|
|
194
|
-
// Trigger onSession callback
|
|
195
|
-
if (onSession) {
|
|
196
|
-
onSession(res.session, res.link);
|
|
197
|
-
}
|
|
198
|
-
// Fetch profile data
|
|
199
|
-
const api = new ApiClass(config.apiMode === "testnet" ? "proton-test" : "proton");
|
|
200
|
-
const permission = res.session.auth.permission.toString();
|
|
201
|
-
const chainId = res.session.chainId.toString();
|
|
202
|
-
api.getProtonAvatar(actor).then(profileRes => {
|
|
203
|
-
if (profileRes) {
|
|
204
|
-
const xprProfile = {
|
|
205
|
-
displayName: profileRes.name,
|
|
206
|
-
avatar: `${profileRes.avatar}`,
|
|
207
|
-
isKyc: profileRes.kyc.length > 0,
|
|
208
|
-
};
|
|
209
|
-
setProfile(xprProfile);
|
|
210
|
-
// Save session data to storage, preserving existing token if present
|
|
211
|
-
const existingEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
|
|
212
|
-
sessionStorage.save(config.requesterAccount, {
|
|
213
|
-
auth: { actor, permission },
|
|
214
|
-
chainId,
|
|
215
|
-
profile: xprProfile,
|
|
216
|
-
identityProofToken: existingEntry?.identityProofToken ?? null,
|
|
217
|
-
});
|
|
218
|
-
if (onProfile) {
|
|
219
|
-
onProfile(xprProfile);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}, [config]);
|
|
226
|
-
const disconnect = useCallback(async () => {
|
|
227
|
-
// Use refs to get latest values (avoid stale closure issues)
|
|
228
|
-
const currentSession = sessionRef.current;
|
|
229
|
-
const currentLink = linkRef.current;
|
|
230
|
-
if (!currentSession || !currentLink) {
|
|
231
|
-
console.warn("No session to disconnect");
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
await currentLink.removeSession(config.requesterAccount, currentSession.auth, currentSession.chainId);
|
|
236
|
-
}
|
|
237
|
-
catch (e) {
|
|
238
|
-
console.error("Error removing session:", e);
|
|
239
|
-
}
|
|
240
|
-
// Clear state
|
|
241
|
-
setSession(null);
|
|
242
|
-
setProfile(null);
|
|
243
|
-
setIdentityProof(null);
|
|
244
|
-
setIdentityProofStatus("idle");
|
|
245
|
-
// Note: Keep link reference for potential session switching
|
|
246
|
-
}, [config.requesterAccount]);
|
|
247
|
-
const addTxError = useCallback((message) => {
|
|
248
|
-
errorsStackRef.current.push(parseTransactionErrorMessage(message));
|
|
249
|
-
}, []);
|
|
250
|
-
const requestIdentityProof = useCallback(async (success, fail) => {
|
|
251
|
-
// Use ref to get the latest session value
|
|
252
|
-
const currentSession = sessionRef.current;
|
|
253
|
-
if (!currentSession) {
|
|
254
|
-
fail(new Error("No session available for identity proof"));
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
if (!identityProofConfig) {
|
|
258
|
-
fail(new Error("Identity proof not configured"));
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
// Idempotent: if already in progress, queue callbacks to be called when complete
|
|
262
|
-
if (isAuthenticatingRef.current) {
|
|
263
|
-
pendingCallbacksRef.current.push({ success, fail });
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
isAuthenticatingRef.current = true;
|
|
267
|
-
try {
|
|
268
|
-
// Step 1: Sign with wallet
|
|
269
|
-
setIdentityProofStatus("signing");
|
|
270
|
-
const proof = await createIdentityProof(currentSession);
|
|
271
|
-
// Step 2: Verify with backend
|
|
272
|
-
setIdentityProofStatus("verifying");
|
|
273
|
-
const verifyRes = await verifyIdentityProof(proof, { createUrl: identityProofConfig.createUrl, headers: identityProofConfig.headers });
|
|
274
|
-
// Step 3: Update state with identity proof data
|
|
275
|
-
const identityProofData = {
|
|
276
|
-
publicKey: proof.signer.publicKey,
|
|
277
|
-
actor: proof.signer.actor,
|
|
278
|
-
token: verifyRes.token,
|
|
279
|
-
data: verifyRes,
|
|
280
|
-
};
|
|
281
|
-
setIdentityProof(identityProofData);
|
|
282
|
-
setIdentityProofStatus("success");
|
|
283
|
-
// Save token to storage
|
|
284
|
-
if (verifyRes.token) {
|
|
285
|
-
const actor = currentSession.auth.actor.toString();
|
|
286
|
-
const permission = currentSession.auth.permission.toString();
|
|
287
|
-
const chainId = currentSession.chainId.toString();
|
|
288
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, verifyRes.token);
|
|
289
|
-
}
|
|
290
|
-
success(identityProofData);
|
|
291
|
-
// Call connect-level callback if registered
|
|
292
|
-
if (onIdentityProofRef.current) {
|
|
293
|
-
onIdentityProofRef.current(identityProofData);
|
|
294
|
-
}
|
|
295
|
-
// Call all queued callbacks with success
|
|
296
|
-
const pendingCallbacks = pendingCallbacksRef.current;
|
|
297
|
-
pendingCallbacksRef.current = [];
|
|
298
|
-
for (const cb of pendingCallbacks) {
|
|
299
|
-
cb.success(identityProofData);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
catch (error) {
|
|
303
|
-
setIdentityProofStatus("error");
|
|
304
|
-
fail(error);
|
|
305
|
-
// Call connect-level error callback if registered
|
|
306
|
-
if (onIdentityProofErrorRef.current) {
|
|
307
|
-
onIdentityProofErrorRef.current(error instanceof Error ? error : new Error(String(error)));
|
|
308
|
-
}
|
|
309
|
-
// Call all queued callbacks with error
|
|
310
|
-
const pendingCallbacks = pendingCallbacksRef.current;
|
|
311
|
-
pendingCallbacksRef.current = [];
|
|
312
|
-
for (const cb of pendingCallbacks) {
|
|
313
|
-
cb.fail(error);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
finally {
|
|
317
|
-
isAuthenticatingRef.current = false;
|
|
318
|
-
}
|
|
319
|
-
}, [identityProofConfig, config.requesterAccount]);
|
|
320
|
-
// Legacy alias
|
|
321
|
-
const authenticate = requestIdentityProof;
|
|
322
|
-
// Validate stored token silently
|
|
323
|
-
const validateStoredToken = useCallback(async (actor, permission, chainId) => {
|
|
324
|
-
if (!identityProofConfig?.validationUrl) {
|
|
325
|
-
return false;
|
|
326
|
-
}
|
|
327
|
-
// Get stored token from session storage
|
|
328
|
-
const storedEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
|
|
329
|
-
const storedToken = storedEntry?.identityProofToken;
|
|
330
|
-
if (!storedToken) {
|
|
331
|
-
return false;
|
|
332
|
-
}
|
|
333
|
-
// Parse token to check if it's expired
|
|
334
|
-
const tokenClaims = parseToken(storedToken);
|
|
335
|
-
if (!tokenClaims) {
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
// Check if token is expired
|
|
339
|
-
if (isTokenExpired(tokenClaims)) {
|
|
340
|
-
// Clear expired token from storage
|
|
341
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, null);
|
|
342
|
-
return false;
|
|
343
|
-
}
|
|
344
|
-
// Validate token with backend
|
|
345
|
-
setIdentityProofStatus("validating");
|
|
346
|
-
try {
|
|
347
|
-
const result = await validateIdentityProof({
|
|
348
|
-
validationUrl: identityProofConfig.validationUrl,
|
|
349
|
-
token: storedToken,
|
|
350
|
-
headers: identityProofConfig.headers,
|
|
351
|
-
timeout: identityProofConfig.timeout,
|
|
352
|
-
});
|
|
353
|
-
if (result.valid) {
|
|
354
|
-
// Token is valid - update state
|
|
355
|
-
const newToken = result.token || storedToken;
|
|
356
|
-
setIdentityProof({
|
|
357
|
-
actor,
|
|
358
|
-
token: newToken,
|
|
359
|
-
});
|
|
360
|
-
setIdentityProofStatus("success");
|
|
361
|
-
// Update token in storage if refreshed
|
|
362
|
-
if (result.token && result.token !== storedToken) {
|
|
363
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, result.token);
|
|
364
|
-
}
|
|
365
|
-
return true;
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
// Clear invalid token from storage
|
|
369
|
-
sessionStorage.updateIdentityProofToken(config.requesterAccount, { actor, permission }, chainId, null);
|
|
370
|
-
setIdentityProofStatus("expired");
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
catch (error) {
|
|
375
|
-
console.error("[XPRNProvider] Token validation error:", error);
|
|
376
|
-
setIdentityProofStatus("error");
|
|
377
|
-
return false;
|
|
378
|
-
}
|
|
379
|
-
}, [identityProofConfig, config.requesterAccount]);
|
|
380
|
-
// Handle token restoration when session is established (no auto-authentication)
|
|
381
|
-
// Note: This effect is skipped when switchToSession is handling the identity proof state
|
|
382
|
-
useEffect(() => {
|
|
383
|
-
// Skip if we're in the middle of a session switch (switchToSession handles this)
|
|
384
|
-
if (isSwitchingSessionRef.current)
|
|
385
|
-
return;
|
|
386
|
-
if (!session || identityProof)
|
|
387
|
-
return;
|
|
388
|
-
if (!identityProofConfig?.validationUrl)
|
|
389
|
-
return;
|
|
390
|
-
const actor = session.auth.actor.toString();
|
|
391
|
-
const permission = session.auth.permission.toString();
|
|
392
|
-
const chainId = session.chainId.toString();
|
|
393
|
-
// Check if there's a stored token and try to validate it silently
|
|
394
|
-
const storedEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
|
|
395
|
-
const hasStoredToken = storedEntry?.identityProofToken && typeof storedEntry.identityProofToken === 'string' && storedEntry.identityProofToken.length > 0;
|
|
396
|
-
if (hasStoredToken) {
|
|
397
|
-
// Try to validate stored token silently - dev handles the result via callbacks
|
|
398
|
-
validateStoredToken(actor, permission, chainId);
|
|
399
|
-
}
|
|
400
|
-
}, [session, identityProof, identityProofConfig, config.requesterAccount, validateStoredToken]);
|
|
401
|
-
// Handle profile callbacks
|
|
402
|
-
useEffect(() => {
|
|
403
|
-
if (profile && onProfileRef.current) {
|
|
404
|
-
onProfileRef.current(profile);
|
|
405
|
-
onProfileRef.current = undefined;
|
|
406
|
-
}
|
|
407
|
-
}, [profile]);
|
|
408
|
-
// Session restoration on mount
|
|
409
|
-
useEffect(() => {
|
|
410
|
-
if (isRestoringRef.current)
|
|
411
|
-
return;
|
|
412
|
-
if (!session && config.restoreSession) {
|
|
413
|
-
isRestoringRef.current = true;
|
|
414
|
-
connect(true);
|
|
415
|
-
}
|
|
416
|
-
}, [config.restoreSession, connect, session]);
|
|
417
|
-
// Helper to clear identity proof
|
|
418
|
-
const clearIdentityProof = useCallback(() => {
|
|
419
|
-
setIdentityProof(null);
|
|
420
|
-
setIdentityProofStatus("idle");
|
|
421
|
-
}, []);
|
|
422
|
-
const providerValue = useMemo(() => {
|
|
423
|
-
return {
|
|
424
|
-
// Config
|
|
425
|
-
config,
|
|
426
|
-
// Session state
|
|
427
|
-
session,
|
|
428
|
-
link,
|
|
429
|
-
profile,
|
|
430
|
-
rpc: jsonRpcRef.current,
|
|
431
|
-
// Identity proof state
|
|
432
|
-
identityProof,
|
|
433
|
-
identityProofStatus,
|
|
434
|
-
isIdentityProofEnabled,
|
|
435
|
-
// Core methods
|
|
436
|
-
connect,
|
|
437
|
-
disconnect,
|
|
438
|
-
requestIdentityProof,
|
|
439
|
-
// Identity proof setters (for custom flows)
|
|
440
|
-
setIdentityProof,
|
|
441
|
-
clearIdentityProof,
|
|
442
|
-
setIdentityProofStatus,
|
|
443
|
-
// Session switching
|
|
444
|
-
listStoredSessions,
|
|
445
|
-
switchToSession,
|
|
446
|
-
// Utility
|
|
447
|
-
addTransactionError: addTxError,
|
|
448
|
-
// Legacy aliases
|
|
449
|
-
authentication: identityProof,
|
|
450
|
-
authStatus: identityProofStatus,
|
|
451
|
-
authenticate: requestIdentityProof,
|
|
452
|
-
};
|
|
453
|
-
}, [
|
|
454
|
-
config,
|
|
455
|
-
session,
|
|
456
|
-
link,
|
|
457
|
-
profile,
|
|
458
|
-
identityProof,
|
|
459
|
-
identityProofStatus,
|
|
460
|
-
isIdentityProofEnabled,
|
|
461
|
-
connect,
|
|
462
|
-
disconnect,
|
|
463
|
-
requestIdentityProof,
|
|
464
|
-
clearIdentityProof,
|
|
465
|
-
listStoredSessions,
|
|
466
|
-
switchToSession,
|
|
467
|
-
addTxError,
|
|
468
|
-
]);
|
|
469
|
-
return (_jsx(XPRNContext.Provider, { value: providerValue, children: children }));
|
|
470
|
-
};
|
|
471
|
-
export function useXPRN() {
|
|
472
|
-
return useContext(XPRNContext);
|
|
473
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { LinkSession } from "@proton/web-sdk";
|
|
2
|
-
import type { IdentityProof } from "./types";
|
|
3
|
-
export type CreateIdentityProofOptions = {
|
|
4
|
-
/** AbortSignal for cancellation */
|
|
5
|
-
signal?: AbortSignal;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* Creates an identity proof by signing a generateauth action with the wallet.
|
|
9
|
-
*
|
|
10
|
-
* This is a pure function that handles only the wallet signing step.
|
|
11
|
-
* It does NOT send the proof to any backend - use `verifyIdentityProof` for that.
|
|
12
|
-
*
|
|
13
|
-
* @param session - The LinkSession from the connected wallet
|
|
14
|
-
* @param options - Optional configuration (abort signal)
|
|
15
|
-
* @returns Promise resolving to the signed IdentityProof
|
|
16
|
-
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```typescript
|
|
19
|
-
* const proof = await createIdentityProof(session);
|
|
20
|
-
* // proof contains: { signer, transaction, signatures }
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
export declare function createIdentityProof(session: LinkSession, options?: CreateIdentityProofOptions): Promise<IdentityProof>;
|