@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.
@@ -5,7 +5,7 @@ type XPRNIdentityProofGateProps = {
5
5
  */
6
6
  children: React.ReactNode;
7
7
  /**
8
- * Content to render when identity proof is required but not yet obtained.
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(options?: {
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, onlyWhenRequired = false, }) => {
21
- const { session, identityProof, identityProofStatus, isIdentityProofEnabled, isIdentityProofRequired, } = useXPRN();
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(options) {
55
- const { session, identityProof, identityProofStatus, isIdentityProofEnabled, isIdentityProofRequired, needsIdentityProof, requestIdentityProof, } = useXPRN();
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 = !isGateActive || isVerified;
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: isGateActive && 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, needsIdentityProof } = useXPRN();
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, needsIdentityProof]);
46
+ }, [identityProofStatus, isIdentityProofEnabled, identityProof, session]);
46
47
  }
47
- export const XPRNSessionName = ({ children, className, }) => {
48
- const { profile, session, config } = useXPRN();
48
+ export const XPRNSessionName = ({ children, className, showIdentityProof = false, }) => {
49
+ const { profile, session } = useXPRN();
49
50
  const badgeStyle = useIdentityProofBadgeStyle();
50
- const showBadge = (config?.identityProof?.required || config?.identityProof?.enforceOnConnect) && badgeStyle.visible;
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, config.enforceAuthentication]);
52
- // Helper booleans
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
- // Keep sessionRef in sync with session state
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
- if (!session || !link) {
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 link.removeSession(config.requesterAccount, session.auth, session.chainId);
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
- }, [session, link, config.requesterAccount]);
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
- fail(new Error("Identity proof already in progress"));
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 and auto-authentication when session is established
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 && identityProofConfig.validationUrl) {
379
- // Try to validate stored token silently
380
- validateStoredToken(actor, permission, chainId).then(isValid => {
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, requestIdentityProof]);
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
- const txResult = await session.transact({ actions: [authenticationAction] }, { broadcast: false });
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 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rockerone/xprnkit",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "source": "src/index.ts",
5
5
  "main": "build/index.js",
6
6
  "type": "module",