@rhinestone/1auth 0.7.4 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{chunk-IHBVEU33.mjs → chunk-6AHEN3FA.mjs} +1 -1
  2. package/dist/chunk-6AHEN3FA.mjs.map +1 -0
  3. package/dist/{chunk-UXBBB7AV.mjs → chunk-E4YZS7FZ.mjs} +68 -32
  4. package/dist/chunk-E4YZS7FZ.mjs.map +1 -0
  5. package/dist/{client-D4HVy1KF.d.mts → client-C8QSA1th.d.mts} +22 -17
  6. package/dist/{client-CuASptX3.d.ts → client-Dn6mL7BZ.d.ts} +22 -17
  7. package/dist/headless.d.mts +2 -2
  8. package/dist/headless.d.ts +2 -2
  9. package/dist/headless.js +4 -6
  10. package/dist/headless.js.map +1 -1
  11. package/dist/headless.mjs +4 -6
  12. package/dist/headless.mjs.map +1 -1
  13. package/dist/index.d.mts +163 -22
  14. package/dist/index.d.ts +163 -22
  15. package/dist/index.js +210 -116
  16. package/dist/index.js.map +1 -1
  17. package/dist/index.mjs +143 -86
  18. package/dist/index.mjs.map +1 -1
  19. package/dist/{provider-6JoziYDq.d.ts → provider-BsVmPgkQ.d.ts} +22 -3
  20. package/dist/{provider-CAQT7Gqx.d.mts → provider-CJv38fIK.d.mts} +22 -3
  21. package/dist/react.d.mts +9 -4
  22. package/dist/react.d.ts +9 -4
  23. package/dist/react.js +4 -7
  24. package/dist/react.js.map +1 -1
  25. package/dist/react.mjs +4 -7
  26. package/dist/react.mjs.map +1 -1
  27. package/dist/server.d.mts +2 -2
  28. package/dist/server.d.ts +2 -2
  29. package/dist/server.js.map +1 -1
  30. package/dist/server.mjs +1 -1
  31. package/dist/{types-C0jKNT_t.d.mts → types-Dzm5lZK-.d.mts} +33 -51
  32. package/dist/{types-C0jKNT_t.d.ts → types-Dzm5lZK-.d.ts} +33 -51
  33. package/dist/{verify-CnOwPq78.d.ts → verify-0VXQpQBJ.d.mts} +4 -6
  34. package/dist/{verify-aWdi5O2z.d.mts → verify-9UgxLSdo.d.ts} +4 -6
  35. package/dist/wagmi.d.mts +3 -3
  36. package/dist/wagmi.d.ts +3 -3
  37. package/dist/wagmi.js +67 -31
  38. package/dist/wagmi.js.map +1 -1
  39. package/dist/wagmi.mjs +1 -1
  40. package/package.json +1 -1
  41. package/dist/chunk-IHBVEU33.mjs.map +0 -1
  42. package/dist/chunk-UXBBB7AV.mjs.map +0 -1
@@ -1,5 +1,6 @@
1
- import { O as OneAuthClient } from './client-CuASptX3.js';
2
- import { a0 as CloseOnStatus } from './types-C0jKNT_t.js';
1
+ import { Address } from 'viem';
2
+ import { O as OneAuthClient } from './client-Dn6mL7BZ.js';
3
+ import { C as CloseOnStatus, h as SignerType } from './types-Dzm5lZK-.js';
3
4
 
4
5
  /**
5
6
  * EIP-1193 JSON-RPC provider factory for the 1auth passkey authentication system.
@@ -30,6 +31,24 @@ type OneAuthProvider = {
30
31
  on: (event: string, listener: Listener) => void;
31
32
  removeListener: (event: string, listener: Listener) => void;
32
33
  disconnect: () => Promise<void>;
34
+ /**
35
+ * Bind the active session in-memory and persist it to `localStorage`.
36
+ *
37
+ * Used by {@link createOneAuthConnection} to hand a freshly connected session
38
+ * to the provider the instant `connect()` resolves — before the app has
39
+ * written `1auth-user` itself. This closes a race where a provider built right
40
+ * after connect would read stale/empty storage and default an EOA session to
41
+ * passkey intents. Passing `null` clears the session (same effect as
42
+ * `disconnect()` minus the events).
43
+ *
44
+ * Existing `createOneAuthProvider` consumers never call this, so their
45
+ * behavior is unchanged: `getStoredUser` falls back to the localStorage read.
46
+ */
47
+ setSession: (session: {
48
+ address: Address;
49
+ signerType?: SignerType;
50
+ connectorId?: string;
51
+ } | null) => void;
33
52
  };
34
53
  type OneAuthProviderOptions = {
35
54
  client: OneAuthClient;
@@ -46,7 +65,7 @@ type OneAuthProviderOptions = {
46
65
  *
47
66
  * The returned provider object can be used anywhere a standard Ethereum provider is
48
67
  * accepted (e.g. passed to `createWalletClient` in viem, used as `window.ethereum`, or
49
- * supplied to ethers.js `BrowserProvider`). Connection state (address + username) is
68
+ * supplied to ethers.js `BrowserProvider`). Connection state (the account address) is
50
69
  * persisted in `localStorage` under the configured `storageKey`.
51
70
  *
52
71
  * @param options - Provider configuration
@@ -1,5 +1,6 @@
1
- import { O as OneAuthClient } from './client-D4HVy1KF.mjs';
2
- import { a0 as CloseOnStatus } from './types-C0jKNT_t.mjs';
1
+ import { Address } from 'viem';
2
+ import { O as OneAuthClient } from './client-C8QSA1th.mjs';
3
+ import { C as CloseOnStatus, h as SignerType } from './types-Dzm5lZK-.mjs';
3
4
 
4
5
  /**
5
6
  * EIP-1193 JSON-RPC provider factory for the 1auth passkey authentication system.
@@ -30,6 +31,24 @@ type OneAuthProvider = {
30
31
  on: (event: string, listener: Listener) => void;
31
32
  removeListener: (event: string, listener: Listener) => void;
32
33
  disconnect: () => Promise<void>;
34
+ /**
35
+ * Bind the active session in-memory and persist it to `localStorage`.
36
+ *
37
+ * Used by {@link createOneAuthConnection} to hand a freshly connected session
38
+ * to the provider the instant `connect()` resolves — before the app has
39
+ * written `1auth-user` itself. This closes a race where a provider built right
40
+ * after connect would read stale/empty storage and default an EOA session to
41
+ * passkey intents. Passing `null` clears the session (same effect as
42
+ * `disconnect()` minus the events).
43
+ *
44
+ * Existing `createOneAuthProvider` consumers never call this, so their
45
+ * behavior is unchanged: `getStoredUser` falls back to the localStorage read.
46
+ */
47
+ setSession: (session: {
48
+ address: Address;
49
+ signerType?: SignerType;
50
+ connectorId?: string;
51
+ } | null) => void;
33
52
  };
34
53
  type OneAuthProviderOptions = {
35
54
  client: OneAuthClient;
@@ -46,7 +65,7 @@ type OneAuthProviderOptions = {
46
65
  *
47
66
  * The returned provider object can be used anywhere a standard Ethereum provider is
48
67
  * accepted (e.g. passed to `createWalletClient` in viem, used as `window.ethereum`, or
49
- * supplied to ethers.js `BrowserProvider`). Connection state (address + username) is
68
+ * supplied to ethers.js `BrowserProvider`). Connection state (the account address) is
50
69
  * persisted in `localStorage` under the configured `storageKey`.
51
70
  *
52
71
  * @param options - Provider configuration
package/dist/react.d.mts CHANGED
@@ -1,15 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { O as OneAuthClient } from './client-D4HVy1KF.mjs';
4
- import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-C0jKNT_t.mjs';
3
+ import { O as OneAuthClient } from './client-C8QSA1th.mjs';
4
+ import { _ as SendIntentOptions, j as SendIntentResult, C as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-Dzm5lZK-.mjs';
5
5
  import 'viem';
6
6
  import '@rhinestone/sdk';
7
7
 
8
8
  interface PayButtonProps {
9
9
  /** The OneAuthClient instance */
10
10
  client: OneAuthClient;
11
- /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */
12
- intent: Omit<SendIntentOptions, "username" | "closeOn">;
11
+ /**
12
+ * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted
13
+ * because PayButton resolves it internally (cached session or auth modal) and
14
+ * overwrites it on the sendIntent call — callers pass only calls + target
15
+ * chain, keeping the drop-in payment API intact.
16
+ */
17
+ intent: Omit<SendIntentOptions, "closeOn" | "accountAddress">;
13
18
  /** Called when payment succeeds */
14
19
  onSuccess?: (result: SendIntentResult) => void;
15
20
  /** Called when payment fails */
package/dist/react.d.ts CHANGED
@@ -1,15 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { O as OneAuthClient } from './client-CuASptX3.js';
4
- import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-C0jKNT_t.js';
3
+ import { O as OneAuthClient } from './client-Dn6mL7BZ.js';
4
+ import { _ as SendIntentOptions, j as SendIntentResult, C as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-Dzm5lZK-.js';
5
5
  import 'viem';
6
6
  import '@rhinestone/sdk';
7
7
 
8
8
  interface PayButtonProps {
9
9
  /** The OneAuthClient instance */
10
10
  client: OneAuthClient;
11
- /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */
12
- intent: Omit<SendIntentOptions, "username" | "closeOn">;
11
+ /**
12
+ * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted
13
+ * because PayButton resolves it internally (cached session or auth modal) and
14
+ * overwrites it on the sendIntent call — callers pass only calls + target
15
+ * chain, keeping the drop-in payment API intact.
16
+ */
17
+ intent: Omit<SendIntentOptions, "closeOn" | "accountAddress">;
13
18
  /** Called when payment succeeds */
14
19
  onSuccess?: (result: SendIntentResult) => void;
15
20
  /** Called when payment fails */
package/dist/react.js CHANGED
@@ -160,27 +160,23 @@ function PayButton({
160
160
  }
161
161
  };
162
162
  const executePayment = async (forceReauth = false) => {
163
- let username = null;
164
163
  let accountAddress = null;
165
164
  if (!forceReauth) {
166
165
  const savedUser = localStorage.getItem("1auth-user");
167
166
  if (savedUser) {
168
167
  try {
169
168
  const parsed = JSON.parse(savedUser);
170
- username = parsed.username || null;
171
169
  accountAddress = parsed.address || null;
172
170
  } catch {
173
171
  localStorage.removeItem("1auth-user");
174
172
  }
175
173
  }
176
174
  }
177
- if (!username && !accountAddress) {
175
+ if (!accountAddress) {
178
176
  const authResult = await client.authWithModal();
179
177
  if (authResult.success && authResult.user) {
180
- username = authResult.user.username || null;
181
178
  accountAddress = authResult.user.address || null;
182
179
  const stored = {};
183
- if (username) stored.username = username;
184
180
  if (accountAddress) stored.address = accountAddress;
185
181
  if (authResult.signerType) stored.signerType = authResult.signerType;
186
182
  localStorage.setItem("1auth-user", JSON.stringify(stored));
@@ -192,8 +188,9 @@ function PayButton({
192
188
  try {
193
189
  result = await client.sendIntent({
194
190
  ...intent,
195
- username: username || void 0,
196
- accountAddress: accountAddress || void 0,
191
+ // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard
192
+ // above either resolved an address or bailed out before reaching here.
193
+ accountAddress,
197
194
  closeOn
198
195
  });
199
196
  } catch (err) {
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx","../src/sponsorship-fetch.ts"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */\n intent: Omit<SendIntentOptions, \"username\" | \"closeOn\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ username, address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached username/address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing user from localStorage\n let username: string | null = null;\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n username = parsed.username || null;\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no user (or forced reauth), authenticate first\n if (!username && !accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n username = authResult.user.username || null;\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (username) stored.username = username;\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n username: username || undefined,\n accountAddress: accountAddress || undefined,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n","/**\n * Shared helpers for fetching sponsorship tokens (access + extension) from an\n * integrator's backend endpoints.\n *\n * The integrator's token routes return their failure reason in the JSON body\n * (`{ error: \"createSponsorshipSigner: missing env var(s): ...\" }`). Surfacing\n * only the HTTP status — \"Failed to fetch access token (500 ...)\" — hides that\n * reason and turns every misconfiguration into the same opaque dialog error.\n * These helpers read the body and fold the server's message into the thrown\n * Error so the actual cause (missing env var, bad JWK, expired session) is\n * visible at the call site and in the dialog.\n */\n\n/**\n * Pull a human-readable reason out of a failed token response. Tries the JSON\n * `error`/`message` fields first, then falls back to a short text snippet, then\n * to the HTTP status text. Never throws — diagnostics must not mask the original\n * failure with a parse error.\n */\nasync function extractServerReason(response: Response): Promise<string> {\n try {\n // Clone so a JSON parse failure can still fall back to reading text.\n const text = await response.clone().text();\n if (!text) return response.statusText;\n try {\n const data = JSON.parse(text) as { error?: unknown; message?: unknown };\n const reason = data.error ?? data.message;\n if (typeof reason === \"string\" && reason.trim()) return reason;\n } catch {\n // Body wasn't JSON — fall through to the raw text snippet.\n }\n // Cap the snippet so an HTML error page doesn't flood the message.\n return text.slice(0, 200);\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Build an Error for a failed token fetch that includes the server's reason.\n * `label` names the token kind, e.g. \"access token\" or \"extension token\".\n */\nexport async function tokenFetchError(\n label: string,\n response: Response,\n): Promise<Error> {\n const reason = await extractServerReason(response);\n const suffix = reason ? `: ${reason}` : \"\";\n return new Error(\n `Failed to fetch ${label} (${response.status} ${response.statusText})${suffix}`,\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.tsx","../src/sponsorship-fetch.ts"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /**\n * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted\n * because PayButton resolves it internally (cached session or auth modal) and\n * overwrites it on the sendIntent call — callers pass only calls + target\n * chain, keeping the drop-in payment API intact.\n */\n intent: Omit<SendIntentOptions, \"closeOn\" | \"accountAddress\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached account address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing account address from localStorage\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no account address (or forced reauth), authenticate first\n if (!accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard\n // above either resolved an address or bailed out before reaching here.\n accountAddress,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n","/**\n * Shared helpers for fetching sponsorship tokens (access + extension) from an\n * integrator's backend endpoints.\n *\n * The integrator's token routes return their failure reason in the JSON body\n * (`{ error: \"createSponsorshipSigner: missing env var(s): ...\" }`). Surfacing\n * only the HTTP status — \"Failed to fetch access token (500 ...)\" — hides that\n * reason and turns every misconfiguration into the same opaque dialog error.\n * These helpers read the body and fold the server's message into the thrown\n * Error so the actual cause (missing env var, bad JWK, expired session) is\n * visible at the call site and in the dialog.\n */\n\n/**\n * Pull a human-readable reason out of a failed token response. Tries the JSON\n * `error`/`message` fields first, then falls back to a short text snippet, then\n * to the HTTP status text. Never throws — diagnostics must not mask the original\n * failure with a parse error.\n */\nasync function extractServerReason(response: Response): Promise<string> {\n try {\n // Clone so a JSON parse failure can still fall back to reading text.\n const text = await response.clone().text();\n if (!text) return response.statusText;\n try {\n const data = JSON.parse(text) as { error?: unknown; message?: unknown };\n const reason = data.error ?? data.message;\n if (typeof reason === \"string\" && reason.trim()) return reason;\n } catch {\n // Body wasn't JSON — fall through to the raw text snippet.\n }\n // Cap the snippet so an HTML error page doesn't flood the message.\n return text.slice(0, 200);\n } catch {\n return response.statusText;\n }\n}\n\n/**\n * Build an Error for a failed token fetch that includes the server's reason.\n * `label` names the token kind, e.g. \"access token\" or \"extension token\".\n */\nexport async function tokenFetchError(\n label: string,\n response: Response,\n): Promise<Error> {\n const reason = await extractServerReason(response);\n const suffix = reason ? `: ${reason}` : \"\";\n return new Error(\n `Failed to fetch ${label} (${response.status} ${response.statusText})${suffix}`,\n );\n}\n"],"mappings":"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","names":[]}
package/dist/react.mjs CHANGED
@@ -101,27 +101,23 @@ function PayButton({
101
101
  }
102
102
  };
103
103
  const executePayment = async (forceReauth = false) => {
104
- let username = null;
105
104
  let accountAddress = null;
106
105
  if (!forceReauth) {
107
106
  const savedUser = localStorage.getItem("1auth-user");
108
107
  if (savedUser) {
109
108
  try {
110
109
  const parsed = JSON.parse(savedUser);
111
- username = parsed.username || null;
112
110
  accountAddress = parsed.address || null;
113
111
  } catch {
114
112
  localStorage.removeItem("1auth-user");
115
113
  }
116
114
  }
117
115
  }
118
- if (!username && !accountAddress) {
116
+ if (!accountAddress) {
119
117
  const authResult = await client.authWithModal();
120
118
  if (authResult.success && authResult.user) {
121
- username = authResult.user.username || null;
122
119
  accountAddress = authResult.user.address || null;
123
120
  const stored = {};
124
- if (username) stored.username = username;
125
121
  if (accountAddress) stored.address = accountAddress;
126
122
  if (authResult.signerType) stored.signerType = authResult.signerType;
127
123
  localStorage.setItem("1auth-user", JSON.stringify(stored));
@@ -133,8 +129,9 @@ function PayButton({
133
129
  try {
134
130
  result = await client.sendIntent({
135
131
  ...intent,
136
- username: username || void 0,
137
- accountAddress: accountAddress || void 0,
132
+ // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard
133
+ // above either resolved an address or bailed out before reaching here.
134
+ accountAddress,
138
135
  closeOn
139
136
  });
140
137
  } catch (err) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/react.tsx"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /** Intent parameters (calls, targetChain, etc.) - username will be filled automatically */\n intent: Omit<SendIntentOptions, \"username\" | \"closeOn\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ username, address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached username/address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing user from localStorage\n let username: string | null = null;\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n username = parsed.username || null;\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no user (or forced reauth), authenticate first\n if (!username && !accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n username = authResult.user.username || null;\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (username) stored.username = username;\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n username: username || undefined,\n accountAddress: accountAddress || undefined,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/react.tsx"],"sourcesContent":["/**\n * @file React integration for the 1auth SDK.\n *\n * Exports the {@link PayButton} component — a ready-to-use payment button that\n * handles authentication, session recovery, and intent submission without\n * requiring the consuming app to manage auth state directly.\n *\n * @module @rhinestone/1auth/react\n */\n\nimport * as React from \"react\";\nimport { OneAuthClient } from \"./client\";\nimport { tokenFetchError } from \"./sponsorship-fetch\";\nimport type {\n SendIntentOptions,\n SendIntentResult,\n CloseOnStatus,\n SponsorshipCallbackConfig,\n SponsorshipUrlConfig,\n} from \"./types\";\n\n// Fingerprint icon SVG\nconst FingerprintIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4\" />\n <path d=\"M14 13.12c0 2.38 0 6.38-1 8.88\" />\n <path d=\"M17.29 21.02c.12-.6.43-2.3.5-3.02\" />\n <path d=\"M2 12a10 10 0 0 1 18-6\" />\n <path d=\"M2 16h.01\" />\n <path d=\"M21.8 16c.2-2 .131-5.354 0-6\" />\n <path d=\"M5 19.5C5.5 18 6 15 6 12a6 6 0 0 1 .34-2\" />\n <path d=\"M8.65 22c.21-.66.45-1.32.57-2\" />\n <path d=\"M9 6.8a6 6 0 0 1 9 5.2v2\" />\n </svg>\n);\n\n// Default styles\nconst defaultStyles: React.CSSProperties = {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: \"8px\",\n padding: \"16px 32px\",\n fontSize: \"16px\",\n fontWeight: 500,\n color: \"#ffffff\",\n backgroundColor: \"#18181b\",\n border: \"none\",\n borderRadius: \"9999px\",\n cursor: \"pointer\",\n transition: \"background-color 0.2s\",\n width: \"100%\",\n};\n\nconst defaultDisabledStyles: React.CSSProperties = {\n opacity: 0.5,\n cursor: \"not-allowed\",\n};\n\nconst defaultHoverStyles: React.CSSProperties = {\n backgroundColor: \"#27272a\",\n};\n\nconst defaultSuccessStyles: React.CSSProperties = {\n backgroundColor: \"#16a34a\",\n cursor: \"default\",\n};\n\n// Checkmark icon SVG\nconst CheckIcon = ({ className }: { className?: string }) => (\n <svg\n className={className}\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M20 6L9 17l-5-5\" />\n </svg>\n);\n\nexport interface PayButtonProps {\n /** The OneAuthClient instance */\n client: OneAuthClient;\n /**\n * Intent parameters (calls, targetChain, etc.). `accountAddress` is omitted\n * because PayButton resolves it internally (cached session or auth modal) and\n * overwrites it on the sendIntent call — callers pass only calls + target\n * chain, keeping the drop-in payment API intact.\n */\n intent: Omit<SendIntentOptions, \"closeOn\" | \"accountAddress\">;\n /** Called when payment succeeds */\n onSuccess?: (result: SendIntentResult) => void;\n /** Called when payment fails */\n onError?: (error: Error) => void;\n /** When to close the dialog and return success. Defaults to \"preconfirmed\" */\n closeOn?: CloseOnStatus;\n /** Button text - defaults to \"Pay with 1auth\" */\n children?: React.ReactNode;\n /** Custom class name */\n className?: string;\n /** Custom inline styles (merged with defaults) */\n style?: React.CSSProperties;\n /** Disabled state */\n disabled?: boolean;\n /** Hide the fingerprint icon */\n hideIcon?: boolean;\n}\n\n/**\n * A pre-built payment button that integrates 1auth passkey authentication\n * and intent submission into a single interactive element.\n *\n * The button manages its own loading and success states. On first click it\n * recovers the last authenticated user from `localStorage`; if none is found\n * it opens the auth modal before submitting the intent. On subsequent clicks\n * the stored session is reused so the user only needs to re-authenticate when\n * their session has been invalidated server-side.\n *\n * @param props - See {@link PayButtonProps} for the full set of options.\n *\n * @example\n * ```tsx\n * import { PayButton } from \"@rhinestone/1auth/react\";\n * import { client } from \"./auth\"; // your OneAuthClient instance\n *\n * <PayButton\n * client={client}\n * intent={{\n * targetChain: 8453, // Base\n * calls: [{ to: \"0xRecipient\", data: \"0x\", value: \"1000000\" }],\n * }}\n * onSuccess={(result) => console.log(\"tx:\", result.transactionHash)}\n * onError={(err) => console.error(err)}\n * >\n * Pay $5 USDC\n * </PayButton>\n * ```\n */\nexport function PayButton({\n client,\n intent,\n onSuccess,\n onError,\n closeOn = \"preconfirmed\",\n children = \"Pay with 1auth\",\n className,\n style,\n disabled,\n hideIcon,\n}: PayButtonProps) {\n const [isProcessing, setIsProcessing] = React.useState(false);\n const [isSuccess, setIsSuccess] = React.useState(false);\n const [isHovered, setIsHovered] = React.useState(false);\n\n const handleClick = async () => {\n if (disabled || isProcessing || isSuccess) return;\n\n setIsProcessing(true);\n\n try {\n await executePayment();\n } catch (err) {\n if (err instanceof Error && !err.message.includes(\"rejected\")) {\n onError?.(err);\n }\n } finally {\n setIsProcessing(false);\n }\n };\n\n /**\n * Core payment execution with built-in session recovery and re-auth retry.\n *\n * Flow:\n * 1. On first call (`forceReauth = false`), attempt to load a cached user\n * from `localStorage` under the key `\"1auth-user\"`. If the JSON is\n * malformed the cache entry is removed so the next call starts clean.\n * 2. If no cached user exists (or `forceReauth` is `true`), open the auth\n * modal. On success, persist `{ address }` to `localStorage`\n * so subsequent clicks skip authentication.\n * 3. Submit the intent. If the server responds with \"User not found\" — which\n * happens when a cached account address no longer exists on the server —\n * clear `localStorage` and call `executePayment(true)` to force a fresh\n * authentication before retrying once.\n *\n * @param forceReauth - When `true`, skip the localStorage lookup and go\n * straight to the auth modal. Used internally on \"User not found\" errors\n * to recover from a stale cache without requiring a second user gesture.\n */\n const executePayment = async (forceReauth = false) => {\n // Try to get existing account address from localStorage\n let accountAddress: string | null = null;\n if (!forceReauth) {\n const savedUser = localStorage.getItem(\"1auth-user\");\n if (savedUser) {\n try {\n const parsed = JSON.parse(savedUser);\n accountAddress = parsed.address || null;\n } catch {\n localStorage.removeItem(\"1auth-user\");\n }\n }\n }\n\n // If no account address (or forced reauth), authenticate first\n if (!accountAddress) {\n const authResult = await client.authWithModal();\n if (authResult.success && authResult.user) {\n accountAddress = authResult.user.address || null;\n const stored: Record<string, string> = {};\n if (accountAddress) stored.address = accountAddress;\n if (authResult.signerType) stored.signerType = authResult.signerType;\n localStorage.setItem(\"1auth-user\", JSON.stringify(stored));\n } else {\n // Auth cancelled or failed\n return;\n }\n }\n\n // Send the intent\n let result: SendIntentResult;\n try {\n result = await client.sendIntent({\n ...intent,\n // Guaranteed non-null: the `if (!accountAddress) { ...return }` guard\n // above either resolved an address or bailed out before reaching here.\n accountAddress,\n closeOn,\n });\n } catch (err) {\n // If user not found, clear localStorage and force re-authentication\n if (err instanceof Error && err.message.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n throw err;\n }\n\n if (result.success) {\n setIsSuccess(true);\n onSuccess?.(result);\n } else {\n // If user not found error in result, clear localStorage and retry\n if (result.error?.message?.includes(\"User not found\")) {\n localStorage.removeItem(\"1auth-user\");\n return executePayment(true);\n }\n onError?.(new Error(result.error?.message || \"Payment failed\"));\n }\n };\n\n const combinedStyles: React.CSSProperties = {\n ...defaultStyles,\n ...(isSuccess ? defaultSuccessStyles : {}),\n ...(isHovered && !disabled && !isProcessing && !isSuccess ? defaultHoverStyles : {}),\n ...(disabled || isProcessing ? defaultDisabledStyles : {}),\n ...style,\n };\n\n return (\n <button\n type=\"button\"\n className={className}\n style={combinedStyles}\n onClick={handleClick}\n disabled={disabled || isProcessing || isSuccess}\n onMouseEnter={() => setIsHovered(true)}\n onMouseLeave={() => setIsHovered(false)}\n >\n {isSuccess ? (\n <>\n <CheckIcon className=\"pay-button-icon\" />\n Paid\n </>\n ) : isProcessing ? (\n \"Processing...\"\n ) : (\n <>\n {!hideIcon && <FingerprintIcon className=\"pay-button-icon\" />}\n {children}\n </>\n )}\n </button>\n );\n}\n\n/**\n * Margin (ms) before a JWT's `exp` at which the cached access token is\n * treated as stale. Covers clock skew between the minting app server and\n * the orchestrator's verifier plus in-flight time of the prepare call —\n * a token that verifies fine locally can still be rejected downstream if\n * it expires mid-request.\n */\nconst ACCESS_TOKEN_EXPIRY_MARGIN_MS = 60_000;\n\n/**\n * Check whether a cached JWT access token is still safely usable.\n *\n * Decodes the payload WITHOUT verifying the signature — the client only\n * needs the `exp` claim to know when to re-mint; verification happens\n * server-side. Returns `false` (unusable) when the token is expired or\n * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that\n * can't be decoded or carry no numeric `exp` are treated as unusable so\n * a malformed cache entry never gets served indefinitely.\n */\nexport function isAccessTokenFresh(token: string): boolean {\n try {\n const payload = token.split(\".\")[1];\n if (!payload) return false;\n // base64url → base64 before atob; JWTs use the url-safe alphabet.\n const json = atob(payload.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n const claims = JSON.parse(json) as { exp?: unknown };\n if (typeof claims.exp !== \"number\") return false;\n return Date.now() < claims.exp * 1000 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;\n } catch {\n return false;\n }\n}\n\n/**\n * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an\n * expiry-aware access-token cache held in a closure.\n *\n * Exported as the non-React core of {@link useSponsorship} so the caching\n * behaviour is unit-testable (and usable outside React). The access token is\n * cached and re-used only while its `exp` claim is comfortably in the\n * future — a long-lived tab must re-mint rather than serve a token the\n * orchestrator will reject with a 401 (`\"exp\" claim timestamp check\n * failed`), which previously surfaced to users as \"Failed to get quote from\n * orchestrator\". The extension token is always fetched fresh per intent\n * because it binds to a specific `intentOp`.\n *\n * Both fetches use `credentials: \"include\"` so the app's own session cookies\n * authenticate the user — no separate auth channel needed.\n */\nexport function createCachedSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n let cachedToken: string | null = null;\n\n const fetchAccessToken = async (): Promise<string> => {\n const res = await fetch(accessTokenUrl, {\n method: \"GET\",\n credentials: \"include\",\n });\n if (!res.ok) {\n // Folds the server's JSON error reason into the message so\n // misconfigurations aren't reduced to an opaque HTTP status.\n throw await tokenFetchError(\"sponsorship access token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in access-token response\");\n cachedToken = body.token;\n return body.token;\n };\n\n return {\n accessToken: async () =>\n cachedToken && isAccessTokenFresh(cachedToken)\n ? cachedToken\n : await fetchAccessToken(),\n getExtensionToken: async (intentOp: string) => {\n const res = await fetch(extensionTokenUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ intentOp }),\n });\n if (res.status === 401) {\n // Access token likely expired — drop cached copy so the next\n // accessToken() call re-mints, then surface the error for the\n // integrator to retry.\n cachedToken = null;\n }\n if (!res.ok) {\n throw await tokenFetchError(\"sponsorship extension token\", res);\n }\n const body = (await res.json()) as { token?: string };\n if (!body.token) throw new Error(\"Missing `token` in extension-token response\");\n return body.token;\n },\n };\n}\n\n/**\n * React hook wrapper around {@link createCachedSponsorship}. The returned\n * config is stable across renders (memoized by URL), so passing it to\n * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does\n * not thrash the client. Re-creating on URL change naturally resets the\n * cached token so we never serve a token minted against the previous\n * endpoint.\n */\nexport function useSponsorship(\n config: SponsorshipUrlConfig,\n): SponsorshipCallbackConfig {\n const { accessTokenUrl, extensionTokenUrl } = config;\n\n return React.useMemo<SponsorshipCallbackConfig>(\n () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),\n [accessTokenUrl, extensionTokenUrl],\n );\n}\n\n// Re-export types for convenience\nexport type { SendIntentOptions, SendIntentResult, CloseOnStatus } from \"./types\";\n"],"mappings":"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","names":[]}
package/dist/server.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Address, Hex } from 'viem';
2
- export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-aWdi5O2z.mjs';
3
- export { W as WebAuthnSignature } from './types-C0jKNT_t.mjs';
2
+ export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-0VXQpQBJ.mjs';
3
+ export { W as WebAuthnSignature } from './types-Dzm5lZK-.mjs';
4
4
  import '@rhinestone/sdk';
5
5
 
6
6
  /**
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Address, Hex } from 'viem';
2
- export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-CnOwPq78.js';
3
- export { W as WebAuthnSignature } from './types-C0jKNT_t.js';
2
+ export { e as encodeWebAuthnSignature, q as hashMessage, k as isTestnet, v as verifyMessageHash } from './verify-9UgxLSdo.js';
3
+ export { W as WebAuthnSignature } from './types-Dzm5lZK-.js';
4
4
  import '@rhinestone/sdk';
5
5
 
6
6
  /**