@rhinestone/1auth 0.6.8 → 0.6.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.
Files changed (51) hide show
  1. package/README.md +171 -12
  2. package/dist/chunk-GUAI55LL.mjs +179 -0
  3. package/dist/chunk-GUAI55LL.mjs.map +1 -0
  4. package/dist/chunk-IHBVEU33.mjs +20 -0
  5. package/dist/chunk-IHBVEU33.mjs.map +1 -0
  6. package/dist/chunk-IIACVHR3.mjs +28 -0
  7. package/dist/chunk-IIACVHR3.mjs.map +1 -0
  8. package/dist/chunk-N6KE5CII.mjs +72 -0
  9. package/dist/chunk-N6KE5CII.mjs.map +1 -0
  10. package/dist/{chunk-SXISYG2P.mjs → chunk-THKG3FAG.mjs} +137 -255
  11. package/dist/chunk-THKG3FAG.mjs.map +1 -0
  12. package/dist/{client-BrMrhetG.d.mts → client-B_CzDa_I.d.ts} +360 -857
  13. package/dist/{client-BrMrhetG.d.ts → client-F4DnFM8d.d.mts} +360 -857
  14. package/dist/headless.d.mts +109 -0
  15. package/dist/headless.d.ts +109 -0
  16. package/dist/headless.js +467 -0
  17. package/dist/headless.js.map +1 -0
  18. package/dist/headless.mjs +382 -0
  19. package/dist/headless.mjs.map +1 -0
  20. package/dist/index.d.mts +96 -144
  21. package/dist/index.d.ts +96 -144
  22. package/dist/index.js +3212 -756
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +3010 -705
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/{provider-CDl9wYEc.d.mts → provider-Cd7Ip5L-.d.ts} +6 -5
  27. package/dist/{provider-Dgv533YQ.d.ts → provider-IvYXPMpk.d.mts} +6 -5
  28. package/dist/react.d.mts +42 -2
  29. package/dist/react.d.ts +42 -2
  30. package/dist/react.js +92 -2
  31. package/dist/react.js.map +1 -1
  32. package/dist/react.mjs +66 -1
  33. package/dist/react.mjs.map +1 -1
  34. package/dist/server.d.mts +118 -0
  35. package/dist/server.d.ts +118 -0
  36. package/dist/server.js +356 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/server.mjs +282 -0
  39. package/dist/server.mjs.map +1 -0
  40. package/dist/types-U_dwxbtS.d.mts +1488 -0
  41. package/dist/types-U_dwxbtS.d.ts +1488 -0
  42. package/dist/verify-BLgZzwmJ.d.ts +150 -0
  43. package/dist/verify-C8-a5c3K.d.mts +150 -0
  44. package/dist/wagmi.d.mts +5 -2
  45. package/dist/wagmi.d.ts +5 -2
  46. package/dist/wagmi.js +138 -43
  47. package/dist/wagmi.js.map +1 -1
  48. package/dist/wagmi.mjs +3 -1
  49. package/dist/wagmi.mjs.map +1 -1
  50. package/package.json +15 -2
  51. package/dist/chunk-SXISYG2P.mjs.map +0 -1
@@ -1,4 +1,5 @@
1
- import { O as OneAuthClient, z as CloseOnStatus } from './client-BrMrhetG.mjs';
1
+ import { O as OneAuthClient } from './client-B_CzDa_I.js';
2
+ import { a0 as CloseOnStatus } from './types-U_dwxbtS.js';
2
3
 
3
4
  /**
4
5
  * EIP-1193 JSON-RPC provider factory for the 1auth passkey authentication system.
@@ -53,10 +54,10 @@ type OneAuthProviderOptions = {
53
54
  * @param options.chainId - Default chain ID the provider reports via `eth_chainId`
54
55
  * @param options.storageKey - localStorage key for persisting the connected user.
55
56
  * Defaults to `"1auth-user"`. Override to support multiple concurrent connections.
56
- * @param options.closeOn - Controls when the signing dialog closes and the promise resolves.
57
- * `"preconfirmed"` resolves as soon as the intent is submitted to the orchestrator;
58
- * `"completed"` waits for on-chain confirmation. Defaults to `"completed"` when
59
- * `waitForHash` is true, otherwise `"preconfirmed"`.
57
+ * @param options.closeOn - Controls when the signing dialog closes and shows the "Done" button.
58
+ * `"preconfirmed"` (default) closes as soon as the intent is confirmed by the orchestrator.
59
+ * The `waitForHash` option independently controls whether the SDK continues polling for
60
+ * a transaction hash after the dialog closes.
60
61
  * @param options.waitForHash - When true, polls the intent status until a transaction hash
61
62
  * is available before resolving `eth_sendTransaction` / `wallet_sendCalls`. Defaults to true.
62
63
  * @param options.hashTimeoutMs - Maximum milliseconds to wait for a transaction hash.
@@ -1,4 +1,5 @@
1
- import { O as OneAuthClient, z as CloseOnStatus } from './client-BrMrhetG.js';
1
+ import { O as OneAuthClient } from './client-F4DnFM8d.mjs';
2
+ import { a0 as CloseOnStatus } from './types-U_dwxbtS.mjs';
2
3
 
3
4
  /**
4
5
  * EIP-1193 JSON-RPC provider factory for the 1auth passkey authentication system.
@@ -53,10 +54,10 @@ type OneAuthProviderOptions = {
53
54
  * @param options.chainId - Default chain ID the provider reports via `eth_chainId`
54
55
  * @param options.storageKey - localStorage key for persisting the connected user.
55
56
  * Defaults to `"1auth-user"`. Override to support multiple concurrent connections.
56
- * @param options.closeOn - Controls when the signing dialog closes and the promise resolves.
57
- * `"preconfirmed"` resolves as soon as the intent is submitted to the orchestrator;
58
- * `"completed"` waits for on-chain confirmation. Defaults to `"completed"` when
59
- * `waitForHash` is true, otherwise `"preconfirmed"`.
57
+ * @param options.closeOn - Controls when the signing dialog closes and shows the "Done" button.
58
+ * `"preconfirmed"` (default) closes as soon as the intent is confirmed by the orchestrator.
59
+ * The `waitForHash` option independently controls whether the SDK continues polling for
60
+ * a transaction hash after the dialog closes.
60
61
  * @param options.waitForHash - When true, polls the intent status until a transaction hash
61
62
  * is available before resolving `eth_sendTransaction` / `wallet_sendCalls`. Defaults to true.
62
63
  * @param options.hashTimeoutMs - Maximum milliseconds to wait for a transaction hash.
package/dist/react.d.mts CHANGED
@@ -1,6 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { O as OneAuthClient, v as SendIntentOptions, S as SendIntentResult, z as CloseOnStatus } from './client-BrMrhetG.mjs';
3
+ import { O as OneAuthClient } from './client-F4DnFM8d.mjs';
4
+ import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-U_dwxbtS.mjs';
5
+ import 'viem';
6
+ import '@rhinestone/sdk';
4
7
 
5
8
  interface PayButtonProps {
6
9
  /** The OneAuthClient instance */
@@ -55,5 +58,42 @@ interface PayButtonProps {
55
58
  * ```
56
59
  */
57
60
  declare function PayButton({ client, intent, onSuccess, onError, closeOn, children, className, style, disabled, hideIcon, }: PayButtonProps): react_jsx_runtime.JSX.Element;
61
+ /**
62
+ * Check whether a cached JWT access token is still safely usable.
63
+ *
64
+ * Decodes the payload WITHOUT verifying the signature — the client only
65
+ * needs the `exp` claim to know when to re-mint; verification happens
66
+ * server-side. Returns `false` (unusable) when the token is expired or
67
+ * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that
68
+ * can't be decoded or carry no numeric `exp` are treated as unusable so
69
+ * a malformed cache entry never gets served indefinitely.
70
+ */
71
+ declare function isAccessTokenFresh(token: string): boolean;
72
+ /**
73
+ * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an
74
+ * expiry-aware access-token cache held in a closure.
75
+ *
76
+ * Exported as the non-React core of {@link useSponsorship} so the caching
77
+ * behaviour is unit-testable (and usable outside React). The access token is
78
+ * cached and re-used only while its `exp` claim is comfortably in the
79
+ * future — a long-lived tab must re-mint rather than serve a token the
80
+ * orchestrator will reject with a 401 (`"exp" claim timestamp check
81
+ * failed`), which previously surfaced to users as "Failed to get quote from
82
+ * orchestrator". The extension token is always fetched fresh per intent
83
+ * because it binds to a specific `intentOp`.
84
+ *
85
+ * Both fetches use `credentials: "include"` so the app's own session cookies
86
+ * authenticate the user — no separate auth channel needed.
87
+ */
88
+ declare function createCachedSponsorship(config: SponsorshipUrlConfig): SponsorshipCallbackConfig;
89
+ /**
90
+ * React hook wrapper around {@link createCachedSponsorship}. The returned
91
+ * config is stable across renders (memoized by URL), so passing it to
92
+ * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does
93
+ * not thrash the client. Re-creating on URL change naturally resets the
94
+ * cached token so we never serve a token minted against the previous
95
+ * endpoint.
96
+ */
97
+ declare function useSponsorship(config: SponsorshipUrlConfig): SponsorshipCallbackConfig;
58
98
 
59
- export { CloseOnStatus, PayButton, type PayButtonProps, SendIntentOptions, SendIntentResult };
99
+ export { CloseOnStatus, PayButton, type PayButtonProps, SendIntentOptions, SendIntentResult, createCachedSponsorship, isAccessTokenFresh, useSponsorship };
package/dist/react.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React from 'react';
3
- import { O as OneAuthClient, v as SendIntentOptions, S as SendIntentResult, z as CloseOnStatus } from './client-BrMrhetG.js';
3
+ import { O as OneAuthClient } from './client-B_CzDa_I.js';
4
+ import { Y as SendIntentOptions, i as SendIntentResult, a0 as CloseOnStatus, aw as SponsorshipUrlConfig, av as SponsorshipCallbackConfig } from './types-U_dwxbtS.js';
5
+ import 'viem';
6
+ import '@rhinestone/sdk';
4
7
 
5
8
  interface PayButtonProps {
6
9
  /** The OneAuthClient instance */
@@ -55,5 +58,42 @@ interface PayButtonProps {
55
58
  * ```
56
59
  */
57
60
  declare function PayButton({ client, intent, onSuccess, onError, closeOn, children, className, style, disabled, hideIcon, }: PayButtonProps): react_jsx_runtime.JSX.Element;
61
+ /**
62
+ * Check whether a cached JWT access token is still safely usable.
63
+ *
64
+ * Decodes the payload WITHOUT verifying the signature — the client only
65
+ * needs the `exp` claim to know when to re-mint; verification happens
66
+ * server-side. Returns `false` (unusable) when the token is expired or
67
+ * within {@link ACCESS_TOKEN_EXPIRY_MARGIN_MS} of expiry. Tokens that
68
+ * can't be decoded or carry no numeric `exp` are treated as unusable so
69
+ * a malformed cache entry never gets served indefinitely.
70
+ */
71
+ declare function isAccessTokenFresh(token: string): boolean;
72
+ /**
73
+ * Build a {@link SponsorshipCallbackConfig} from token endpoint URLs with an
74
+ * expiry-aware access-token cache held in a closure.
75
+ *
76
+ * Exported as the non-React core of {@link useSponsorship} so the caching
77
+ * behaviour is unit-testable (and usable outside React). The access token is
78
+ * cached and re-used only while its `exp` claim is comfortably in the
79
+ * future — a long-lived tab must re-mint rather than serve a token the
80
+ * orchestrator will reject with a 401 (`"exp" claim timestamp check
81
+ * failed`), which previously surfaced to users as "Failed to get quote from
82
+ * orchestrator". The extension token is always fetched fresh per intent
83
+ * because it binds to a specific `intentOp`.
84
+ *
85
+ * Both fetches use `credentials: "include"` so the app's own session cookies
86
+ * authenticate the user — no separate auth channel needed.
87
+ */
88
+ declare function createCachedSponsorship(config: SponsorshipUrlConfig): SponsorshipCallbackConfig;
89
+ /**
90
+ * React hook wrapper around {@link createCachedSponsorship}. The returned
91
+ * config is stable across renders (memoized by URL), so passing it to
92
+ * `new OneAuthClient({ sponsorship })` or `client.setSponsorship(...)` does
93
+ * not thrash the client. Re-creating on URL change naturally resets the
94
+ * cached token so we never serve a token minted against the previous
95
+ * endpoint.
96
+ */
97
+ declare function useSponsorship(config: SponsorshipUrlConfig): SponsorshipCallbackConfig;
58
98
 
59
- export { CloseOnStatus, PayButton, type PayButtonProps, SendIntentOptions, SendIntentResult };
99
+ export { CloseOnStatus, PayButton, type PayButtonProps, SendIntentOptions, SendIntentResult, createCachedSponsorship, isAccessTokenFresh, useSponsorship };
package/dist/react.js CHANGED
@@ -30,10 +30,39 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/react.tsx
31
31
  var react_exports = {};
32
32
  __export(react_exports, {
33
- PayButton: () => PayButton
33
+ PayButton: () => PayButton,
34
+ createCachedSponsorship: () => createCachedSponsorship,
35
+ isAccessTokenFresh: () => isAccessTokenFresh,
36
+ useSponsorship: () => useSponsorship
34
37
  });
35
38
  module.exports = __toCommonJS(react_exports);
36
39
  var React = __toESM(require("react"));
40
+
41
+ // src/sponsorship-fetch.ts
42
+ async function extractServerReason(response) {
43
+ try {
44
+ const text = await response.clone().text();
45
+ if (!text) return response.statusText;
46
+ try {
47
+ const data = JSON.parse(text);
48
+ const reason = data.error ?? data.message;
49
+ if (typeof reason === "string" && reason.trim()) return reason;
50
+ } catch {
51
+ }
52
+ return text.slice(0, 200);
53
+ } catch {
54
+ return response.statusText;
55
+ }
56
+ }
57
+ async function tokenFetchError(label, response) {
58
+ const reason = await extractServerReason(response);
59
+ const suffix = reason ? `: ${reason}` : "";
60
+ return new Error(
61
+ `Failed to fetch ${label} (${response.status} ${response.statusText})${suffix}`
62
+ );
63
+ }
64
+
65
+ // src/react.tsx
37
66
  var import_jsx_runtime = require("react/jsx-runtime");
38
67
  var FingerprintIcon = ({ className }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
39
68
  "svg",
@@ -153,6 +182,7 @@ function PayButton({
153
182
  const stored = {};
154
183
  if (username) stored.username = username;
155
184
  if (accountAddress) stored.address = accountAddress;
185
+ if (authResult.signerType) stored.signerType = authResult.signerType;
156
186
  localStorage.setItem("1auth-user", JSON.stringify(stored));
157
187
  } else {
158
188
  return;
@@ -211,8 +241,68 @@ function PayButton({
211
241
  }
212
242
  );
213
243
  }
244
+ var ACCESS_TOKEN_EXPIRY_MARGIN_MS = 6e4;
245
+ function isAccessTokenFresh(token) {
246
+ try {
247
+ const payload = token.split(".")[1];
248
+ if (!payload) return false;
249
+ const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
250
+ const claims = JSON.parse(json);
251
+ if (typeof claims.exp !== "number") return false;
252
+ return Date.now() < claims.exp * 1e3 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;
253
+ } catch {
254
+ return false;
255
+ }
256
+ }
257
+ function createCachedSponsorship(config) {
258
+ const { accessTokenUrl, extensionTokenUrl } = config;
259
+ let cachedToken = null;
260
+ const fetchAccessToken = async () => {
261
+ const res = await fetch(accessTokenUrl, {
262
+ method: "GET",
263
+ credentials: "include"
264
+ });
265
+ if (!res.ok) {
266
+ throw await tokenFetchError("sponsorship access token", res);
267
+ }
268
+ const body = await res.json();
269
+ if (!body.token) throw new Error("Missing `token` in access-token response");
270
+ cachedToken = body.token;
271
+ return body.token;
272
+ };
273
+ return {
274
+ accessToken: async () => cachedToken && isAccessTokenFresh(cachedToken) ? cachedToken : await fetchAccessToken(),
275
+ getExtensionToken: async (intentOp) => {
276
+ const res = await fetch(extensionTokenUrl, {
277
+ method: "POST",
278
+ credentials: "include",
279
+ headers: { "Content-Type": "application/json" },
280
+ body: JSON.stringify({ intentOp })
281
+ });
282
+ if (res.status === 401) {
283
+ cachedToken = null;
284
+ }
285
+ if (!res.ok) {
286
+ throw await tokenFetchError("sponsorship extension token", res);
287
+ }
288
+ const body = await res.json();
289
+ if (!body.token) throw new Error("Missing `token` in extension-token response");
290
+ return body.token;
291
+ }
292
+ };
293
+ }
294
+ function useSponsorship(config) {
295
+ const { accessTokenUrl, extensionTokenUrl } = config;
296
+ return React.useMemo(
297
+ () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),
298
+ [accessTokenUrl, extensionTokenUrl]
299
+ );
300
+ }
214
301
  // Annotate the CommonJS export names for ESM import in node:
215
302
  0 && (module.exports = {
216
- PayButton
303
+ PayButton,
304
+ createCachedSponsorship,
305
+ isAccessTokenFresh,
306
+ useSponsorship
217
307
  });
218
308
  //# sourceMappingURL=react.js.map
package/dist/react.js.map CHANGED
@@ -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 type { SendIntentOptions, SendIntentResult, CloseOnStatus } 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 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// 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","../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":[]}
package/dist/react.mjs CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ tokenFetchError
3
+ } from "./chunk-IIACVHR3.mjs";
4
+
1
5
  // src/react.tsx
2
6
  import * as React from "react";
3
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -119,6 +123,7 @@ function PayButton({
119
123
  const stored = {};
120
124
  if (username) stored.username = username;
121
125
  if (accountAddress) stored.address = accountAddress;
126
+ if (authResult.signerType) stored.signerType = authResult.signerType;
122
127
  localStorage.setItem("1auth-user", JSON.stringify(stored));
123
128
  } else {
124
129
  return;
@@ -177,7 +182,67 @@ function PayButton({
177
182
  }
178
183
  );
179
184
  }
185
+ var ACCESS_TOKEN_EXPIRY_MARGIN_MS = 6e4;
186
+ function isAccessTokenFresh(token) {
187
+ try {
188
+ const payload = token.split(".")[1];
189
+ if (!payload) return false;
190
+ const json = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
191
+ const claims = JSON.parse(json);
192
+ if (typeof claims.exp !== "number") return false;
193
+ return Date.now() < claims.exp * 1e3 - ACCESS_TOKEN_EXPIRY_MARGIN_MS;
194
+ } catch {
195
+ return false;
196
+ }
197
+ }
198
+ function createCachedSponsorship(config) {
199
+ const { accessTokenUrl, extensionTokenUrl } = config;
200
+ let cachedToken = null;
201
+ const fetchAccessToken = async () => {
202
+ const res = await fetch(accessTokenUrl, {
203
+ method: "GET",
204
+ credentials: "include"
205
+ });
206
+ if (!res.ok) {
207
+ throw await tokenFetchError("sponsorship access token", res);
208
+ }
209
+ const body = await res.json();
210
+ if (!body.token) throw new Error("Missing `token` in access-token response");
211
+ cachedToken = body.token;
212
+ return body.token;
213
+ };
214
+ return {
215
+ accessToken: async () => cachedToken && isAccessTokenFresh(cachedToken) ? cachedToken : await fetchAccessToken(),
216
+ getExtensionToken: async (intentOp) => {
217
+ const res = await fetch(extensionTokenUrl, {
218
+ method: "POST",
219
+ credentials: "include",
220
+ headers: { "Content-Type": "application/json" },
221
+ body: JSON.stringify({ intentOp })
222
+ });
223
+ if (res.status === 401) {
224
+ cachedToken = null;
225
+ }
226
+ if (!res.ok) {
227
+ throw await tokenFetchError("sponsorship extension token", res);
228
+ }
229
+ const body = await res.json();
230
+ if (!body.token) throw new Error("Missing `token` in extension-token response");
231
+ return body.token;
232
+ }
233
+ };
234
+ }
235
+ function useSponsorship(config) {
236
+ const { accessTokenUrl, extensionTokenUrl } = config;
237
+ return React.useMemo(
238
+ () => createCachedSponsorship({ accessTokenUrl, extensionTokenUrl }),
239
+ [accessTokenUrl, extensionTokenUrl]
240
+ );
241
+ }
180
242
  export {
181
- PayButton
243
+ PayButton,
244
+ createCachedSponsorship,
245
+ isAccessTokenFresh,
246
+ useSponsorship
182
247
  };
183
248
  //# sourceMappingURL=react.mjs.map
@@ -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 type { SendIntentOptions, SendIntentResult, CloseOnStatus } 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 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// 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 /** 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":[]}