@rockerone/xprnkit 0.3.9 → 0.3.10
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/xprn-identity-proof-gate.d.ts +2 -9
- package/build/components/identity/xprn-identity-proof-gate.js +9 -14
- package/build/components/identity/xprn-session-name.js +6 -5
- package/build/providers/XPRNProvider.d.ts +0 -10
- package/build/providers/XPRNProvider.js +34 -33
- package/build/services/identity-proof/create-identity-proof.js +9 -1
- package/build/services/identity-proof/types.d.ts +0 -4
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ type XPRNIdentityProofGateProps = {
|
|
|
5
5
|
*/
|
|
6
6
|
children: React.ReactNode;
|
|
7
7
|
/**
|
|
8
|
-
* Content to render when identity proof is
|
|
8
|
+
* Content to render when identity proof is not obtained.
|
|
9
9
|
* If not provided, renders nothing.
|
|
10
10
|
*/
|
|
11
11
|
fallback?: React.ReactNode;
|
|
@@ -24,11 +24,6 @@ type XPRNIdentityProofGateProps = {
|
|
|
24
24
|
* If not provided, uses fallback.
|
|
25
25
|
*/
|
|
26
26
|
notConnected?: React.ReactNode;
|
|
27
|
-
/**
|
|
28
|
-
* If true, only gates when identityProof.required is true in config.
|
|
29
|
-
* If false (default), gates whenever identity proof is configured.
|
|
30
|
-
*/
|
|
31
|
-
onlyWhenRequired?: boolean;
|
|
32
27
|
};
|
|
33
28
|
/**
|
|
34
29
|
* Conditionally renders children based on identity proof status.
|
|
@@ -51,9 +46,7 @@ export declare const XPRNIdentityProofGate: React.FunctionComponent<XPRNIdentity
|
|
|
51
46
|
* Hook to check identity proof gate status
|
|
52
47
|
* Returns the same information used by XPRNIdentityProofGate
|
|
53
48
|
*/
|
|
54
|
-
export declare function useIdentityProofGate(
|
|
55
|
-
onlyWhenRequired?: boolean;
|
|
56
|
-
}): {
|
|
49
|
+
export declare function useIdentityProofGate(): {
|
|
57
50
|
isGateActive: boolean;
|
|
58
51
|
isConnected: boolean;
|
|
59
52
|
isInProgress: boolean;
|
|
@@ -17,16 +17,12 @@ import { useXPRN } from "../../providers/XPRNProvider";
|
|
|
17
17
|
* </XPRNIdentityProofGate>
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
|
-
export const XPRNIdentityProofGate = ({ children, fallback = null, loading, error, notConnected,
|
|
21
|
-
const { session, identityProof, identityProofStatus, isIdentityProofEnabled,
|
|
20
|
+
export const XPRNIdentityProofGate = ({ children, fallback = null, loading, error, notConnected, }) => {
|
|
21
|
+
const { session, identityProof, identityProofStatus, isIdentityProofEnabled, } = useXPRN();
|
|
22
22
|
// If identity proof is not enabled, render children directly
|
|
23
23
|
if (!isIdentityProofEnabled) {
|
|
24
24
|
return _jsx(_Fragment, { children: children });
|
|
25
25
|
}
|
|
26
|
-
// If onlyWhenRequired is true and identity proof is not required, render children
|
|
27
|
-
if (onlyWhenRequired && !isIdentityProofRequired) {
|
|
28
|
-
return _jsx(_Fragment, { children: children });
|
|
29
|
-
}
|
|
30
26
|
// No session connected
|
|
31
27
|
if (!session) {
|
|
32
28
|
return _jsx(_Fragment, { children: notConnected ?? fallback });
|
|
@@ -51,26 +47,25 @@ XPRNIdentityProofGate.displayName = "XPRNIdentityProofGate";
|
|
|
51
47
|
* Hook to check identity proof gate status
|
|
52
48
|
* Returns the same information used by XPRNIdentityProofGate
|
|
53
49
|
*/
|
|
54
|
-
export function useIdentityProofGate(
|
|
55
|
-
const { session, identityProof, identityProofStatus, isIdentityProofEnabled,
|
|
56
|
-
const onlyWhenRequired = options?.onlyWhenRequired ?? false;
|
|
57
|
-
// Determine if gating should be active
|
|
58
|
-
const isGateActive = isIdentityProofEnabled && (!onlyWhenRequired || isIdentityProofRequired);
|
|
50
|
+
export function useIdentityProofGate() {
|
|
51
|
+
const { session, identityProof, identityProofStatus, isIdentityProofEnabled, requestIdentityProof, } = useXPRN();
|
|
59
52
|
// Determine current state
|
|
60
53
|
const isConnected = session !== null;
|
|
61
54
|
const isInProgress = identityProofStatus === "signing" || identityProofStatus === "verifying" || identityProofStatus === "validating";
|
|
62
55
|
const hasError = identityProofStatus === "error" || identityProofStatus === "expired";
|
|
63
56
|
const isVerified = identityProof !== null;
|
|
64
57
|
// Should render protected content?
|
|
65
|
-
const shouldRenderChildren = !
|
|
58
|
+
const shouldRenderChildren = !isIdentityProofEnabled || isVerified;
|
|
59
|
+
// Needs identity proof: enabled, connected, and not yet verified
|
|
60
|
+
const needsIdentityProof = isIdentityProofEnabled && isConnected && !isVerified;
|
|
66
61
|
return {
|
|
67
|
-
isGateActive,
|
|
62
|
+
isGateActive: isIdentityProofEnabled,
|
|
68
63
|
isConnected,
|
|
69
64
|
isInProgress,
|
|
70
65
|
hasError,
|
|
71
66
|
isVerified,
|
|
72
67
|
shouldRenderChildren,
|
|
73
|
-
needsIdentityProof
|
|
68
|
+
needsIdentityProof,
|
|
74
69
|
requestIdentityProof,
|
|
75
70
|
identityProofStatus,
|
|
76
71
|
};
|
|
@@ -7,7 +7,7 @@ import { useXPRN } from "../../providers/XPRNProvider";
|
|
|
7
7
|
* Get badge styling based on identity proof status
|
|
8
8
|
*/
|
|
9
9
|
function useIdentityProofBadgeStyle() {
|
|
10
|
-
const { identityProofStatus, isIdentityProofEnabled,
|
|
10
|
+
const { identityProofStatus, isIdentityProofEnabled, identityProof, session } = useXPRN();
|
|
11
11
|
return useMemo(() => {
|
|
12
12
|
if (!isIdentityProofEnabled) {
|
|
13
13
|
return { visible: false, className: "" };
|
|
@@ -18,6 +18,7 @@ function useIdentityProofBadgeStyle() {
|
|
|
18
18
|
const isSuccess = identityProofStatus === "success";
|
|
19
19
|
const isError = identityProofStatus === "error" || identityProofStatus === "expired";
|
|
20
20
|
const isIdle = identityProofStatus === "idle";
|
|
21
|
+
const needsIdentityProof = session !== null && identityProof === null;
|
|
21
22
|
// Build className based on status
|
|
22
23
|
let className = "";
|
|
23
24
|
if (isPending) {
|
|
@@ -42,12 +43,12 @@ function useIdentityProofBadgeStyle() {
|
|
|
42
43
|
isSuccess,
|
|
43
44
|
isError,
|
|
44
45
|
};
|
|
45
|
-
}, [identityProofStatus, isIdentityProofEnabled,
|
|
46
|
+
}, [identityProofStatus, isIdentityProofEnabled, identityProof, session]);
|
|
46
47
|
}
|
|
47
|
-
export const XPRNSessionName = ({ children, className, }) => {
|
|
48
|
-
const { profile, session
|
|
48
|
+
export const XPRNSessionName = ({ children, className, showIdentityProof = false, }) => {
|
|
49
|
+
const { profile, session } = useXPRN();
|
|
49
50
|
const badgeStyle = useIdentityProofBadgeStyle();
|
|
50
|
-
const showBadge =
|
|
51
|
+
const showBadge = showIdentityProof && badgeStyle.visible;
|
|
51
52
|
const rootClasses = classNames({
|
|
52
53
|
"flex gap-2 items-center justify-center": true,
|
|
53
54
|
[`${className}`]: className,
|
|
@@ -35,12 +35,8 @@ export type XPRNIdentityProofConfig = {
|
|
|
35
35
|
validationUrl?: string;
|
|
36
36
|
/** Time in seconds before expiration to trigger validation (default: 300) */
|
|
37
37
|
validationBuffer?: number;
|
|
38
|
-
/** Automatically authenticate on connect (default: false) */
|
|
39
|
-
enforceOnConnect?: boolean;
|
|
40
38
|
/** Automatically re-authenticate when token expires (default: false) */
|
|
41
39
|
autoReauthenticate?: boolean;
|
|
42
|
-
/** Require identity proof for the app (default: false) */
|
|
43
|
-
required?: boolean;
|
|
44
40
|
/** Additional headers for identity proof requests */
|
|
45
41
|
headers?: Record<string, string>;
|
|
46
42
|
/** Request timeout in milliseconds */
|
|
@@ -58,8 +54,6 @@ export type XPRProviderConfig = {
|
|
|
58
54
|
identityProof?: XPRNIdentityProofConfig;
|
|
59
55
|
/** @deprecated Use identityProof.createUrl instead */
|
|
60
56
|
authenticationUrl?: string;
|
|
61
|
-
/** @deprecated Use identityProof.enforceOnConnect instead */
|
|
62
|
-
enforceAuthentication?: boolean;
|
|
63
57
|
};
|
|
64
58
|
type XPRNProviderProps = {
|
|
65
59
|
children: React.ReactNode | React.ReactNode[];
|
|
@@ -73,10 +67,6 @@ type XPRNProviderContext = {
|
|
|
73
67
|
rpc: JsonRpc | null;
|
|
74
68
|
identityProof: XPRNIdentityProof | null;
|
|
75
69
|
identityProofStatus: IdentityProofStatus;
|
|
76
|
-
/** True if identity proof is configured but not yet obtained for current session */
|
|
77
|
-
needsIdentityProof: boolean;
|
|
78
|
-
/** True if identity proof is configured with required=true */
|
|
79
|
-
isIdentityProofRequired: boolean;
|
|
80
70
|
/** True if identity proof is enabled (config.identityProof is defined) */
|
|
81
71
|
isIdentityProofEnabled: boolean;
|
|
82
72
|
connect: (restore?: boolean, onSession?: (session: LinkSession, link: ProtonWebLink | Link) => void, onProfile?: (profile: XPRNProfile) => void, onIdentityProof?: (proof: XPRNIdentityProof) => void, onIdentityProofError?: (error: Error) => void) => void;
|
|
@@ -14,8 +14,6 @@ const XPRNContext = React.createContext({
|
|
|
14
14
|
rpc: null,
|
|
15
15
|
identityProof: null,
|
|
16
16
|
identityProofStatus: "idle",
|
|
17
|
-
needsIdentityProof: false,
|
|
18
|
-
isIdentityProofRequired: false,
|
|
19
17
|
isIdentityProofEnabled: false,
|
|
20
18
|
connect: () => { },
|
|
21
19
|
disconnect: async () => { },
|
|
@@ -44,15 +42,12 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
44
42
|
if (config.authenticationUrl) {
|
|
45
43
|
return {
|
|
46
44
|
createUrl: config.authenticationUrl,
|
|
47
|
-
enforceOnConnect: config.enforceAuthentication,
|
|
48
45
|
};
|
|
49
46
|
}
|
|
50
47
|
return null;
|
|
51
|
-
}, [config.identityProof, config.authenticationUrl
|
|
52
|
-
// Helper
|
|
48
|
+
}, [config.identityProof, config.authenticationUrl]);
|
|
49
|
+
// Helper boolean
|
|
53
50
|
const isIdentityProofEnabled = identityProofConfig !== null;
|
|
54
|
-
const isIdentityProofRequired = identityProofConfig?.required ?? false;
|
|
55
|
-
const needsIdentityProof = isIdentityProofEnabled && session !== null && identityProof === null;
|
|
56
51
|
// Internal refs
|
|
57
52
|
const jsonRpcRef = useRef(new JsonRpc(config.endpoints));
|
|
58
53
|
const errorsStackRef = useRef([]);
|
|
@@ -64,8 +59,12 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
64
59
|
const isAuthenticatingRef = useRef(false);
|
|
65
60
|
const isSwitchingSessionRef = useRef(false);
|
|
66
61
|
const sessionRef = useRef(null);
|
|
67
|
-
|
|
62
|
+
const linkRef = useRef(null);
|
|
63
|
+
// Queue for pending callbacks when requestIdentityProof is called while already in progress
|
|
64
|
+
const pendingCallbacksRef = useRef([]);
|
|
65
|
+
// Keep refs in sync with state
|
|
68
66
|
sessionRef.current = session;
|
|
67
|
+
linkRef.current = link;
|
|
69
68
|
// List stored sessions from proton-web-sdk localStorage
|
|
70
69
|
const listStoredSessions = useCallback(() => {
|
|
71
70
|
return sessionStorage.getLinkList(config.requesterAccount);
|
|
@@ -222,12 +221,15 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
222
221
|
});
|
|
223
222
|
}, [config]);
|
|
224
223
|
const disconnect = useCallback(async () => {
|
|
225
|
-
|
|
224
|
+
// Use refs to get latest values (avoid stale closure issues)
|
|
225
|
+
const currentSession = sessionRef.current;
|
|
226
|
+
const currentLink = linkRef.current;
|
|
227
|
+
if (!currentSession || !currentLink) {
|
|
226
228
|
console.warn("No session to disconnect");
|
|
227
229
|
return;
|
|
228
230
|
}
|
|
229
231
|
try {
|
|
230
|
-
await
|
|
232
|
+
await currentLink.removeSession(config.requesterAccount, currentSession.auth, currentSession.chainId);
|
|
231
233
|
}
|
|
232
234
|
catch (e) {
|
|
233
235
|
console.error("Error removing session:", e);
|
|
@@ -238,7 +240,7 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
238
240
|
setIdentityProof(null);
|
|
239
241
|
setIdentityProofStatus("idle");
|
|
240
242
|
// Note: Keep link reference for potential session switching
|
|
241
|
-
}, [
|
|
243
|
+
}, [config.requesterAccount]);
|
|
242
244
|
const addTxError = useCallback((message) => {
|
|
243
245
|
errorsStackRef.current.push(parseTransactionErrorMessage(message));
|
|
244
246
|
}, []);
|
|
@@ -253,8 +255,9 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
253
255
|
fail(new Error("Identity proof not configured"));
|
|
254
256
|
return;
|
|
255
257
|
}
|
|
258
|
+
// Idempotent: if already in progress, queue callbacks to be called when complete
|
|
256
259
|
if (isAuthenticatingRef.current) {
|
|
257
|
-
|
|
260
|
+
pendingCallbacksRef.current.push({ success, fail });
|
|
258
261
|
return;
|
|
259
262
|
}
|
|
260
263
|
isAuthenticatingRef.current = true;
|
|
@@ -286,6 +289,12 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
286
289
|
if (onIdentityProofRef.current) {
|
|
287
290
|
onIdentityProofRef.current(identityProofData);
|
|
288
291
|
}
|
|
292
|
+
// Call all queued callbacks with success
|
|
293
|
+
const pendingCallbacks = pendingCallbacksRef.current;
|
|
294
|
+
pendingCallbacksRef.current = [];
|
|
295
|
+
for (const cb of pendingCallbacks) {
|
|
296
|
+
cb.success(identityProofData);
|
|
297
|
+
}
|
|
289
298
|
}
|
|
290
299
|
catch (error) {
|
|
291
300
|
setIdentityProofStatus("error");
|
|
@@ -294,6 +303,12 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
294
303
|
if (onIdentityProofErrorRef.current) {
|
|
295
304
|
onIdentityProofErrorRef.current(error instanceof Error ? error : new Error(String(error)));
|
|
296
305
|
}
|
|
306
|
+
// Call all queued callbacks with error
|
|
307
|
+
const pendingCallbacks = pendingCallbacksRef.current;
|
|
308
|
+
pendingCallbacksRef.current = [];
|
|
309
|
+
for (const cb of pendingCallbacks) {
|
|
310
|
+
cb.fail(error);
|
|
311
|
+
}
|
|
297
312
|
}
|
|
298
313
|
finally {
|
|
299
314
|
isAuthenticatingRef.current = false;
|
|
@@ -359,7 +374,7 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
359
374
|
return false;
|
|
360
375
|
}
|
|
361
376
|
}, [identityProofConfig, config.requesterAccount]);
|
|
362
|
-
// Handle token restoration
|
|
377
|
+
// Handle token restoration when session is established (no auto-authentication)
|
|
363
378
|
// Note: This effect is skipped when switchToSession is handling the identity proof state
|
|
364
379
|
useEffect(() => {
|
|
365
380
|
// Skip if we're in the middle of a session switch (switchToSession handles this)
|
|
@@ -367,28 +382,19 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
367
382
|
return;
|
|
368
383
|
if (!session || identityProof)
|
|
369
384
|
return;
|
|
370
|
-
if (!identityProofConfig)
|
|
385
|
+
if (!identityProofConfig?.validationUrl)
|
|
371
386
|
return;
|
|
372
387
|
const actor = session.auth.actor.toString();
|
|
373
388
|
const permission = session.auth.permission.toString();
|
|
374
389
|
const chainId = session.chainId.toString();
|
|
375
|
-
// Check if there's a stored token and try to validate it
|
|
390
|
+
// Check if there's a stored token and try to validate it silently
|
|
376
391
|
const storedEntry = sessionStorage.get(config.requesterAccount, { actor, permission }, chainId);
|
|
377
392
|
const hasStoredToken = storedEntry?.identityProofToken && typeof storedEntry.identityProofToken === 'string' && storedEntry.identityProofToken.length > 0;
|
|
378
|
-
if (hasStoredToken
|
|
379
|
-
// Try to validate stored token silently
|
|
380
|
-
validateStoredToken(actor, permission, chainId)
|
|
381
|
-
if (!isValid && (identityProofConfig.enforceOnConnect || identityProofConfig.required)) {
|
|
382
|
-
// Token invalid and identity proof is required - trigger full auth flow
|
|
383
|
-
requestIdentityProof(() => { }, () => { });
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
else if (identityProofConfig.enforceOnConnect || identityProofConfig.required) {
|
|
388
|
-
// No stored token (or no validation URL) but identity proof is required - trigger auth flow
|
|
389
|
-
requestIdentityProof(() => { }, () => { });
|
|
393
|
+
if (hasStoredToken) {
|
|
394
|
+
// Try to validate stored token silently - dev handles the result via callbacks
|
|
395
|
+
validateStoredToken(actor, permission, chainId);
|
|
390
396
|
}
|
|
391
|
-
}, [session, identityProof, identityProofConfig, config.requesterAccount, validateStoredToken
|
|
397
|
+
}, [session, identityProof, identityProofConfig, config.requesterAccount, validateStoredToken]);
|
|
392
398
|
// Handle profile callbacks
|
|
393
399
|
useEffect(() => {
|
|
394
400
|
if (profile && onProfileRef.current) {
|
|
@@ -417,9 +423,6 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
417
423
|
// Identity proof state
|
|
418
424
|
identityProof,
|
|
419
425
|
identityProofStatus,
|
|
420
|
-
// Helper booleans
|
|
421
|
-
needsIdentityProof,
|
|
422
|
-
isIdentityProofRequired,
|
|
423
426
|
isIdentityProofEnabled,
|
|
424
427
|
// Core methods
|
|
425
428
|
connect,
|
|
@@ -442,8 +445,6 @@ export const XPRNProvider = ({ children, config, }) => {
|
|
|
442
445
|
profile,
|
|
443
446
|
identityProof,
|
|
444
447
|
identityProofStatus,
|
|
445
|
-
needsIdentityProof,
|
|
446
|
-
isIdentityProofRequired,
|
|
447
448
|
isIdentityProofEnabled,
|
|
448
449
|
connect,
|
|
449
450
|
disconnect,
|
|
@@ -28,7 +28,15 @@ export async function createIdentityProof(session, options) {
|
|
|
28
28
|
time: new Date().toISOString().slice(0, -1),
|
|
29
29
|
});
|
|
30
30
|
// Sign the transaction without broadcasting
|
|
31
|
-
|
|
31
|
+
let txResult;
|
|
32
|
+
try {
|
|
33
|
+
txResult = await session.transact({ actions: [authenticationAction] }, { broadcast: false });
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// Re-throw with clearer context for wallet signing failures
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
throw new Error(`Wallet signing failed: ${message}`);
|
|
39
|
+
}
|
|
32
40
|
// Check for abort after wallet interaction
|
|
33
41
|
if (options?.signal?.aborted) {
|
|
34
42
|
throw new DOMException("Aborted", "AbortError");
|
|
@@ -34,12 +34,8 @@ export type IdentityProofConfig = {
|
|
|
34
34
|
validationUrl?: string;
|
|
35
35
|
/** Time in seconds before expiration to trigger validation (default: 300 = 5 minutes) */
|
|
36
36
|
validationBuffer?: number;
|
|
37
|
-
/** Automatically authenticate on connect (default: false) */
|
|
38
|
-
enforceOnConnect?: boolean;
|
|
39
37
|
/** Automatically re-authenticate when token expires (default: false) */
|
|
40
38
|
autoReauthenticate?: boolean;
|
|
41
|
-
/** Require identity proof for the app - enables helper booleans and gate component (default: false) */
|
|
42
|
-
required?: boolean;
|
|
43
39
|
/** Additional headers for identity proof requests */
|
|
44
40
|
headers?: Record<string, string>;
|
|
45
41
|
/** Request timeout in milliseconds */
|