@usethrottle/auth 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-HGBAIM6B.js → chunk-2DREGJUF.js} +2 -2
- package/dist/{chunk-SZ427BQ3.js → chunk-NCSTIPJF.js} +8 -1
- package/dist/{chunk-SZ427BQ3.js.map → chunk-NCSTIPJF.js.map} +1 -1
- package/dist/{chunk-AIXBUDOT.js → chunk-ST77QX7X.js} +12 -1
- package/dist/chunk-ST77QX7X.js.map +1 -0
- package/dist/index.cjs +7 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/react/components/index.cjs +1012 -615
- package/dist/react/components/index.cjs.map +1 -1
- package/dist/react/components/index.d.cts +76 -14
- package/dist/react/components/index.d.ts +76 -14
- package/dist/react/components/index.js +974 -596
- package/dist/react/components/index.js.map +1 -1
- package/dist/react/forms/index.cjs +11 -0
- package/dist/react/forms/index.cjs.map +1 -1
- package/dist/react/forms/index.js +2 -2
- package/dist/react/index.cjs +7 -0
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +24 -0
- package/dist/react/index.d.ts +24 -0
- package/dist/react/index.js +2 -2
- package/dist/styles.css +31 -3
- package/package.json +1 -1
- package/dist/chunk-AIXBUDOT.js.map +0 -1
- /package/dist/{chunk-HGBAIM6B.js.map → chunk-2DREGJUF.js.map} +0 -0
|
@@ -64,6 +64,10 @@ function waitCopy(seconds) {
|
|
|
64
64
|
const minutes = Math.ceil(seconds / 60);
|
|
65
65
|
return `Please wait ${minutes} ${minutes === 1 ? "minute" : "minutes"} and try again.`;
|
|
66
66
|
}
|
|
67
|
+
function fieldLabel(path) {
|
|
68
|
+
const words = path.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([a-zA-Z])([0-9])/g, "$1 $2").toLowerCase().trim();
|
|
69
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
70
|
+
}
|
|
67
71
|
function messageForError(err) {
|
|
68
72
|
const e = err;
|
|
69
73
|
switch (e?.code) {
|
|
@@ -96,6 +100,13 @@ function messageForError(err) {
|
|
|
96
100
|
return "We could not find that.";
|
|
97
101
|
case "payment_method_in_use":
|
|
98
102
|
return e.message || "That payment method is in use and cannot be removed.";
|
|
103
|
+
case "validation_error": {
|
|
104
|
+
const first = e.details?.[0];
|
|
105
|
+
if (first?.path && first?.message) {
|
|
106
|
+
return `${fieldLabel(first.path)} ${first.message.replace(/\bNOT\b/g, "not")}.`;
|
|
107
|
+
}
|
|
108
|
+
return "Please check the highlighted fields and try again.";
|
|
109
|
+
}
|
|
99
110
|
default:
|
|
100
111
|
return "Something went wrong. Please try again.";
|
|
101
112
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/react/forms/index.tsx","../../../src/react/forms/SignIn.tsx","../../../src/react/context.tsx","../../../src/react/forms/useFormAction.ts","../../../src/react/forms/messages.ts","../../../src/react/forms/SignUp.tsx","../../../src/react/forms/ForgotPassword.tsx","../../../src/react/forms/ResetPassword.tsx","../../../src/react/forms/VerifyEmail.tsx"],"sourcesContent":["export { SignInForm } from './SignIn';\nexport type { SignInFormProps } from './SignIn';\nexport { SignUpForm } from './SignUp';\nexport type { SignUpFormProps } from './SignUp';\nexport { ForgotPasswordForm } from './ForgotPassword';\nexport type { ForgotPasswordFormProps } from './ForgotPassword';\nexport { ResetPasswordForm } from './ResetPassword';\nexport type { ResetPasswordFormProps } from './ResetPassword';\nexport { VerifyEmailPanel } from './VerifyEmail';\nexport type { VerifyEmailPanelProps } from './VerifyEmail';\nexport { messageForError } from './messages';\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface SignInFormProps {\n onSuccess?: () => void;\n onForgotPassword?: () => void;\n className?: string;\n labels?: Partial<{ email: string; password: string; submit: string; forgot: string }>;\n}\n\nexport function SignInForm(props: SignInFormProps) {\n const { auth } = useAuth();\n const { submitting, error, run } = useFormAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const ids = { email: `${useId()}-email`, password: `${useId()}-password` };\n const l = { email: 'Email', password: 'Password', submit: 'Sign in', forgot: 'Forgot your password?', ...props.labels };\n\n return (\n <form\n data-testid=\"throttle-auth-signin\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.signIn(email, password); props.onSuccess?.(); });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={ids.email}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={ids.email} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n <label className=\"throttle-auth-label\" htmlFor={ids.password}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={ids.password} type=\"password\" autoComplete=\"current-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n\n {props.onForgotPassword && (\n <button className=\"throttle-auth-link\" type=\"button\" onClick={props.onForgotPassword}>{l.forgot}</button>\n )}\n </form>\n );\n}\n","import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport type { ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../client';\nimport { createAccount, type ThrottleAccount } from '../account';\nimport type { AuthSnapshot } from '../session';\nimport type { ThrottleAuthConfig } from '../types';\n\nexport interface ThrottleAuthProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n /** Inject a pre-built client. Tests use it; merchants sharing one client across roots may too. */\n auth?: ThrottleAuth;\n children: ReactNode;\n}\n\ninterface ContextValue { auth: ThrottleAuth; account: ThrottleAccount }\n\nconst Ctx = createContext<ContextValue | null>(null);\n\nexport function ThrottleAuthProvider(props: ThrottleAuthProviderProps) {\n const { auth: injected, publishableKey, baseUrl, storage, onStepUpRequired, children } = props;\n\n // `storage` and `onStepUpRequired` are deliberately NOT dependencies: they are\n // read once at mount. A merchant passing `storage={memoryStorage()}` inline —\n // which the README suggests — would otherwise get a new client on every parent\n // render, and with it a fresh empty SessionState that reads as signed out.\n const value = useMemo<ContextValue>(() => {\n const auth = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired });\n return { auth, account: createAccount(auth) };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const restored = useRef<ThrottleAuth | null>(null);\n useEffect(() => {\n // Once per client. StrictMode runs effects twice in development, and a\n // second restore() would present the refresh token that the first one\n // just rotated away.\n if (restored.current === value.auth) return;\n restored.current = value.auth;\n void value.auth.restore();\n }, [value]);\n\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\nfunction useCtx(): ContextValue {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useAuth must be used inside a <ThrottleAuthProvider>');\n return ctx;\n}\n\nexport function useAuthContext(): ContextValue { return useCtx(); }\n\nexport type UseAuthResult = AuthSnapshot & {\n isLoading: boolean;\n isAuthenticated: boolean;\n auth: ThrottleAuth;\n account: ThrottleAccount;\n};\n\nexport function useAuth(): UseAuthResult {\n const { auth, account } = useCtx();\n const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);\n return {\n ...snapshot,\n isLoading: snapshot.status === 'loading',\n isAuthenticated: snapshot.status === 'authenticated',\n auth,\n account,\n };\n}\n","import { useCallback, useRef, useState } from 'react';\nimport { messageForError } from './messages';\n\nexport interface FormAction {\n submitting: boolean;\n error: string | null;\n notice: string | null;\n run(fn: () => Promise<string | void>): Promise<void>;\n}\n\n/** Submit-once semantics plus mapped error copy, shared by all five forms. */\nexport function useFormAction(): FormAction {\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const inFlight = useRef(false);\n\n const run = useCallback(async (fn: () => Promise<string | void>) => {\n if (inFlight.current) return; // a double-click is one attempt, not two\n inFlight.current = true;\n setSubmitting(true);\n setError(null);\n setNotice(null);\n try {\n const message = await fn();\n if (typeof message === 'string') setNotice(message);\n } catch (err) {\n setError(messageForError(err));\n } finally {\n inFlight.current = false;\n setSubmitting(false);\n }\n }, []);\n\n return { submitting, error, notice, run };\n}\n","import { ThrottleAuthError } from '../../errors';\n\nfunction waitCopy(seconds?: number): string {\n if (!seconds || !Number.isFinite(seconds)) return 'Please wait a moment and try again.';\n if (seconds < 60) return `Please wait ${Math.ceil(seconds)} seconds and try again.`;\n const minutes = Math.ceil(seconds / 60);\n return `Please wait ${minutes} ${minutes === 1 ? 'minute' : 'minutes'} and try again.`;\n}\n\n/**\n * Buyer-facing copy for every code the storefront API returns. Two codes are\n * deliberately NOT passed through from the server: `invalid_credentials` (the\n * server is vague on purpose — enumerating accounts through our own UI would\n * undo that) and anything unrecognised.\n */\nexport function messageForError(err: unknown): string {\n const e = err as Partial<ThrottleAuthError> & { code?: string };\n switch (e?.code) {\n case 'invalid_credentials':\n return 'That email or password is not correct.';\n case 'weak_password':\n // The server's message IS the policy reason (\"Use at least 12 characters\").\n return e.message || 'Please choose a stronger password.';\n case 'too_many_attempts':\n return `Too many attempts. ${waitCopy(e.retryAfter)}`;\n case 'verification_required':\n return 'Please verify your email address to continue.';\n case 'step_up_required':\n return 'Please re-enter your password to continue.';\n case 'session_revoked':\n return 'Your session has ended. Please sign in again.';\n case 'token_expired':\n case 'token_invalid':\n case 'token_already_used':\n return 'That link is no longer valid. Please request a new link.';\n case 'auth_unavailable':\n return 'Accounts are briefly unavailable. Please try again shortly.';\n case 'origin_not_allowed':\n case 'auth_not_enabled':\n case 'invalid_api_key':\n case 'environment_mismatch':\n // Merchant misconfiguration, not something the buyer can fix.\n return 'Accounts are not available right now. Please contact support.';\n case 'cart_already_claimed':\n return 'That cart belongs to a different account.';\n case 'not_found':\n return 'We could not find that.';\n case 'payment_method_in_use':\n // The server's message IS the fix (\"set another card as default\n // first\") — same rationale as weak_password below.\n return e.message || 'That payment method is in use and cannot be removed.';\n default:\n return 'Something went wrong. Please try again.';\n }\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface SignUpFormProps {\n onSuccess?: () => void;\n /** Called for the 202 shape: the address already has credentials, so we sent an email instead. */\n onPending?: () => void;\n className?: string;\n labels?: Partial<{ email: string; password: string; submit: string; pending: string }>;\n}\n\nexport function SignUpForm(props: SignUpFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const ids = { email: `${useId()}-email`, password: `${useId()}-password` };\n const l = {\n email: 'Email', password: 'Password', submit: 'Create account',\n pending: 'Check your email — we sent you a link to finish signing in.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-signup\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n const result = await auth.register(email, password);\n // 202: an account already exists for this address. There is no\n // session — say so rather than routing to a signed-in page.\n if (result.status === 'pending') { props.onPending?.(); return l.pending; }\n props.onSuccess?.();\n });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={ids.email}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={ids.email} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n <label className=\"throttle-auth-label\" htmlFor={ids.password}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={ids.password} type=\"password\" autoComplete=\"new-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface ForgotPasswordFormProps {\n onSent?: () => void;\n className?: string;\n labels?: Partial<{ email: string; submit: string; sent: string }>;\n}\n\nexport function ForgotPasswordForm(props: ForgotPasswordFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [email, setEmail] = useState('');\n const id = `${useId()}-email`;\n const l = {\n email: 'Email', submit: 'Send reset link',\n // Identical copy whether or not the address has an account — the API is\n // deliberately silent on that and this form must be too.\n sent: 'If an account exists for that address, we have sent a reset link.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-forgot\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.forgotPassword(email); props.onSent?.(); return l.sent; });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={id}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={id} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface ResetPasswordFormProps {\n /** The token from the emailed link, e.g. `new URLSearchParams(location.search).get('token')`. */\n token: string;\n onSuccess?: () => void;\n className?: string;\n labels?: Partial<{ password: string; submit: string; done: string }>;\n}\n\nexport function ResetPasswordForm(props: ResetPasswordFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [password, setPassword] = useState('');\n const id = `${useId()}-new-password`;\n const l = {\n password: 'New password', submit: 'Set new password',\n done: 'Your password has been reset. Please sign in with your new password.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-reset\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n await auth.resetPassword(props.token, password);\n props.onSuccess?.();\n // Every session died with the reset, this one included — the buyer\n // signs in again rather than landing on an account page.\n return l.done;\n });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={id}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={id} type=\"password\" autoComplete=\"new-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface VerifyEmailPanelProps {\n /** Token from the emailed link. Omit to render the \"resend\" state instead. */\n token?: string;\n onVerified?: () => void;\n className?: string;\n labels?: Partial<{ verifying: string; verified: string; prompt: string; resend: string; resent: string }>;\n}\n\nexport function VerifyEmailPanel(props: VerifyEmailPanelProps) {\n const { auth, emailVerified } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const attempted = useRef<string | null>(null);\n const [verifiedLocally, setVerifiedLocally] = useState(false);\n const l = {\n verifying: 'Verifying your email…',\n verified: 'Your email address is verified.',\n prompt: 'Please verify your email address to unlock your full account.',\n resend: 'Resend verification email',\n resent: 'Sent. Check your inbox.',\n ...props.labels,\n };\n\n useEffect(() => {\n if (!props.token || attempted.current === props.token) return;\n // Once per token. Re-running would burn the single-use token and report\n // `token_already_used` to a buyer who did nothing wrong.\n //\n // useFormAction's `inFlight` flag happens to block the StrictMode\n // double-invoke too, but that is a submit-debounce concern that belongs to\n // form actions generally. Burning a single-use token is this panel's own\n // invariant, and it should not depend on another layer's debounce\n // semantics staying exactly as they are today.\n attempted.current = props.token;\n void run(async () => {\n await auth.verifyEmail(props.token!);\n setVerifiedLocally(true);\n props.onVerified?.();\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.token]);\n\n return (\n <div data-testid=\"throttle-auth-verify\" className={props.className ?? 'throttle-auth-panel'}>\n {props.token && submitting && <p className=\"throttle-auth-notice\" role=\"status\">{l.verifying}</p>}\n {(emailVerified || verifiedLocally) && (\n <p className=\"throttle-auth-notice\" role=\"status\">{l.verified}</p>\n )}\n {!emailVerified && !props.token && <p className=\"throttle-auth-prompt\">{l.prompt}</p>}\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n {!emailVerified && !props.token && (\n <button\n className=\"throttle-auth-submit\" type=\"button\" disabled={submitting}\n onClick={() => void run(async () => { await auth.resendVerification(); return l.resent; })}\n >\n {l.resend}\n </button>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/react/forms/index.tsx","../../../src/react/forms/SignIn.tsx","../../../src/react/context.tsx","../../../src/react/forms/useFormAction.ts","../../../src/react/forms/messages.ts","../../../src/react/forms/SignUp.tsx","../../../src/react/forms/ForgotPassword.tsx","../../../src/react/forms/ResetPassword.tsx","../../../src/react/forms/VerifyEmail.tsx"],"sourcesContent":["export { SignInForm } from './SignIn';\nexport type { SignInFormProps } from './SignIn';\nexport { SignUpForm } from './SignUp';\nexport type { SignUpFormProps } from './SignUp';\nexport { ForgotPasswordForm } from './ForgotPassword';\nexport type { ForgotPasswordFormProps } from './ForgotPassword';\nexport { ResetPasswordForm } from './ResetPassword';\nexport type { ResetPasswordFormProps } from './ResetPassword';\nexport { VerifyEmailPanel } from './VerifyEmail';\nexport type { VerifyEmailPanelProps } from './VerifyEmail';\nexport { messageForError } from './messages';\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface SignInFormProps {\n onSuccess?: () => void;\n onForgotPassword?: () => void;\n className?: string;\n labels?: Partial<{ email: string; password: string; submit: string; forgot: string }>;\n}\n\nexport function SignInForm(props: SignInFormProps) {\n const { auth } = useAuth();\n const { submitting, error, run } = useFormAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const ids = { email: `${useId()}-email`, password: `${useId()}-password` };\n const l = { email: 'Email', password: 'Password', submit: 'Sign in', forgot: 'Forgot your password?', ...props.labels };\n\n return (\n <form\n data-testid=\"throttle-auth-signin\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.signIn(email, password); props.onSuccess?.(); });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={ids.email}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={ids.email} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n <label className=\"throttle-auth-label\" htmlFor={ids.password}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={ids.password} type=\"password\" autoComplete=\"current-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n\n {props.onForgotPassword && (\n <button className=\"throttle-auth-link\" type=\"button\" onClick={props.onForgotPassword}>{l.forgot}</button>\n )}\n </form>\n );\n}\n","import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport type { ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../client';\nimport { createAccount, type ThrottleAccount } from '../account';\nimport type { AuthSnapshot } from '../session';\nimport type { ThrottleAuthConfig } from '../types';\n\nexport interface ThrottleAuthProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n /** Inject a pre-built client. Tests use it; merchants sharing one client across roots may too. */\n auth?: ThrottleAuth;\n children: ReactNode;\n}\n\ninterface ContextValue { auth: ThrottleAuth; account: ThrottleAccount }\n\nconst Ctx = createContext<ContextValue | null>(null);\n\nexport function ThrottleAuthProvider(props: ThrottleAuthProviderProps) {\n const { auth: injected, publishableKey, baseUrl, storage, onStepUpRequired, children } = props;\n\n // `storage` and `onStepUpRequired` are deliberately NOT dependencies: they are\n // read once at mount. A merchant passing `storage={memoryStorage()}` inline —\n // which the README suggests — would otherwise get a new client on every parent\n // render, and with it a fresh empty SessionState that reads as signed out.\n const value = useMemo<ContextValue>(() => {\n const auth = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired });\n return { auth, account: createAccount(auth) };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const restored = useRef<ThrottleAuth | null>(null);\n useEffect(() => {\n // Once per client. StrictMode runs effects twice in development, and a\n // second restore() would present the refresh token that the first one\n // just rotated away.\n if (restored.current === value.auth) return;\n restored.current = value.auth;\n void value.auth.restore();\n }, [value]);\n\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\nfunction useCtx(): ContextValue {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useAuth must be used inside a <ThrottleAuthProvider>');\n return ctx;\n}\n\nexport function useAuthContext(): ContextValue { return useCtx(); }\n\nexport type UseAuthResult = AuthSnapshot & {\n isLoading: boolean;\n isAuthenticated: boolean;\n auth: ThrottleAuth;\n account: ThrottleAccount;\n};\n\nexport function useAuth(): UseAuthResult {\n const { auth, account } = useCtx();\n const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);\n return {\n ...snapshot,\n isLoading: snapshot.status === 'loading',\n isAuthenticated: snapshot.status === 'authenticated',\n auth,\n account,\n };\n}\n","import { useCallback, useRef, useState } from 'react';\nimport { messageForError } from './messages';\n\nexport interface FormAction {\n submitting: boolean;\n error: string | null;\n notice: string | null;\n run(fn: () => Promise<string | void>): Promise<void>;\n}\n\n/** Submit-once semantics plus mapped error copy, shared by all five forms. */\nexport function useFormAction(): FormAction {\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const inFlight = useRef(false);\n\n const run = useCallback(async (fn: () => Promise<string | void>) => {\n if (inFlight.current) return; // a double-click is one attempt, not two\n inFlight.current = true;\n setSubmitting(true);\n setError(null);\n setNotice(null);\n try {\n const message = await fn();\n if (typeof message === 'string') setNotice(message);\n } catch (err) {\n setError(messageForError(err));\n } finally {\n inFlight.current = false;\n setSubmitting(false);\n }\n }, []);\n\n return { submitting, error, notice, run };\n}\n","import { ThrottleAuthError } from '../../errors';\n\nfunction waitCopy(seconds?: number): string {\n if (!seconds || !Number.isFinite(seconds)) return 'Please wait a moment and try again.';\n if (seconds < 60) return `Please wait ${Math.ceil(seconds)} seconds and try again.`;\n const minutes = Math.ceil(seconds / 60);\n return `Please wait ${minutes} ${minutes === 1 ? 'minute' : 'minutes'} and try again.`;\n}\n\n\n/**\n * `addressLine1` -> `Address line 1`. The API names fields for programs; this\n * is the same name written for the person reading the form.\n */\nfunction fieldLabel(path: string): string {\n const words = path\n .replace(/([a-z])([A-Z])/g, '$1 $2')\n .replace(/([a-zA-Z])([0-9])/g, '$1 $2')\n .toLowerCase()\n .trim();\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\ninterface ValidationDetail { path?: string; message?: string }\n\n/**\n * Buyer-facing copy for every code the storefront API returns. Two codes are\n * deliberately NOT passed through from the server: `invalid_credentials` (the\n * server is vague on purpose — enumerating accounts through our own UI would\n * undo that) and anything unrecognised.\n */\nexport function messageForError(err: unknown): string {\n const e = err as Partial<ThrottleAuthError> & { code?: string };\n switch (e?.code) {\n case 'invalid_credentials':\n return 'That email or password is not correct.';\n case 'weak_password':\n // The server's message IS the policy reason (\"Use at least 12 characters\").\n return e.message || 'Please choose a stronger password.';\n case 'too_many_attempts':\n return `Too many attempts. ${waitCopy(e.retryAfter)}`;\n case 'verification_required':\n return 'Please verify your email address to continue.';\n case 'step_up_required':\n return 'Please re-enter your password to continue.';\n case 'session_revoked':\n return 'Your session has ended. Please sign in again.';\n case 'token_expired':\n case 'token_invalid':\n case 'token_already_used':\n return 'That link is no longer valid. Please request a new link.';\n case 'auth_unavailable':\n return 'Accounts are briefly unavailable. Please try again shortly.';\n case 'origin_not_allowed':\n case 'auth_not_enabled':\n case 'invalid_api_key':\n case 'environment_mismatch':\n // Merchant misconfiguration, not something the buyer can fix.\n return 'Accounts are not available right now. Please contact support.';\n case 'cart_already_claimed':\n return 'That cart belongs to a different account.';\n case 'not_found':\n return 'We could not find that.';\n case 'payment_method_in_use':\n // The server's message IS the fix (\"set another card as default\n // first\") — same rationale as weak_password below.\n return e.message || 'That payment method is in use and cannot be removed.';\n case 'validation_error': {\n // The server already said which field is wrong and why. Dropping that on\n // the floor left buyers staring at \"Something went wrong\" in front of a\n // one-word fix (a country name typed where ISO-3166 alpha-2 belongs).\n // The raw `message` is an ajv path (\"body/countryCode must ...\"), so it\n // is the details we want, not the summary.\n const first = (e.details as ValidationDetail[] | undefined)?.[0];\n if (first?.path && first?.message) {\n return `${fieldLabel(first.path)} ${first.message.replace(/\\bNOT\\b/g, 'not')}.`;\n }\n return 'Please check the highlighted fields and try again.';\n }\n default:\n return 'Something went wrong. Please try again.';\n }\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface SignUpFormProps {\n onSuccess?: () => void;\n /** Called for the 202 shape: the address already has credentials, so we sent an email instead. */\n onPending?: () => void;\n className?: string;\n labels?: Partial<{ email: string; password: string; submit: string; pending: string }>;\n}\n\nexport function SignUpForm(props: SignUpFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const ids = { email: `${useId()}-email`, password: `${useId()}-password` };\n const l = {\n email: 'Email', password: 'Password', submit: 'Create account',\n pending: 'Check your email — we sent you a link to finish signing in.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-signup\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n const result = await auth.register(email, password);\n // 202: an account already exists for this address. There is no\n // session — say so rather than routing to a signed-in page.\n if (result.status === 'pending') { props.onPending?.(); return l.pending; }\n props.onSuccess?.();\n });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={ids.email}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={ids.email} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n <label className=\"throttle-auth-label\" htmlFor={ids.password}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={ids.password} type=\"password\" autoComplete=\"new-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface ForgotPasswordFormProps {\n onSent?: () => void;\n className?: string;\n labels?: Partial<{ email: string; submit: string; sent: string }>;\n}\n\nexport function ForgotPasswordForm(props: ForgotPasswordFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [email, setEmail] = useState('');\n const id = `${useId()}-email`;\n const l = {\n email: 'Email', submit: 'Send reset link',\n // Identical copy whether or not the address has an account — the API is\n // deliberately silent on that and this form must be too.\n sent: 'If an account exists for that address, we have sent a reset link.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-forgot\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => { await auth.forgotPassword(email); props.onSent?.(); return l.sent; });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={id}>{l.email}</label>\n <input className=\"throttle-auth-input\" id={id} type=\"email\" autoComplete=\"email\"\n value={email} onChange={(e) => setEmail(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useId, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface ResetPasswordFormProps {\n /** The token from the emailed link, e.g. `new URLSearchParams(location.search).get('token')`. */\n token: string;\n onSuccess?: () => void;\n className?: string;\n labels?: Partial<{ password: string; submit: string; done: string }>;\n}\n\nexport function ResetPasswordForm(props: ResetPasswordFormProps) {\n const { auth } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const [password, setPassword] = useState('');\n const id = `${useId()}-new-password`;\n const l = {\n password: 'New password', submit: 'Set new password',\n done: 'Your password has been reset. Please sign in with your new password.',\n ...props.labels,\n };\n\n return (\n <form\n data-testid=\"throttle-auth-reset\"\n className={props.className ?? 'throttle-auth-form'}\n onSubmit={(e) => {\n e.preventDefault();\n void run(async () => {\n await auth.resetPassword(props.token, password);\n props.onSuccess?.();\n // Every session died with the reset, this one included — the buyer\n // signs in again rather than landing on an account page.\n return l.done;\n });\n }}\n >\n <label className=\"throttle-auth-label\" htmlFor={id}>{l.password}</label>\n <input className=\"throttle-auth-input\" id={id} type=\"password\" autoComplete=\"new-password\"\n value={password} onChange={(e) => setPassword(e.target.value)} required />\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n <button className=\"throttle-auth-submit\" type=\"submit\" disabled={submitting}>\n {submitting ? '…' : l.submit}\n </button>\n </form>\n );\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { useAuth } from '../context';\nimport { useFormAction } from './useFormAction';\n\nexport interface VerifyEmailPanelProps {\n /** Token from the emailed link. Omit to render the \"resend\" state instead. */\n token?: string;\n onVerified?: () => void;\n className?: string;\n labels?: Partial<{ verifying: string; verified: string; prompt: string; resend: string; resent: string }>;\n}\n\nexport function VerifyEmailPanel(props: VerifyEmailPanelProps) {\n const { auth, emailVerified } = useAuth();\n const { submitting, error, notice, run } = useFormAction();\n const attempted = useRef<string | null>(null);\n const [verifiedLocally, setVerifiedLocally] = useState(false);\n const l = {\n verifying: 'Verifying your email…',\n verified: 'Your email address is verified.',\n prompt: 'Please verify your email address to unlock your full account.',\n resend: 'Resend verification email',\n resent: 'Sent. Check your inbox.',\n ...props.labels,\n };\n\n useEffect(() => {\n if (!props.token || attempted.current === props.token) return;\n // Once per token. Re-running would burn the single-use token and report\n // `token_already_used` to a buyer who did nothing wrong.\n //\n // useFormAction's `inFlight` flag happens to block the StrictMode\n // double-invoke too, but that is a submit-debounce concern that belongs to\n // form actions generally. Burning a single-use token is this panel's own\n // invariant, and it should not depend on another layer's debounce\n // semantics staying exactly as they are today.\n attempted.current = props.token;\n void run(async () => {\n await auth.verifyEmail(props.token!);\n setVerifiedLocally(true);\n props.onVerified?.();\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.token]);\n\n return (\n <div data-testid=\"throttle-auth-verify\" className={props.className ?? 'throttle-auth-panel'}>\n {props.token && submitting && <p className=\"throttle-auth-notice\" role=\"status\">{l.verifying}</p>}\n {(emailVerified || verifiedLocally) && (\n <p className=\"throttle-auth-notice\" role=\"status\">{l.verified}</p>\n )}\n {!emailVerified && !props.token && <p className=\"throttle-auth-prompt\">{l.prompt}</p>}\n\n {error && <p className=\"throttle-auth-error\" role=\"alert\">{error}</p>}\n {notice && <p className=\"throttle-auth-notice\" role=\"status\">{notice}</p>}\n\n {!emailVerified && !props.token && (\n <button\n className=\"throttle-auth-submit\" type=\"button\" disabled={submitting}\n onClick={() => void run(async () => { await auth.resendVerification(); return l.resent; })}\n >\n {l.resend}\n </button>\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime","import_react","import_jsx_runtime"]}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
messageForError
|
|
4
|
-
} from "../../chunk-
|
|
4
|
+
} from "../../chunk-ST77QX7X.js";
|
|
5
5
|
import {
|
|
6
6
|
useAuth
|
|
7
|
-
} from "../../chunk-
|
|
7
|
+
} from "../../chunk-NCSTIPJF.js";
|
|
8
8
|
|
|
9
9
|
// src/react/forms/SignIn.tsx
|
|
10
10
|
import { useId, useState as useState2 } from "react";
|
package/dist/react/index.cjs
CHANGED
|
@@ -407,6 +407,13 @@ function createAccount(auth) {
|
|
|
407
407
|
// this is how a buyer changes what they are billed on: move the default.
|
|
408
408
|
setDefaultPaymentMethod: (id) => call("PATCH", `/me/payment-methods/${seg(id)}`, { isDefault: true }),
|
|
409
409
|
deletePaymentMethod: (id) => call("DELETE", `/me/payment-methods/${seg(id)}`),
|
|
410
|
+
// Saving a card is two calls because the card number never reaches us: the
|
|
411
|
+
// first mints a processor-hosted embed session, the buyer types their card
|
|
412
|
+
// into that, and the second records what the processor vaulted. Everything
|
|
413
|
+
// about the saved card is read back from the processor, so nothing here
|
|
414
|
+
// trusts what the browser claims the card was.
|
|
415
|
+
startCardSetup: () => call("POST", "/me/payment-methods/embed-token"),
|
|
416
|
+
confirmCardSetup: (transactionId) => call("POST", "/me/payment-methods/confirm", { transactionId }),
|
|
410
417
|
listOrders: (page) => call("GET", `/me/orders${query(page)}`),
|
|
411
418
|
getOrder: (id) => call("GET", `/me/orders/${seg(id)}`),
|
|
412
419
|
listInvoices: (page) => call("GET", `/me/invoices${query(page)}`),
|
package/dist/react/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx","../../src/react/context.tsx","../../src/errors.ts","../../src/request.ts","../../src/session.ts","../../src/storage.ts","../../src/client.ts","../../src/account.ts","../../src/react/hooks.ts"],"sourcesContent":["export { ThrottleAuthProvider, useAuth, useAuthContext } from './context';\nexport type { ThrottleAuthProviderProps, UseAuthResult } from './context';\nexport { useCustomer, useAddresses, useSavedCards, useOrders, useInvoices, useSubscriptions,\n useOrder, useInvoice } from './hooks';\nexport type { Resource } from './hooks';\nexport * from '../index';\n","import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport type { ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../client';\nimport { createAccount, type ThrottleAccount } from '../account';\nimport type { AuthSnapshot } from '../session';\nimport type { ThrottleAuthConfig } from '../types';\n\nexport interface ThrottleAuthProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n /** Inject a pre-built client. Tests use it; merchants sharing one client across roots may too. */\n auth?: ThrottleAuth;\n children: ReactNode;\n}\n\ninterface ContextValue { auth: ThrottleAuth; account: ThrottleAccount }\n\nconst Ctx = createContext<ContextValue | null>(null);\n\nexport function ThrottleAuthProvider(props: ThrottleAuthProviderProps) {\n const { auth: injected, publishableKey, baseUrl, storage, onStepUpRequired, children } = props;\n\n // `storage` and `onStepUpRequired` are deliberately NOT dependencies: they are\n // read once at mount. A merchant passing `storage={memoryStorage()}` inline —\n // which the README suggests — would otherwise get a new client on every parent\n // render, and with it a fresh empty SessionState that reads as signed out.\n const value = useMemo<ContextValue>(() => {\n const auth = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired });\n return { auth, account: createAccount(auth) };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const restored = useRef<ThrottleAuth | null>(null);\n useEffect(() => {\n // Once per client. StrictMode runs effects twice in development, and a\n // second restore() would present the refresh token that the first one\n // just rotated away.\n if (restored.current === value.auth) return;\n restored.current = value.auth;\n void value.auth.restore();\n }, [value]);\n\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\nfunction useCtx(): ContextValue {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useAuth must be used inside a <ThrottleAuthProvider>');\n return ctx;\n}\n\nexport function useAuthContext(): ContextValue { return useCtx(); }\n\nexport type UseAuthResult = AuthSnapshot & {\n isLoading: boolean;\n isAuthenticated: boolean;\n auth: ThrottleAuth;\n account: ThrottleAccount;\n};\n\nexport function useAuth(): UseAuthResult {\n const { auth, account } = useCtx();\n const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);\n return {\n ...snapshot,\n isLoading: snapshot.status === 'loading',\n isAuthenticated: snapshot.status === 'authenticated',\n auth,\n account,\n };\n}\n","export class ThrottleAuthError extends Error {\n readonly code: string;\n readonly statusCode: number;\n readonly retryAfter?: number;\n readonly details?: unknown;\n\n constructor(args: { code: string; message: string; statusCode: number; retryAfter?: number; details?: unknown }) {\n super(args.message);\n this.name = 'ThrottleAuthError';\n this.code = args.code;\n this.statusCode = args.statusCode;\n this.retryAfter = args.retryAfter;\n this.details = args.details;\n }\n}\n","import { ThrottleAuthError } from './errors';\n\nexport interface RequestInput {\n method: string;\n path: string;\n baseUrl: string;\n publishableKey: string;\n accessToken?: string;\n stepUpToken?: string;\n body?: unknown;\n}\n\n/** Returns the UNWRAPPED `data` from the `{ data: … }` envelope. Throws ThrottleAuthError otherwise. */\nexport async function request(input: RequestInput): Promise<any> {\n const headers: Record<string, string> = { 'X-API-Key': input.publishableKey };\n if (input.body !== undefined) headers['Content-Type'] = 'application/json';\n if (input.accessToken) headers.Authorization = `Bearer ${input.accessToken}`;\n if (input.stepUpToken) headers['X-Step-Up'] = input.stepUpToken;\n\n const res = await fetch(`${input.baseUrl}/v1/storefront${input.path}`, {\n method: input.method,\n headers,\n ...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}),\n });\n\n const text = await res.text();\n let parsed: any = {};\n try {\n parsed = text ? JSON.parse(text) : {};\n } catch {\n // Non-JSON body — an HTML gateway page, say. Leave parsed empty so the\n // branch below still throws a typed error rather than a raw SyntaxError.\n }\n\n if (!res.ok) {\n const e = parsed?.error ?? {};\n // `Retry-After` is server-controlled but not trusted: Number('soon') is\n // NaN, and a NaN reaches the UI as \"wait NaN seconds\".\n const ra = Number(res.headers.get('retry-after'));\n throw new ThrottleAuthError({\n code: e.code ?? 'request_failed',\n message: e.message ?? `Request failed with ${res.status}`,\n statusCode: res.status,\n retryAfter: Number.isFinite(ra) && ra >= 0 ? ra : undefined,\n details: e.details,\n });\n }\n\n // `'data' in parsed`, not `parsed?.data ?? parsed`: a legitimate\n // `{ \"data\": null }` is nullish, and `??` would fall through and hand the\n // caller the whole envelope instead of the null the API actually sent.\n return parsed && typeof parsed === 'object' && 'data' in parsed ? parsed.data : (parsed ?? {});\n}\n","import type { Customer, Session } from './types';\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';\n\nexport interface AuthSnapshot {\n status: AuthStatus;\n customer: Customer | null;\n /**\n * False here does NOT only mean \"we emailed them and they have not clicked\".\n * While it is false the API may also be withholding pre-existing account\n * data behind the claim cutoff — empty order lists and a 403 on `PATCH /me`\n * are expected, not broken. See the plan header.\n */\n emailVerified: boolean;\n}\n\ntype Listener = (snapshot: AuthSnapshot) => void;\n\nexport class SessionState {\n #snapshot: AuthSnapshot = { status: 'loading', customer: null, emailVerified: false };\n #accessToken: string | null = null;\n #expiresAt: number | null = null;\n #listeners = new Set<Listener>();\n\n get(): AuthSnapshot { return this.#snapshot; }\n accessToken(): string | null { return this.#accessToken; }\n expiresAt(): number | null { return this.#expiresAt; }\n\n setSession(session: Session): void {\n this.#accessToken = session.accessToken;\n this.#expiresAt = Date.parse(session.expiresAt);\n this.#publish({\n status: 'authenticated',\n customer: session.customer,\n emailVerified: Boolean(session.customer?.emailVerified),\n });\n }\n\n setStatus(status: AuthStatus): void {\n this.#publish({ ...this.#snapshot, status });\n }\n\n clear(): void {\n this.#accessToken = null;\n this.#expiresAt = null;\n this.#publish({ status: 'unauthenticated', customer: null, emailVerified: false });\n }\n\n subscribe(fn: Listener): () => void {\n this.#listeners.add(fn);\n return () => { this.#listeners.delete(fn); };\n }\n\n #publish(next: AuthSnapshot): void {\n // Identity matters: React's useSyncExternalStore re-renders on reference\n // change, and a token refresh every ten minutes must not re-render the\n // whole account area when nothing visible changed.\n const same =\n next.status === this.#snapshot.status &&\n next.emailVerified === this.#snapshot.emailVerified &&\n customerEquals(next.customer, this.#snapshot.customer);\n if (same) return;\n this.#snapshot = next;\n for (const fn of [...this.#listeners]) {\n // A merchant's subscriber throwing must not stop the rest from hearing.\n try { fn(next); } catch { /* merchant callback */ }\n }\n }\n}\n\nfunction customerEquals(a: Customer | null, b: Customer | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]) as Set<keyof Customer>;\n for (const k of keys) if (a[k] !== b[k]) return false;\n return true;\n}\n","import type { AuthStorage } from './types';\n\nexport type { AuthStorage };\n\nexport function memoryStorage(): AuthStorage {\n const map = new Map<string, string>();\n return {\n get: (k) => map.get(k) ?? null,\n set: (k, v) => void map.set(k, v),\n remove: (k) => void map.delete(k),\n };\n}\n\n/**\n * localStorage when it works, memory when it does not. Safari private mode,\n * hardened browser settings and server-side rendering all make localStorage\n * either absent or throwing; none of them may take the storefront down.\n */\nexport function localStorageAdapter(): AuthStorage {\n const fallback = memoryStorage();\n const ls = (): Storage | null => {\n try {\n return typeof localStorage !== 'undefined' ? localStorage : null;\n } catch {\n return null;\n }\n };\n return {\n get(k) {\n try {\n const v = ls()?.getItem(k);\n if (v != null) return v;\n } catch { /* fall through */ }\n return fallback.get(k);\n },\n set(k, v) {\n fallback.set(k, v);\n try { ls()?.setItem(k, v); } catch { /* memory-only this session */ }\n },\n remove(k) {\n fallback.remove(k);\n try { ls()?.removeItem(k); } catch { /* already gone */ }\n },\n };\n}\n","import { request } from './request';\nimport { ThrottleAuthError } from './errors';\nimport { SessionState, type AuthSnapshot } from './session';\nimport { localStorageAdapter, type AuthStorage } from './storage';\nimport type { Customer, Session, ThrottleAuthConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://api.usethrottle.dev';\n/** Refresh this far before expiry so ordinary calls rarely see a 401 at all. */\nconst REFRESH_SKEW_MS = 30_000;\n\nexport interface DeviceSession {\n id: string;\n userAgent: string | null;\n ipPrefix: string | null;\n createdAt: string;\n lastUsedAt: string;\n current: boolean;\n}\n\nexport interface ThrottleAuth {\n register(email: string, password: string): Promise<{ status: 'active' | 'pending' }>;\n signIn(email: string, password: string): Promise<void>;\n signOut(): Promise<void>;\n signOutEverywhere(): Promise<void>;\n restore(): Promise<void>;\n getSnapshot(): AuthSnapshot;\n subscribe(fn: (s: AuthSnapshot) => void): () => void;\n stepUp(password: string): Promise<void>;\n call<T = any>(method: string, path: string, body?: unknown): Promise<T>;\n verifyEmail(token: string): Promise<void>;\n resendVerification(): Promise<void>;\n forgotPassword(email: string): Promise<void>;\n resetPassword(token: string, password: string): Promise<void>;\n changePassword(password: string): Promise<void>;\n listSessions(): Promise<DeviceSession[]>;\n revokeSession(id: string): Promise<void>;\n}\n\nexport function createThrottleAuth(config: ThrottleAuthConfig): ThrottleAuth {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const storage: AuthStorage = config.storage ?? localStorageAdapter();\n // Namespaced by key so two Throttle applications on one origin cannot read\n // each other's refresh token out of shared localStorage.\n const storageKey = `throttle_auth_rt:${config.publishableKey}`;\n const lockName = `throttle_auth_refresh:${config.publishableKey}`;\n\n const state = new SessionState();\n let refreshInFlight: Promise<void> | null = null;\n let stepUpToken: string | null = null;\n let stepUpExpiresAt = 0;\n\n const base = { baseUrl, publishableKey: config.publishableKey };\n\n async function persist(session: Session): Promise<void> {\n state.setSession(session);\n await storage.set(storageKey, session.refreshToken);\n }\n\n async function forget(): Promise<void> {\n stepUpToken = null;\n state.clear();\n await storage.remove(storageKey);\n }\n\n /**\n * Cross-tab single flight. Two tabs refreshing at once would each present the\n * same stored token; the second is a replay, and the server kills the family.\n * Web Locks serialises them; where it is unavailable the in-process guard\n * below still covers the common single-tab case.\n */\n function withLock<T>(fn: () => Promise<T>): Promise<T> {\n const locks = (globalThis as any).navigator?.locks;\n return locks?.request ? locks.request(lockName, fn) : fn();\n }\n\n async function doRefresh(): Promise<void> {\n await withLock(async () => {\n // Re-read INSIDE the lock: another tab may have rotated while we waited,\n // and presenting the token we read before the lock would be a replay.\n const refreshToken = await storage.get(storageKey);\n if (!refreshToken) {\n await forget();\n throw new ThrottleAuthError({ code: 'session_revoked', message: 'No session', statusCode: 401 });\n }\n try {\n const data = await request({ ...base, method: 'POST', path: '/auth/refresh', body: { refreshToken } });\n await persist(data as Session);\n } catch (err) {\n // Drop the stored token ONLY when the server actually refused the\n // session: 401 session_revoked, or 403 environment_mismatch. Those are\n // the two the refresh route returns, and there is nothing left to\n // retry with in either case.\n //\n // Everything else keeps the token. `request()` throws a\n // ThrottleAuthError for EVERY non-2xx, so testing the error class\n // alone also caught a 502 with an HTML gateway body — a buyer whose\n // tab refreshed during a rolling deploy was permanently signed out\n // client-side even though their session was never invalidated. A 429\n // is the same shape: \"try later\", not \"you are logged out\". And a\n // fetch() that rejects outright never produces a ThrottleAuthError,\n // so it was already handled.\n const refused =\n err instanceof ThrottleAuthError && (err.statusCode === 401 || err.statusCode === 403);\n if (refused) await forget();\n throw err;\n }\n });\n }\n\n function refresh(): Promise<void> {\n if (!refreshInFlight) {\n refreshInFlight = doRefresh().finally(() => { refreshInFlight = null; });\n }\n return refreshInFlight;\n }\n\n async function accessToken(): Promise<string | null> {\n const expires = state.expiresAt();\n if (state.accessToken() && expires && expires - Date.now() > REFRESH_SKEW_MS) {\n return state.accessToken();\n }\n if (await storage.get(storageKey)) {\n await refresh();\n return state.accessToken();\n }\n return state.accessToken();\n }\n\n async function ensureStepUp(): Promise<boolean> {\n if (stepUpToken && stepUpExpiresAt > Date.now()) return true;\n if (!config.onStepUpRequired) return false;\n const password = await config.onStepUpRequired();\n if (!password) return false;\n await stepUp(password);\n return true;\n }\n\n async function stepUp(password: string): Promise<void> {\n const data = await request({\n ...base, method: 'POST', path: '/auth/step-up',\n accessToken: await accessToken() ?? undefined, body: { password },\n });\n stepUpToken = data.stepUpToken;\n stepUpExpiresAt = Date.now() + (Number(data.expiresIn) || 300) * 1000 - 5_000;\n }\n\n async function call<T = any>(method: string, path: string, body?: unknown): Promise<T> {\n let retriedAuth = false;\n let retriedStepUp = false;\n // Only true on the retry that follows a `step_up_required` 403 — never\n // attached speculatively. Sending an authorization-grade header on every\n // request once minted meant it rode along on unrelated calls too, and\n // cross-origin each of those then failed CORS preflight. The cost is one\n // extra round trip per gated call, which is how the server signals\n // step-up in the first place.\n let sendStepUp = false;\n\n for (;;) {\n const token = await accessToken();\n try {\n return await request({\n ...base, method, path, body,\n accessToken: token ?? undefined,\n stepUpToken: sendStepUp ? (stepUpToken ?? undefined) : undefined,\n });\n } catch (err) {\n const e = err as ThrottleAuthError;\n // `session_revoked` belongs here too. The server returns it for a\n // stale session EPOCH as well as for a genuinely dead session, and a\n // changed password bumps the epoch while deliberately sparing the\n // current session — so one refresh recovers it. When the session\n // really is dead the refresh fails and forget() runs, which is the\n // same outcome, one request later.\n const expired = e.statusCode === 401 &&\n (e.code === 'unauthorized' || e.code === 'token_expired' || e.code === 'session_revoked');\n if (expired && !retriedAuth) {\n retriedAuth = true;\n await refresh(); // shared — ten callers here produce ONE request\n continue;\n }\n if (e.code === 'step_up_required' && !retriedStepUp) {\n retriedStepUp = true;\n // ensureStepUp() reuses the cached token when it's still within its\n // 300s validity (no re-prompt); it was never sent above, so this is\n // the first the server has seen of it for this call.\n if (await ensureStepUp()) { sendStepUp = true; continue; }\n }\n throw err;\n }\n }\n }\n\n return {\n async register(email, password) {\n const data = await request({ ...base, method: 'POST', path: '/auth/register', body: { email, password } });\n if (data?.status === 'pending') return { status: 'pending' };\n await persist(data as Session);\n return { status: 'active' };\n },\n\n async signIn(email, password) {\n const data = await request({ ...base, method: 'POST', path: '/auth/login', body: { email, password } });\n await persist(data as Session);\n },\n\n async signOut() {\n try { await call('POST', '/auth/logout'); } catch { /* local state still goes */ }\n await forget();\n },\n\n async signOutEverywhere() {\n try { await call('POST', '/auth/logout-all'); } catch { /* local state still goes */ }\n await forget();\n },\n\n async restore() {\n if (!(await storage.get(storageKey))) { state.setStatus('unauthenticated'); return; }\n try { await refresh(); } catch { /* forget() already ran */ }\n },\n\n getSnapshot: () => state.get(),\n subscribe: (fn) => state.subscribe(fn),\n stepUp,\n call,\n\n async verifyEmail(token) {\n await request({ ...base, method: 'POST', path: '/auth/verify-email', body: { token } });\n // `ver` is a claim baked into the access token, and the claim cutoff is\n // read from the session row. Without a new token the buyer stays gated —\n // and their own order history stays hidden — for the rest of this\n // token's ten minutes.\n if (await storage.get(storageKey)) {\n try { await refresh(); } catch { /* verification still succeeded */ }\n }\n },\n\n async resendVerification() {\n await call('POST', '/auth/verify-email/resend');\n },\n\n async forgotPassword(email) {\n // Always 202, whether or not the address has an account. Do not infer\n // anything from it and do not tell the buyer whether it existed.\n await request({ ...base, method: 'POST', path: '/auth/forgot-password', body: { email } });\n },\n\n async resetPassword(token, password) {\n await request({ ...base, method: 'POST', path: '/auth/reset-password', body: { token, password } });\n // The server revokes every session on reset, this one included.\n await forget();\n },\n\n async changePassword(password) {\n await call('POST', '/me/change-password', { password });\n // The change bumps the session epoch. This session was deliberately\n // spared, but the access token in hand now carries a stale epoch and the\n // next write would 401. Rotate now rather than on the buyer's next click\n // — and do not let a failed rotation report a change that already\n // succeeded server-side as a failure.\n try { await refresh(); } catch { /* the password change already succeeded */ }\n },\n\n async listSessions() {\n return call<DeviceSession[]>('GET', '/me/sessions');\n },\n\n async revokeSession(id) {\n const mine = await call<DeviceSession[]>('GET', '/me/sessions');\n const current = mine.find((s) => s.current);\n await call('DELETE', `/me/sessions/${encodeURIComponent(id)}`);\n if (current?.id === id) await forget();\n },\n };\n}\n\nexport type { AuthSnapshot, Customer, Session, ThrottleAuthConfig };\n","import type { ThrottleAuth } from './client';\nimport type {\n Address, AddressInput, Customer, Invoice, Order, OrderDetail,\n PaymentMethod, ProfilePatch, Subscription,\n} from './types';\n\nexport interface Page { cursor?: string; limit?: number }\n\nexport interface ThrottleAccount {\n getProfile(): Promise<Customer>;\n updateProfile(patch: ProfilePatch): Promise<Customer>;\n listAddresses(): Promise<Address[]>;\n createAddress(input: AddressInput): Promise<Address>;\n updateAddress(id: string, patch: Partial<AddressInput>): Promise<Address>;\n deleteAddress(id: string): Promise<void>;\n listPaymentMethods(): Promise<PaymentMethod[]>;\n setDefaultPaymentMethod(id: string): Promise<PaymentMethod>;\n deletePaymentMethod(id: string): Promise<void>;\n listOrders(page?: Page): Promise<Order[]>;\n getOrder(id: string): Promise<OrderDetail>;\n listInvoices(page?: Page): Promise<Invoice[]>;\n getInvoice(id: string): Promise<Invoice>;\n /** A 600-second signed URL. Fetch it on click — it expires. */\n getInvoicePdfUrl(id: string): Promise<{ url: string }>;\n listSubscriptions(page?: Page): Promise<Subscription[]>;\n getSubscription(id: string): Promise<Subscription>;\n cancelSubscription(id: string, opts?: { atPeriodEnd?: boolean }): Promise<Subscription>;\n pauseSubscription(id: string): Promise<Subscription>;\n resumeSubscription(id: string): Promise<Subscription>;\n claimCart(cartId: string): Promise<{ cartId: string; customerId: string }>;\n}\n\nconst seg = (v: string) => encodeURIComponent(v);\n\nfunction query(page?: Page): string {\n const params = new URLSearchParams();\n if (page?.cursor) params.set('cursor', page.cursor);\n if (page?.limit != null) params.set('limit', String(page.limit));\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\nexport function createAccount(auth: ThrottleAuth): ThrottleAccount {\n const { call } = auth;\n return {\n getProfile: () => call('GET', '/me'),\n updateProfile: (patch) => call('PATCH', '/me', patch),\n\n listAddresses: () => call('GET', '/me/addresses'),\n createAddress: (input) => call('POST', '/me/addresses', input),\n updateAddress: (id, patch) => call('PATCH', `/me/addresses/${seg(id)}`, patch),\n deleteAddress: (id) => call('DELETE', `/me/addresses/${seg(id)}`),\n\n listPaymentMethods: () => call('GET', '/me/payment-methods'),\n // The platform has no \"change this subscription's card\" capability, so\n // this is how a buyer changes what they are billed on: move the default.\n setDefaultPaymentMethod: (id) => call('PATCH', `/me/payment-methods/${seg(id)}`, { isDefault: true }),\n deletePaymentMethod: (id) => call('DELETE', `/me/payment-methods/${seg(id)}`),\n\n listOrders: (page) => call('GET', `/me/orders${query(page)}`),\n getOrder: (id) => call('GET', `/me/orders/${seg(id)}`),\n listInvoices: (page) => call('GET', `/me/invoices${query(page)}`),\n getInvoice: (id) => call('GET', `/me/invoices/${seg(id)}`),\n getInvoicePdfUrl: (id) => call('GET', `/me/invoices/${seg(id)}/pdf`),\n listSubscriptions: (page) => call('GET', `/me/subscriptions${query(page)}`),\n getSubscription: (id) => call('GET', `/me/subscriptions/${seg(id)}`),\n\n cancelSubscription: (id, opts) => call('POST', `/me/subscriptions/${seg(id)}/cancel`, opts),\n pauseSubscription: (id) => call('POST', `/me/subscriptions/${seg(id)}/pause`),\n resumeSubscription: (id) => call('POST', `/me/subscriptions/${seg(id)}/resume`),\n\n claimCart: (cartId) => call('POST', `/me/carts/${seg(cartId)}/claim`),\n };\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { ThrottleAuthError } from '../errors';\nimport type { Address, AddressInput, Customer, Invoice, Order, OrderDetail, PaymentMethod, ProfilePatch, Subscription } from '../types';\nimport type { Page } from '../account';\nimport { useAuth } from './context';\n\nexport interface Resource<T> {\n data: T;\n isLoading: boolean;\n error: ThrottleAuthError | null;\n refetch(): Promise<void>;\n}\n\nfunction asError(err: unknown): ThrottleAuthError {\n if (err instanceof ThrottleAuthError) return err;\n const anyErr = err as any;\n return new ThrottleAuthError({\n code: anyErr?.code ?? 'request_failed',\n message: anyErr?.message ?? String(err),\n statusCode: anyErr?.statusCode ?? 0,\n });\n}\n\n/**\n * One fetch-on-auth-change primitive behind every resource hook. `emailVerified`\n * is a dependency on purpose: verifying lifts the claim cutoff, and lists that\n * were legitimately empty a moment ago are not empty any more.\n */\nfunction useResource<T>(empty: T, load: () => Promise<T>, deps: unknown[]): Resource<T> {\n const { status, emailVerified } = useAuth();\n const [data, setData] = useState<T>(empty);\n const [isLoading, setIsLoading] = useState(status === 'loading');\n const [error, setError] = useState<ThrottleAuthError | null>(null);\n const gen = useRef(0);\n const alive = useRef(true);\n\n useEffect(() => {\n // Set on mount, not only cleared on unmount: React StrictMode mounts,\n // unmounts and remounts in development, and a ref that is only ever set\n // to false would silently discard every response for the rest of the\n // component's life.\n alive.current = true;\n return () => { alive.current = false; gen.current++; };\n }, []);\n\n const refetch = useCallback(async () => {\n const mine = ++gen.current;\n setIsLoading(true);\n try {\n const next = await load();\n if (!alive.current || gen.current !== mine) return;\n setData(next);\n setError(null);\n } catch (err) {\n if (!alive.current || gen.current !== mine) return;\n setError(asError(err));\n } finally {\n if (alive.current && gen.current === mine) setIsLoading(false);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n if (status !== 'authenticated') {\n gen.current++; // abandon anything in flight\n setData(empty);\n setError(null);\n setIsLoading(status === 'loading');\n return;\n }\n void refetch();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, emailVerified, refetch]);\n\n return { data, isLoading, error, refetch };\n}\n\nexport function useCustomer(): Resource<Customer | null> & { update(patch: ProfilePatch): Promise<void> } {\n const { account } = useAuth();\n const res = useResource<Customer | null>(null, () => account.getProfile(), [account]);\n const update = useCallback(async (patch: ProfilePatch) => {\n await account.updateProfile(patch);\n await res.refetch();\n }, [account, res]);\n return { ...res, update };\n}\n\nexport function useAddresses(): Resource<Address[]> & {\n create(input: AddressInput): Promise<void>;\n update(id: string, patch: Partial<AddressInput>): Promise<void>;\n remove(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<Address[]>([], () => account.listAddresses(), [account]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n create: (input) => after(account.createAddress(input)),\n update: (id, patch) => after(account.updateAddress(id, patch)),\n remove: (id) => after(account.deleteAddress(id)),\n };\n}\n\nexport function useSavedCards(): Resource<PaymentMethod[]> & {\n setDefault(id: string): Promise<void>;\n remove(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<PaymentMethod[]>([], () => account.listPaymentMethods(), [account]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n setDefault: (id) => after(account.setDefaultPaymentMethod(id)),\n remove: (id) => after(account.deletePaymentMethod(id)),\n };\n}\n\nexport function useOrders(page?: Page): Resource<Order[]> {\n const { account } = useAuth();\n return useResource<Order[]>([], () => account.listOrders(page), [account, page?.cursor, page?.limit]);\n}\n\nexport function useInvoices(page?: Page): Resource<Invoice[]> {\n const { account } = useAuth();\n return useResource<Invoice[]>([], () => account.listInvoices(page), [account, page?.cursor, page?.limit]);\n}\n\nexport function useSubscriptions(page?: Page): Resource<Subscription[]> & {\n cancel(id: string, opts?: { atPeriodEnd?: boolean }): Promise<void>;\n pause(id: string): Promise<void>;\n resume(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<Subscription[]>([], () => account.listSubscriptions(page),\n [account, page?.cursor, page?.limit]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n cancel: (id, opts) => after(account.cancelSubscription(id, opts)),\n pause: (id) => after(account.pauseSubscription(id)),\n resume: (id) => after(account.resumeSubscription(id)),\n };\n}\n\n/**\n * Single-resource reads for the detail views. A `null` id means \"nothing\n * selected\" and must not call the API — `useResource` still runs, so the\n * loader resolves to null rather than being skipped, which keeps the\n * loading/error shape identical whether or not an id is present.\n */\nexport function useOrder(id: string | null): Resource<OrderDetail | null> {\n const { account } = useAuth();\n return useResource<OrderDetail | null>(null, async () => (id ? account.getOrder(id) : null), [account, id]);\n}\n\nexport function useInvoice(id: string | null): Resource<Invoice | null> {\n const { account } = useAuth();\n return useResource<Invoice | null>(null, async () => (id ? account.getInvoice(id) : null), [account, id]);\n}\n"],"mappings":"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","names":["import_react"]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.tsx","../../src/react/context.tsx","../../src/errors.ts","../../src/request.ts","../../src/session.ts","../../src/storage.ts","../../src/client.ts","../../src/account.ts","../../src/react/hooks.ts"],"sourcesContent":["export { ThrottleAuthProvider, useAuth, useAuthContext } from './context';\nexport type { ThrottleAuthProviderProps, UseAuthResult } from './context';\nexport { useCustomer, useAddresses, useSavedCards, useOrders, useInvoices, useSubscriptions,\n useOrder, useInvoice } from './hooks';\nexport type { Resource } from './hooks';\nexport * from '../index';\n","import { createContext, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from 'react';\nimport type { ReactNode } from 'react';\nimport { createThrottleAuth, type ThrottleAuth } from '../client';\nimport { createAccount, type ThrottleAccount } from '../account';\nimport type { AuthSnapshot } from '../session';\nimport type { ThrottleAuthConfig } from '../types';\n\nexport interface ThrottleAuthProviderProps extends Partial<ThrottleAuthConfig> {\n publishableKey: string;\n /** Inject a pre-built client. Tests use it; merchants sharing one client across roots may too. */\n auth?: ThrottleAuth;\n children: ReactNode;\n}\n\ninterface ContextValue { auth: ThrottleAuth; account: ThrottleAccount }\n\nconst Ctx = createContext<ContextValue | null>(null);\n\nexport function ThrottleAuthProvider(props: ThrottleAuthProviderProps) {\n const { auth: injected, publishableKey, baseUrl, storage, onStepUpRequired, children } = props;\n\n // `storage` and `onStepUpRequired` are deliberately NOT dependencies: they are\n // read once at mount. A merchant passing `storage={memoryStorage()}` inline —\n // which the README suggests — would otherwise get a new client on every parent\n // render, and with it a fresh empty SessionState that reads as signed out.\n const value = useMemo<ContextValue>(() => {\n const auth = injected ?? createThrottleAuth({ publishableKey, baseUrl, storage, onStepUpRequired });\n return { auth, account: createAccount(auth) };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [injected, publishableKey, baseUrl]);\n\n const restored = useRef<ThrottleAuth | null>(null);\n useEffect(() => {\n // Once per client. StrictMode runs effects twice in development, and a\n // second restore() would present the refresh token that the first one\n // just rotated away.\n if (restored.current === value.auth) return;\n restored.current = value.auth;\n void value.auth.restore();\n }, [value]);\n\n return <Ctx.Provider value={value}>{children}</Ctx.Provider>;\n}\n\nfunction useCtx(): ContextValue {\n const ctx = useContext(Ctx);\n if (!ctx) throw new Error('useAuth must be used inside a <ThrottleAuthProvider>');\n return ctx;\n}\n\nexport function useAuthContext(): ContextValue { return useCtx(); }\n\nexport type UseAuthResult = AuthSnapshot & {\n isLoading: boolean;\n isAuthenticated: boolean;\n auth: ThrottleAuth;\n account: ThrottleAccount;\n};\n\nexport function useAuth(): UseAuthResult {\n const { auth, account } = useCtx();\n const snapshot = useSyncExternalStore(auth.subscribe, auth.getSnapshot, auth.getSnapshot);\n return {\n ...snapshot,\n isLoading: snapshot.status === 'loading',\n isAuthenticated: snapshot.status === 'authenticated',\n auth,\n account,\n };\n}\n","export class ThrottleAuthError extends Error {\n readonly code: string;\n readonly statusCode: number;\n readonly retryAfter?: number;\n readonly details?: unknown;\n\n constructor(args: { code: string; message: string; statusCode: number; retryAfter?: number; details?: unknown }) {\n super(args.message);\n this.name = 'ThrottleAuthError';\n this.code = args.code;\n this.statusCode = args.statusCode;\n this.retryAfter = args.retryAfter;\n this.details = args.details;\n }\n}\n","import { ThrottleAuthError } from './errors';\n\nexport interface RequestInput {\n method: string;\n path: string;\n baseUrl: string;\n publishableKey: string;\n accessToken?: string;\n stepUpToken?: string;\n body?: unknown;\n}\n\n/** Returns the UNWRAPPED `data` from the `{ data: … }` envelope. Throws ThrottleAuthError otherwise. */\nexport async function request(input: RequestInput): Promise<any> {\n const headers: Record<string, string> = { 'X-API-Key': input.publishableKey };\n if (input.body !== undefined) headers['Content-Type'] = 'application/json';\n if (input.accessToken) headers.Authorization = `Bearer ${input.accessToken}`;\n if (input.stepUpToken) headers['X-Step-Up'] = input.stepUpToken;\n\n const res = await fetch(`${input.baseUrl}/v1/storefront${input.path}`, {\n method: input.method,\n headers,\n ...(input.body !== undefined ? { body: JSON.stringify(input.body) } : {}),\n });\n\n const text = await res.text();\n let parsed: any = {};\n try {\n parsed = text ? JSON.parse(text) : {};\n } catch {\n // Non-JSON body — an HTML gateway page, say. Leave parsed empty so the\n // branch below still throws a typed error rather than a raw SyntaxError.\n }\n\n if (!res.ok) {\n const e = parsed?.error ?? {};\n // `Retry-After` is server-controlled but not trusted: Number('soon') is\n // NaN, and a NaN reaches the UI as \"wait NaN seconds\".\n const ra = Number(res.headers.get('retry-after'));\n throw new ThrottleAuthError({\n code: e.code ?? 'request_failed',\n message: e.message ?? `Request failed with ${res.status}`,\n statusCode: res.status,\n retryAfter: Number.isFinite(ra) && ra >= 0 ? ra : undefined,\n details: e.details,\n });\n }\n\n // `'data' in parsed`, not `parsed?.data ?? parsed`: a legitimate\n // `{ \"data\": null }` is nullish, and `??` would fall through and hand the\n // caller the whole envelope instead of the null the API actually sent.\n return parsed && typeof parsed === 'object' && 'data' in parsed ? parsed.data : (parsed ?? {});\n}\n","import type { Customer, Session } from './types';\n\nexport type AuthStatus = 'loading' | 'authenticated' | 'unauthenticated';\n\nexport interface AuthSnapshot {\n status: AuthStatus;\n customer: Customer | null;\n /**\n * False here does NOT only mean \"we emailed them and they have not clicked\".\n * While it is false the API may also be withholding pre-existing account\n * data behind the claim cutoff — empty order lists and a 403 on `PATCH /me`\n * are expected, not broken. See the plan header.\n */\n emailVerified: boolean;\n}\n\ntype Listener = (snapshot: AuthSnapshot) => void;\n\nexport class SessionState {\n #snapshot: AuthSnapshot = { status: 'loading', customer: null, emailVerified: false };\n #accessToken: string | null = null;\n #expiresAt: number | null = null;\n #listeners = new Set<Listener>();\n\n get(): AuthSnapshot { return this.#snapshot; }\n accessToken(): string | null { return this.#accessToken; }\n expiresAt(): number | null { return this.#expiresAt; }\n\n setSession(session: Session): void {\n this.#accessToken = session.accessToken;\n this.#expiresAt = Date.parse(session.expiresAt);\n this.#publish({\n status: 'authenticated',\n customer: session.customer,\n emailVerified: Boolean(session.customer?.emailVerified),\n });\n }\n\n setStatus(status: AuthStatus): void {\n this.#publish({ ...this.#snapshot, status });\n }\n\n clear(): void {\n this.#accessToken = null;\n this.#expiresAt = null;\n this.#publish({ status: 'unauthenticated', customer: null, emailVerified: false });\n }\n\n subscribe(fn: Listener): () => void {\n this.#listeners.add(fn);\n return () => { this.#listeners.delete(fn); };\n }\n\n #publish(next: AuthSnapshot): void {\n // Identity matters: React's useSyncExternalStore re-renders on reference\n // change, and a token refresh every ten minutes must not re-render the\n // whole account area when nothing visible changed.\n const same =\n next.status === this.#snapshot.status &&\n next.emailVerified === this.#snapshot.emailVerified &&\n customerEquals(next.customer, this.#snapshot.customer);\n if (same) return;\n this.#snapshot = next;\n for (const fn of [...this.#listeners]) {\n // A merchant's subscriber throwing must not stop the rest from hearing.\n try { fn(next); } catch { /* merchant callback */ }\n }\n }\n}\n\nfunction customerEquals(a: Customer | null, b: Customer | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]) as Set<keyof Customer>;\n for (const k of keys) if (a[k] !== b[k]) return false;\n return true;\n}\n","import type { AuthStorage } from './types';\n\nexport type { AuthStorage };\n\nexport function memoryStorage(): AuthStorage {\n const map = new Map<string, string>();\n return {\n get: (k) => map.get(k) ?? null,\n set: (k, v) => void map.set(k, v),\n remove: (k) => void map.delete(k),\n };\n}\n\n/**\n * localStorage when it works, memory when it does not. Safari private mode,\n * hardened browser settings and server-side rendering all make localStorage\n * either absent or throwing; none of them may take the storefront down.\n */\nexport function localStorageAdapter(): AuthStorage {\n const fallback = memoryStorage();\n const ls = (): Storage | null => {\n try {\n return typeof localStorage !== 'undefined' ? localStorage : null;\n } catch {\n return null;\n }\n };\n return {\n get(k) {\n try {\n const v = ls()?.getItem(k);\n if (v != null) return v;\n } catch { /* fall through */ }\n return fallback.get(k);\n },\n set(k, v) {\n fallback.set(k, v);\n try { ls()?.setItem(k, v); } catch { /* memory-only this session */ }\n },\n remove(k) {\n fallback.remove(k);\n try { ls()?.removeItem(k); } catch { /* already gone */ }\n },\n };\n}\n","import { request } from './request';\nimport { ThrottleAuthError } from './errors';\nimport { SessionState, type AuthSnapshot } from './session';\nimport { localStorageAdapter, type AuthStorage } from './storage';\nimport type { Customer, Session, ThrottleAuthConfig } from './types';\n\nconst DEFAULT_BASE_URL = 'https://api.usethrottle.dev';\n/** Refresh this far before expiry so ordinary calls rarely see a 401 at all. */\nconst REFRESH_SKEW_MS = 30_000;\n\nexport interface DeviceSession {\n id: string;\n userAgent: string | null;\n ipPrefix: string | null;\n createdAt: string;\n lastUsedAt: string;\n current: boolean;\n}\n\nexport interface ThrottleAuth {\n register(email: string, password: string): Promise<{ status: 'active' | 'pending' }>;\n signIn(email: string, password: string): Promise<void>;\n signOut(): Promise<void>;\n signOutEverywhere(): Promise<void>;\n restore(): Promise<void>;\n getSnapshot(): AuthSnapshot;\n subscribe(fn: (s: AuthSnapshot) => void): () => void;\n stepUp(password: string): Promise<void>;\n call<T = any>(method: string, path: string, body?: unknown): Promise<T>;\n verifyEmail(token: string): Promise<void>;\n resendVerification(): Promise<void>;\n forgotPassword(email: string): Promise<void>;\n resetPassword(token: string, password: string): Promise<void>;\n changePassword(password: string): Promise<void>;\n listSessions(): Promise<DeviceSession[]>;\n revokeSession(id: string): Promise<void>;\n}\n\nexport function createThrottleAuth(config: ThrottleAuthConfig): ThrottleAuth {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n const storage: AuthStorage = config.storage ?? localStorageAdapter();\n // Namespaced by key so two Throttle applications on one origin cannot read\n // each other's refresh token out of shared localStorage.\n const storageKey = `throttle_auth_rt:${config.publishableKey}`;\n const lockName = `throttle_auth_refresh:${config.publishableKey}`;\n\n const state = new SessionState();\n let refreshInFlight: Promise<void> | null = null;\n let stepUpToken: string | null = null;\n let stepUpExpiresAt = 0;\n\n const base = { baseUrl, publishableKey: config.publishableKey };\n\n async function persist(session: Session): Promise<void> {\n state.setSession(session);\n await storage.set(storageKey, session.refreshToken);\n }\n\n async function forget(): Promise<void> {\n stepUpToken = null;\n state.clear();\n await storage.remove(storageKey);\n }\n\n /**\n * Cross-tab single flight. Two tabs refreshing at once would each present the\n * same stored token; the second is a replay, and the server kills the family.\n * Web Locks serialises them; where it is unavailable the in-process guard\n * below still covers the common single-tab case.\n */\n function withLock<T>(fn: () => Promise<T>): Promise<T> {\n const locks = (globalThis as any).navigator?.locks;\n return locks?.request ? locks.request(lockName, fn) : fn();\n }\n\n async function doRefresh(): Promise<void> {\n await withLock(async () => {\n // Re-read INSIDE the lock: another tab may have rotated while we waited,\n // and presenting the token we read before the lock would be a replay.\n const refreshToken = await storage.get(storageKey);\n if (!refreshToken) {\n await forget();\n throw new ThrottleAuthError({ code: 'session_revoked', message: 'No session', statusCode: 401 });\n }\n try {\n const data = await request({ ...base, method: 'POST', path: '/auth/refresh', body: { refreshToken } });\n await persist(data as Session);\n } catch (err) {\n // Drop the stored token ONLY when the server actually refused the\n // session: 401 session_revoked, or 403 environment_mismatch. Those are\n // the two the refresh route returns, and there is nothing left to\n // retry with in either case.\n //\n // Everything else keeps the token. `request()` throws a\n // ThrottleAuthError for EVERY non-2xx, so testing the error class\n // alone also caught a 502 with an HTML gateway body — a buyer whose\n // tab refreshed during a rolling deploy was permanently signed out\n // client-side even though their session was never invalidated. A 429\n // is the same shape: \"try later\", not \"you are logged out\". And a\n // fetch() that rejects outright never produces a ThrottleAuthError,\n // so it was already handled.\n const refused =\n err instanceof ThrottleAuthError && (err.statusCode === 401 || err.statusCode === 403);\n if (refused) await forget();\n throw err;\n }\n });\n }\n\n function refresh(): Promise<void> {\n if (!refreshInFlight) {\n refreshInFlight = doRefresh().finally(() => { refreshInFlight = null; });\n }\n return refreshInFlight;\n }\n\n async function accessToken(): Promise<string | null> {\n const expires = state.expiresAt();\n if (state.accessToken() && expires && expires - Date.now() > REFRESH_SKEW_MS) {\n return state.accessToken();\n }\n if (await storage.get(storageKey)) {\n await refresh();\n return state.accessToken();\n }\n return state.accessToken();\n }\n\n async function ensureStepUp(): Promise<boolean> {\n if (stepUpToken && stepUpExpiresAt > Date.now()) return true;\n if (!config.onStepUpRequired) return false;\n const password = await config.onStepUpRequired();\n if (!password) return false;\n await stepUp(password);\n return true;\n }\n\n async function stepUp(password: string): Promise<void> {\n const data = await request({\n ...base, method: 'POST', path: '/auth/step-up',\n accessToken: await accessToken() ?? undefined, body: { password },\n });\n stepUpToken = data.stepUpToken;\n stepUpExpiresAt = Date.now() + (Number(data.expiresIn) || 300) * 1000 - 5_000;\n }\n\n async function call<T = any>(method: string, path: string, body?: unknown): Promise<T> {\n let retriedAuth = false;\n let retriedStepUp = false;\n // Only true on the retry that follows a `step_up_required` 403 — never\n // attached speculatively. Sending an authorization-grade header on every\n // request once minted meant it rode along on unrelated calls too, and\n // cross-origin each of those then failed CORS preflight. The cost is one\n // extra round trip per gated call, which is how the server signals\n // step-up in the first place.\n let sendStepUp = false;\n\n for (;;) {\n const token = await accessToken();\n try {\n return await request({\n ...base, method, path, body,\n accessToken: token ?? undefined,\n stepUpToken: sendStepUp ? (stepUpToken ?? undefined) : undefined,\n });\n } catch (err) {\n const e = err as ThrottleAuthError;\n // `session_revoked` belongs here too. The server returns it for a\n // stale session EPOCH as well as for a genuinely dead session, and a\n // changed password bumps the epoch while deliberately sparing the\n // current session — so one refresh recovers it. When the session\n // really is dead the refresh fails and forget() runs, which is the\n // same outcome, one request later.\n const expired = e.statusCode === 401 &&\n (e.code === 'unauthorized' || e.code === 'token_expired' || e.code === 'session_revoked');\n if (expired && !retriedAuth) {\n retriedAuth = true;\n await refresh(); // shared — ten callers here produce ONE request\n continue;\n }\n if (e.code === 'step_up_required' && !retriedStepUp) {\n retriedStepUp = true;\n // ensureStepUp() reuses the cached token when it's still within its\n // 300s validity (no re-prompt); it was never sent above, so this is\n // the first the server has seen of it for this call.\n if (await ensureStepUp()) { sendStepUp = true; continue; }\n }\n throw err;\n }\n }\n }\n\n return {\n async register(email, password) {\n const data = await request({ ...base, method: 'POST', path: '/auth/register', body: { email, password } });\n if (data?.status === 'pending') return { status: 'pending' };\n await persist(data as Session);\n return { status: 'active' };\n },\n\n async signIn(email, password) {\n const data = await request({ ...base, method: 'POST', path: '/auth/login', body: { email, password } });\n await persist(data as Session);\n },\n\n async signOut() {\n try { await call('POST', '/auth/logout'); } catch { /* local state still goes */ }\n await forget();\n },\n\n async signOutEverywhere() {\n try { await call('POST', '/auth/logout-all'); } catch { /* local state still goes */ }\n await forget();\n },\n\n async restore() {\n if (!(await storage.get(storageKey))) { state.setStatus('unauthenticated'); return; }\n try { await refresh(); } catch { /* forget() already ran */ }\n },\n\n getSnapshot: () => state.get(),\n subscribe: (fn) => state.subscribe(fn),\n stepUp,\n call,\n\n async verifyEmail(token) {\n await request({ ...base, method: 'POST', path: '/auth/verify-email', body: { token } });\n // `ver` is a claim baked into the access token, and the claim cutoff is\n // read from the session row. Without a new token the buyer stays gated —\n // and their own order history stays hidden — for the rest of this\n // token's ten minutes.\n if (await storage.get(storageKey)) {\n try { await refresh(); } catch { /* verification still succeeded */ }\n }\n },\n\n async resendVerification() {\n await call('POST', '/auth/verify-email/resend');\n },\n\n async forgotPassword(email) {\n // Always 202, whether or not the address has an account. Do not infer\n // anything from it and do not tell the buyer whether it existed.\n await request({ ...base, method: 'POST', path: '/auth/forgot-password', body: { email } });\n },\n\n async resetPassword(token, password) {\n await request({ ...base, method: 'POST', path: '/auth/reset-password', body: { token, password } });\n // The server revokes every session on reset, this one included.\n await forget();\n },\n\n async changePassword(password) {\n await call('POST', '/me/change-password', { password });\n // The change bumps the session epoch. This session was deliberately\n // spared, but the access token in hand now carries a stale epoch and the\n // next write would 401. Rotate now rather than on the buyer's next click\n // — and do not let a failed rotation report a change that already\n // succeeded server-side as a failure.\n try { await refresh(); } catch { /* the password change already succeeded */ }\n },\n\n async listSessions() {\n return call<DeviceSession[]>('GET', '/me/sessions');\n },\n\n async revokeSession(id) {\n const mine = await call<DeviceSession[]>('GET', '/me/sessions');\n const current = mine.find((s) => s.current);\n await call('DELETE', `/me/sessions/${encodeURIComponent(id)}`);\n if (current?.id === id) await forget();\n },\n };\n}\n\nexport type { AuthSnapshot, Customer, Session, ThrottleAuthConfig };\n","import type { ThrottleAuth } from './client';\nimport type {\n Address, AddressInput, Customer, Invoice, Order, OrderDetail,\n PaymentMethod, ProfilePatch, Subscription,\n} from './types';\n\nexport interface Page { cursor?: string; limit?: number }\n\n/** What the payment widget needs to render a card form. Passed through from\n * the processor — `buyerExternalIdentifier` in particular must reach the\n * widget unchanged, because the session token pins it and the processor\n * refuses a mismatch by rendering nothing at all. */\nexport interface CardSetupSession {\n embedToken: string;\n checkoutSessionId: string;\n amount: number;\n currency: string;\n merchantAccountId?: string;\n environment?: string;\n buyerExternalIdentifier: string;\n country: string;\n /** A ready-made page that hosts the card form, if you would rather redirect\n * or iframe than embed the widget yourself. */\n hostedUrl: string;\n embedUrl: string;\n}\n\nexport interface ThrottleAccount {\n getProfile(): Promise<Customer>;\n updateProfile(patch: ProfilePatch): Promise<Customer>;\n listAddresses(): Promise<Address[]>;\n createAddress(input: AddressInput): Promise<Address>;\n updateAddress(id: string, patch: Partial<AddressInput>): Promise<Address>;\n deleteAddress(id: string): Promise<void>;\n listPaymentMethods(): Promise<PaymentMethod[]>;\n setDefaultPaymentMethod(id: string): Promise<PaymentMethod>;\n deletePaymentMethod(id: string): Promise<void>;\n /** Opens a processor-hosted session the buyer types their card into. The\n * returned values are handed straight to the payment widget. */\n startCardSetup(): Promise<CardSetupSession>;\n /** Records the card the widget just vaulted, identified by the transaction\n * the widget returns. Safe to call twice with the same id. */\n confirmCardSetup(transactionId: string): Promise<PaymentMethod>;\n listOrders(page?: Page): Promise<Order[]>;\n getOrder(id: string): Promise<OrderDetail>;\n listInvoices(page?: Page): Promise<Invoice[]>;\n getInvoice(id: string): Promise<Invoice>;\n /** A 600-second signed URL. Fetch it on click — it expires. */\n getInvoicePdfUrl(id: string): Promise<{ url: string }>;\n listSubscriptions(page?: Page): Promise<Subscription[]>;\n getSubscription(id: string): Promise<Subscription>;\n cancelSubscription(id: string, opts?: { atPeriodEnd?: boolean }): Promise<Subscription>;\n pauseSubscription(id: string): Promise<Subscription>;\n resumeSubscription(id: string): Promise<Subscription>;\n claimCart(cartId: string): Promise<{ cartId: string; customerId: string }>;\n}\n\nconst seg = (v: string) => encodeURIComponent(v);\n\nfunction query(page?: Page): string {\n const params = new URLSearchParams();\n if (page?.cursor) params.set('cursor', page.cursor);\n if (page?.limit != null) params.set('limit', String(page.limit));\n const s = params.toString();\n return s ? `?${s}` : '';\n}\n\nexport function createAccount(auth: ThrottleAuth): ThrottleAccount {\n const { call } = auth;\n return {\n getProfile: () => call('GET', '/me'),\n updateProfile: (patch) => call('PATCH', '/me', patch),\n\n listAddresses: () => call('GET', '/me/addresses'),\n createAddress: (input) => call('POST', '/me/addresses', input),\n updateAddress: (id, patch) => call('PATCH', `/me/addresses/${seg(id)}`, patch),\n deleteAddress: (id) => call('DELETE', `/me/addresses/${seg(id)}`),\n\n listPaymentMethods: () => call('GET', '/me/payment-methods'),\n // The platform has no \"change this subscription's card\" capability, so\n // this is how a buyer changes what they are billed on: move the default.\n setDefaultPaymentMethod: (id) => call('PATCH', `/me/payment-methods/${seg(id)}`, { isDefault: true }),\n deletePaymentMethod: (id) => call('DELETE', `/me/payment-methods/${seg(id)}`),\n\n // Saving a card is two calls because the card number never reaches us: the\n // first mints a processor-hosted embed session, the buyer types their card\n // into that, and the second records what the processor vaulted. Everything\n // about the saved card is read back from the processor, so nothing here\n // trusts what the browser claims the card was.\n startCardSetup: () => call('POST', '/me/payment-methods/embed-token'),\n confirmCardSetup: (transactionId) =>\n call('POST', '/me/payment-methods/confirm', { transactionId }),\n\n listOrders: (page) => call('GET', `/me/orders${query(page)}`),\n getOrder: (id) => call('GET', `/me/orders/${seg(id)}`),\n listInvoices: (page) => call('GET', `/me/invoices${query(page)}`),\n getInvoice: (id) => call('GET', `/me/invoices/${seg(id)}`),\n getInvoicePdfUrl: (id) => call('GET', `/me/invoices/${seg(id)}/pdf`),\n listSubscriptions: (page) => call('GET', `/me/subscriptions${query(page)}`),\n getSubscription: (id) => call('GET', `/me/subscriptions/${seg(id)}`),\n\n cancelSubscription: (id, opts) => call('POST', `/me/subscriptions/${seg(id)}/cancel`, opts),\n pauseSubscription: (id) => call('POST', `/me/subscriptions/${seg(id)}/pause`),\n resumeSubscription: (id) => call('POST', `/me/subscriptions/${seg(id)}/resume`),\n\n claimCart: (cartId) => call('POST', `/me/carts/${seg(cartId)}/claim`),\n };\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { ThrottleAuthError } from '../errors';\nimport type { Address, AddressInput, Customer, Invoice, Order, OrderDetail, PaymentMethod, ProfilePatch, Subscription } from '../types';\nimport type { Page } from '../account';\nimport { useAuth } from './context';\n\nexport interface Resource<T> {\n data: T;\n isLoading: boolean;\n error: ThrottleAuthError | null;\n refetch(): Promise<void>;\n}\n\nfunction asError(err: unknown): ThrottleAuthError {\n if (err instanceof ThrottleAuthError) return err;\n const anyErr = err as any;\n return new ThrottleAuthError({\n code: anyErr?.code ?? 'request_failed',\n message: anyErr?.message ?? String(err),\n statusCode: anyErr?.statusCode ?? 0,\n });\n}\n\n/**\n * One fetch-on-auth-change primitive behind every resource hook. `emailVerified`\n * is a dependency on purpose: verifying lifts the claim cutoff, and lists that\n * were legitimately empty a moment ago are not empty any more.\n */\nfunction useResource<T>(empty: T, load: () => Promise<T>, deps: unknown[]): Resource<T> {\n const { status, emailVerified } = useAuth();\n const [data, setData] = useState<T>(empty);\n const [isLoading, setIsLoading] = useState(status === 'loading');\n const [error, setError] = useState<ThrottleAuthError | null>(null);\n const gen = useRef(0);\n const alive = useRef(true);\n\n useEffect(() => {\n // Set on mount, not only cleared on unmount: React StrictMode mounts,\n // unmounts and remounts in development, and a ref that is only ever set\n // to false would silently discard every response for the rest of the\n // component's life.\n alive.current = true;\n return () => { alive.current = false; gen.current++; };\n }, []);\n\n const refetch = useCallback(async () => {\n const mine = ++gen.current;\n setIsLoading(true);\n try {\n const next = await load();\n if (!alive.current || gen.current !== mine) return;\n setData(next);\n setError(null);\n } catch (err) {\n if (!alive.current || gen.current !== mine) return;\n setError(asError(err));\n } finally {\n if (alive.current && gen.current === mine) setIsLoading(false);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, deps);\n\n useEffect(() => {\n if (status !== 'authenticated') {\n gen.current++; // abandon anything in flight\n setData(empty);\n setError(null);\n setIsLoading(status === 'loading');\n return;\n }\n void refetch();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status, emailVerified, refetch]);\n\n return { data, isLoading, error, refetch };\n}\n\nexport function useCustomer(): Resource<Customer | null> & { update(patch: ProfilePatch): Promise<void> } {\n const { account } = useAuth();\n const res = useResource<Customer | null>(null, () => account.getProfile(), [account]);\n const update = useCallback(async (patch: ProfilePatch) => {\n await account.updateProfile(patch);\n await res.refetch();\n }, [account, res]);\n return { ...res, update };\n}\n\nexport function useAddresses(): Resource<Address[]> & {\n create(input: AddressInput): Promise<void>;\n update(id: string, patch: Partial<AddressInput>): Promise<void>;\n remove(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<Address[]>([], () => account.listAddresses(), [account]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n create: (input) => after(account.createAddress(input)),\n update: (id, patch) => after(account.updateAddress(id, patch)),\n remove: (id) => after(account.deleteAddress(id)),\n };\n}\n\nexport function useSavedCards(): Resource<PaymentMethod[]> & {\n setDefault(id: string): Promise<void>;\n remove(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<PaymentMethod[]>([], () => account.listPaymentMethods(), [account]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n setDefault: (id) => after(account.setDefaultPaymentMethod(id)),\n remove: (id) => after(account.deletePaymentMethod(id)),\n };\n}\n\nexport function useOrders(page?: Page): Resource<Order[]> {\n const { account } = useAuth();\n return useResource<Order[]>([], () => account.listOrders(page), [account, page?.cursor, page?.limit]);\n}\n\nexport function useInvoices(page?: Page): Resource<Invoice[]> {\n const { account } = useAuth();\n return useResource<Invoice[]>([], () => account.listInvoices(page), [account, page?.cursor, page?.limit]);\n}\n\nexport function useSubscriptions(page?: Page): Resource<Subscription[]> & {\n cancel(id: string, opts?: { atPeriodEnd?: boolean }): Promise<void>;\n pause(id: string): Promise<void>;\n resume(id: string): Promise<void>;\n} {\n const { account } = useAuth();\n const res = useResource<Subscription[]>([], () => account.listSubscriptions(page),\n [account, page?.cursor, page?.limit]);\n const after = async (p: Promise<unknown>) => { await p; await res.refetch(); };\n return {\n ...res,\n cancel: (id, opts) => after(account.cancelSubscription(id, opts)),\n pause: (id) => after(account.pauseSubscription(id)),\n resume: (id) => after(account.resumeSubscription(id)),\n };\n}\n\n/**\n * Single-resource reads for the detail views. A `null` id means \"nothing\n * selected\" and must not call the API — `useResource` still runs, so the\n * loader resolves to null rather than being skipped, which keeps the\n * loading/error shape identical whether or not an id is present.\n */\nexport function useOrder(id: string | null): Resource<OrderDetail | null> {\n const { account } = useAuth();\n return useResource<OrderDetail | null>(null, async () => (id ? account.getOrder(id) : null), [account, id]);\n}\n\nexport function useInvoice(id: string | null): Resource<Invoice | null> {\n const { account } = useAuth();\n return useResource<Invoice | null>(null, async () => (id ? account.getInvoice(id) : null), [account, id]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAA4F;;;ACArF,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAY,MAAqG;AAC/G,UAAM,KAAK,OAAO;AAClB,SAAK,OAAO;AACZ,SAAK,OAAO,KAAK;AACjB,SAAK,aAAa,KAAK;AACvB,SAAK,aAAa,KAAK;AACvB,SAAK,UAAU,KAAK;AAAA,EACtB;AACF;;;ACDA,eAAsB,QAAQ,OAAmC;AAC/D,QAAM,UAAkC,EAAE,aAAa,MAAM,eAAe;AAC5E,MAAI,MAAM,SAAS,OAAW,SAAQ,cAAc,IAAI;AACxD,MAAI,MAAM,YAAa,SAAQ,gBAAgB,UAAU,MAAM,WAAW;AAC1E,MAAI,MAAM,YAAa,SAAQ,WAAW,IAAI,MAAM;AAEpD,QAAM,MAAM,MAAM,MAAM,GAAG,MAAM,OAAO,iBAAiB,MAAM,IAAI,IAAI;AAAA,IACrE,QAAQ,MAAM;AAAA,IACd;AAAA,IACA,GAAI,MAAM,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,MAAM,IAAI,EAAE,IAAI,CAAC;AAAA,EACzE,CAAC;AAED,QAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,MAAI,SAAc,CAAC;AACnB,MAAI;AACF,aAAS,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,EACtC,QAAQ;AAAA,EAGR;AAEA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,QAAQ,SAAS,CAAC;AAG5B,UAAM,KAAK,OAAO,IAAI,QAAQ,IAAI,aAAa,CAAC;AAChD,UAAM,IAAI,kBAAkB;AAAA,MAC1B,MAAM,EAAE,QAAQ;AAAA,MAChB,SAAS,EAAE,WAAW,uBAAuB,IAAI,MAAM;AAAA,MACvD,YAAY,IAAI;AAAA,MAChB,YAAY,OAAO,SAAS,EAAE,KAAK,MAAM,IAAI,KAAK;AAAA,MAClD,SAAS,EAAE;AAAA,IACb,CAAC;AAAA,EACH;AAKA,SAAO,UAAU,OAAO,WAAW,YAAY,UAAU,SAAS,OAAO,OAAQ,UAAU,CAAC;AAC9F;;;AClCO,IAAM,eAAN,MAAmB;AAAA,EACxB,YAA0B,EAAE,QAAQ,WAAW,UAAU,MAAM,eAAe,MAAM;AAAA,EACpF,eAA8B;AAAA,EAC9B,aAA4B;AAAA,EAC5B,aAAa,oBAAI,IAAc;AAAA,EAE/B,MAAoB;AAAE,WAAO,KAAK;AAAA,EAAW;AAAA,EAC7C,cAA6B;AAAE,WAAO,KAAK;AAAA,EAAc;AAAA,EACzD,YAA2B;AAAE,WAAO,KAAK;AAAA,EAAY;AAAA,EAErD,WAAW,SAAwB;AACjC,SAAK,eAAe,QAAQ;AAC5B,SAAK,aAAa,KAAK,MAAM,QAAQ,SAAS;AAC9C,SAAK,SAAS;AAAA,MACZ,QAAQ;AAAA,MACR,UAAU,QAAQ;AAAA,MAClB,eAAe,QAAQ,QAAQ,UAAU,aAAa;AAAA,IACxD,CAAC;AAAA,EACH;AAAA,EAEA,UAAU,QAA0B;AAClC,SAAK,SAAS,EAAE,GAAG,KAAK,WAAW,OAAO,CAAC;AAAA,EAC7C;AAAA,EAEA,QAAc;AACZ,SAAK,eAAe;AACpB,SAAK,aAAa;AAClB,SAAK,SAAS,EAAE,QAAQ,mBAAmB,UAAU,MAAM,eAAe,MAAM,CAAC;AAAA,EACnF;AAAA,EAEA,UAAU,IAA0B;AAClC,SAAK,WAAW,IAAI,EAAE;AACtB,WAAO,MAAM;AAAE,WAAK,WAAW,OAAO,EAAE;AAAA,IAAG;AAAA,EAC7C;AAAA,EAEA,SAAS,MAA0B;AAIjC,UAAM,OACJ,KAAK,WAAW,KAAK,UAAU,UAC/B,KAAK,kBAAkB,KAAK,UAAU,iBACtC,eAAe,KAAK,UAAU,KAAK,UAAU,QAAQ;AACvD,QAAI,KAAM;AACV,SAAK,YAAY;AACjB,eAAW,MAAM,CAAC,GAAG,KAAK,UAAU,GAAG;AAErC,UAAI;AAAE,WAAG,IAAI;AAAA,MAAG,QAAQ;AAAA,MAA0B;AAAA,IACpD;AAAA,EACF;AACF;AAEA,SAAS,eAAe,GAAoB,GAA6B;AACvE,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,CAAC,KAAK,CAAC,EAAG,QAAO;AACrB,QAAM,OAAO,oBAAI,IAAI,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC;AAC3D,aAAW,KAAK,KAAM,KAAI,EAAE,CAAC,MAAM,EAAE,CAAC,EAAG,QAAO;AAChD,SAAO;AACT;;;ACxEO,SAAS,gBAA6B;AAC3C,QAAM,MAAM,oBAAI,IAAoB;AACpC,SAAO;AAAA,IACL,KAAK,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK;AAAA,IAC1B,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC;AAAA,IAChC,QAAQ,CAAC,MAAM,KAAK,IAAI,OAAO,CAAC;AAAA,EAClC;AACF;AAOO,SAAS,sBAAmC;AACjD,QAAM,WAAW,cAAc;AAC/B,QAAM,KAAK,MAAsB;AAC/B,QAAI;AACF,aAAO,OAAO,iBAAiB,cAAc,eAAe;AAAA,IAC9D,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AAAA,IACL,IAAI,GAAG;AACL,UAAI;AACF,cAAM,IAAI,GAAG,GAAG,QAAQ,CAAC;AACzB,YAAI,KAAK,KAAM,QAAO;AAAA,MACxB,QAAQ;AAAA,MAAqB;AAC7B,aAAO,SAAS,IAAI,CAAC;AAAA,IACvB;AAAA,IACA,IAAI,GAAG,GAAG;AACR,eAAS,IAAI,GAAG,CAAC;AACjB,UAAI;AAAE,WAAG,GAAG,QAAQ,GAAG,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAiC;AAAA,IACtE;AAAA,IACA,OAAO,GAAG;AACR,eAAS,OAAO,CAAC;AACjB,UAAI;AAAE,WAAG,GAAG,WAAW,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAqB;AAAA,IAC1D;AAAA,EACF;AACF;;;ACtCA,IAAM,mBAAmB;AAEzB,IAAM,kBAAkB;AA8BjB,SAAS,mBAAmB,QAA0C;AAC3E,QAAM,WAAW,OAAO,WAAW,kBAAkB,QAAQ,OAAO,EAAE;AACtE,QAAM,UAAuB,OAAO,WAAW,oBAAoB;AAGnE,QAAM,aAAa,oBAAoB,OAAO,cAAc;AAC5D,QAAM,WAAW,yBAAyB,OAAO,cAAc;AAE/D,QAAM,QAAQ,IAAI,aAAa;AAC/B,MAAI,kBAAwC;AAC5C,MAAI,cAA6B;AACjC,MAAI,kBAAkB;AAEtB,QAAM,OAAO,EAAE,SAAS,gBAAgB,OAAO,eAAe;AAE9D,iBAAe,QAAQ,SAAiC;AACtD,UAAM,WAAW,OAAO;AACxB,UAAM,QAAQ,IAAI,YAAY,QAAQ,YAAY;AAAA,EACpD;AAEA,iBAAe,SAAwB;AACrC,kBAAc;AACd,UAAM,MAAM;AACZ,UAAM,QAAQ,OAAO,UAAU;AAAA,EACjC;AAQA,WAAS,SAAY,IAAkC;AACrD,UAAM,QAAS,WAAmB,WAAW;AAC7C,WAAO,OAAO,UAAU,MAAM,QAAQ,UAAU,EAAE,IAAI,GAAG;AAAA,EAC3D;AAEA,iBAAe,YAA2B;AACxC,UAAM,SAAS,YAAY;AAGzB,YAAM,eAAe,MAAM,QAAQ,IAAI,UAAU;AACjD,UAAI,CAAC,cAAc;AACjB,cAAM,OAAO;AACb,cAAM,IAAI,kBAAkB,EAAE,MAAM,mBAAmB,SAAS,cAAc,YAAY,IAAI,CAAC;AAAA,MACjG;AACA,UAAI;AACF,cAAM,OAAO,MAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,iBAAiB,MAAM,EAAE,aAAa,EAAE,CAAC;AACrG,cAAM,QAAQ,IAAe;AAAA,MAC/B,SAAS,KAAK;AAcZ,cAAM,UACJ,eAAe,sBAAsB,IAAI,eAAe,OAAO,IAAI,eAAe;AACpF,YAAI,QAAS,OAAM,OAAO;AAC1B,cAAM;AAAA,MACR;AAAA,IACF,CAAC;AAAA,EACH;AAEA,WAAS,UAAyB;AAChC,QAAI,CAAC,iBAAiB;AACpB,wBAAkB,UAAU,EAAE,QAAQ,MAAM;AAAE,0BAAkB;AAAA,MAAM,CAAC;AAAA,IACzE;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,cAAsC;AACnD,UAAM,UAAU,MAAM,UAAU;AAChC,QAAI,MAAM,YAAY,KAAK,WAAW,UAAU,KAAK,IAAI,IAAI,iBAAiB;AAC5E,aAAO,MAAM,YAAY;AAAA,IAC3B;AACA,QAAI,MAAM,QAAQ,IAAI,UAAU,GAAG;AACjC,YAAM,QAAQ;AACd,aAAO,MAAM,YAAY;AAAA,IAC3B;AACA,WAAO,MAAM,YAAY;AAAA,EAC3B;AAEA,iBAAe,eAAiC;AAC9C,QAAI,eAAe,kBAAkB,KAAK,IAAI,EAAG,QAAO;AACxD,QAAI,CAAC,OAAO,iBAAkB,QAAO;AACrC,UAAM,WAAW,MAAM,OAAO,iBAAiB;AAC/C,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,OAAO,QAAQ;AACrB,WAAO;AAAA,EACT;AAEA,iBAAe,OAAO,UAAiC;AACrD,UAAM,OAAO,MAAM,QAAQ;AAAA,MACzB,GAAG;AAAA,MAAM,QAAQ;AAAA,MAAQ,MAAM;AAAA,MAC/B,aAAa,MAAM,YAAY,KAAK;AAAA,MAAW,MAAM,EAAE,SAAS;AAAA,IAClE,CAAC;AACD,kBAAc,KAAK;AACnB,sBAAkB,KAAK,IAAI,KAAK,OAAO,KAAK,SAAS,KAAK,OAAO,MAAO;AAAA,EAC1E;AAEA,iBAAe,KAAc,QAAgB,MAAc,MAA4B;AACrF,QAAI,cAAc;AAClB,QAAI,gBAAgB;AAOpB,QAAI,aAAa;AAEjB,eAAS;AACP,YAAM,QAAQ,MAAM,YAAY;AAChC,UAAI;AACF,eAAO,MAAM,QAAQ;AAAA,UACnB,GAAG;AAAA,UAAM;AAAA,UAAQ;AAAA,UAAM;AAAA,UACvB,aAAa,SAAS;AAAA,UACtB,aAAa,aAAc,eAAe,SAAa;AAAA,QACzD,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,cAAM,IAAI;AAOV,cAAM,UAAU,EAAE,eAAe,QAC9B,EAAE,SAAS,kBAAkB,EAAE,SAAS,mBAAmB,EAAE,SAAS;AACzE,YAAI,WAAW,CAAC,aAAa;AAC3B,wBAAc;AACd,gBAAM,QAAQ;AACd;AAAA,QACF;AACA,YAAI,EAAE,SAAS,sBAAsB,CAAC,eAAe;AACnD,0BAAgB;AAIhB,cAAI,MAAM,aAAa,GAAG;AAAE,yBAAa;AAAM;AAAA,UAAU;AAAA,QAC3D;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,SAAS,OAAO,UAAU;AAC9B,YAAM,OAAO,MAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,kBAAkB,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC;AACzG,UAAI,MAAM,WAAW,UAAW,QAAO,EAAE,QAAQ,UAAU;AAC3D,YAAM,QAAQ,IAAe;AAC7B,aAAO,EAAE,QAAQ,SAAS;AAAA,IAC5B;AAAA,IAEA,MAAM,OAAO,OAAO,UAAU;AAC5B,YAAM,OAAO,MAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,eAAe,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC;AACtG,YAAM,QAAQ,IAAe;AAAA,IAC/B;AAAA,IAEA,MAAM,UAAU;AACd,UAAI;AAAE,cAAM,KAAK,QAAQ,cAAc;AAAA,MAAG,QAAQ;AAAA,MAA+B;AACjF,YAAM,OAAO;AAAA,IACf;AAAA,IAEA,MAAM,oBAAoB;AACxB,UAAI;AAAE,cAAM,KAAK,QAAQ,kBAAkB;AAAA,MAAG,QAAQ;AAAA,MAA+B;AACrF,YAAM,OAAO;AAAA,IACf;AAAA,IAEA,MAAM,UAAU;AACd,UAAI,CAAE,MAAM,QAAQ,IAAI,UAAU,GAAI;AAAE,cAAM,UAAU,iBAAiB;AAAG;AAAA,MAAQ;AACpF,UAAI;AAAE,cAAM,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAA6B;AAAA,IAC9D;AAAA,IAEA,aAAa,MAAM,MAAM,IAAI;AAAA,IAC7B,WAAW,CAAC,OAAO,MAAM,UAAU,EAAE;AAAA,IACrC;AAAA,IACA;AAAA,IAEA,MAAM,YAAY,OAAO;AACvB,YAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,sBAAsB,MAAM,EAAE,MAAM,EAAE,CAAC;AAKtF,UAAI,MAAM,QAAQ,IAAI,UAAU,GAAG;AACjC,YAAI;AAAE,gBAAM,QAAQ;AAAA,QAAG,QAAQ;AAAA,QAAqC;AAAA,MACtE;AAAA,IACF;AAAA,IAEA,MAAM,qBAAqB;AACzB,YAAM,KAAK,QAAQ,2BAA2B;AAAA,IAChD;AAAA,IAEA,MAAM,eAAe,OAAO;AAG1B,YAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,yBAAyB,MAAM,EAAE,MAAM,EAAE,CAAC;AAAA,IAC3F;AAAA,IAEA,MAAM,cAAc,OAAO,UAAU;AACnC,YAAM,QAAQ,EAAE,GAAG,MAAM,QAAQ,QAAQ,MAAM,wBAAwB,MAAM,EAAE,OAAO,SAAS,EAAE,CAAC;AAElG,YAAM,OAAO;AAAA,IACf;AAAA,IAEA,MAAM,eAAe,UAAU;AAC7B,YAAM,KAAK,QAAQ,uBAAuB,EAAE,SAAS,CAAC;AAMtD,UAAI;AAAE,cAAM,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAA8C;AAAA,IAC/E;AAAA,IAEA,MAAM,eAAe;AACnB,aAAO,KAAsB,OAAO,cAAc;AAAA,IACpD;AAAA,IAEA,MAAM,cAAc,IAAI;AACtB,YAAM,OAAO,MAAM,KAAsB,OAAO,cAAc;AAC9D,YAAM,UAAU,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO;AAC1C,YAAM,KAAK,UAAU,gBAAgB,mBAAmB,EAAE,CAAC,EAAE;AAC7D,UAAI,SAAS,OAAO,GAAI,OAAM,OAAO;AAAA,IACvC;AAAA,EACF;AACF;;;ACxNA,IAAM,MAAM,CAAC,MAAc,mBAAmB,CAAC;AAE/C,SAAS,MAAM,MAAqB;AAClC,QAAM,SAAS,IAAI,gBAAgB;AACnC,MAAI,MAAM,OAAQ,QAAO,IAAI,UAAU,KAAK,MAAM;AAClD,MAAI,MAAM,SAAS,KAAM,QAAO,IAAI,SAAS,OAAO,KAAK,KAAK,CAAC;AAC/D,QAAM,IAAI,OAAO,SAAS;AAC1B,SAAO,IAAI,IAAI,CAAC,KAAK;AACvB;AAEO,SAAS,cAAc,MAAqC;AACjE,QAAM,EAAE,KAAK,IAAI;AACjB,SAAO;AAAA,IACL,YAAY,MAAM,KAAK,OAAO,KAAK;AAAA,IACnC,eAAe,CAAC,UAAU,KAAK,SAAS,OAAO,KAAK;AAAA,IAEpD,eAAe,MAAM,KAAK,OAAO,eAAe;AAAA,IAChD,eAAe,CAAC,UAAU,KAAK,QAAQ,iBAAiB,KAAK;AAAA,IAC7D,eAAe,CAAC,IAAI,UAAU,KAAK,SAAS,iBAAiB,IAAI,EAAE,CAAC,IAAI,KAAK;AAAA,IAC7E,eAAe,CAAC,OAAO,KAAK,UAAU,iBAAiB,IAAI,EAAE,CAAC,EAAE;AAAA,IAEhE,oBAAoB,MAAM,KAAK,OAAO,qBAAqB;AAAA;AAAA;AAAA,IAG3D,yBAAyB,CAAC,OAAO,KAAK,SAAS,uBAAuB,IAAI,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,CAAC;AAAA,IACpG,qBAAqB,CAAC,OAAO,KAAK,UAAU,uBAAuB,IAAI,EAAE,CAAC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAO5E,gBAAgB,MAAM,KAAK,QAAQ,iCAAiC;AAAA,IACpE,kBAAkB,CAAC,kBACjB,KAAK,QAAQ,+BAA+B,EAAE,cAAc,CAAC;AAAA,IAE/D,YAAY,CAAC,SAAS,KAAK,OAAO,aAAa,MAAM,IAAI,CAAC,EAAE;AAAA,IAC5D,UAAU,CAAC,OAAO,KAAK,OAAO,cAAc,IAAI,EAAE,CAAC,EAAE;AAAA,IACrD,cAAc,CAAC,SAAS,KAAK,OAAO,eAAe,MAAM,IAAI,CAAC,EAAE;AAAA,IAChE,YAAY,CAAC,OAAO,KAAK,OAAO,gBAAgB,IAAI,EAAE,CAAC,EAAE;AAAA,IACzD,kBAAkB,CAAC,OAAO,KAAK,OAAO,gBAAgB,IAAI,EAAE,CAAC,MAAM;AAAA,IACnE,mBAAmB,CAAC,SAAS,KAAK,OAAO,oBAAoB,MAAM,IAAI,CAAC,EAAE;AAAA,IAC1E,iBAAiB,CAAC,OAAO,KAAK,OAAO,qBAAqB,IAAI,EAAE,CAAC,EAAE;AAAA,IAEnE,oBAAoB,CAAC,IAAI,SAAS,KAAK,QAAQ,qBAAqB,IAAI,EAAE,CAAC,WAAW,IAAI;AAAA,IAC1F,mBAAmB,CAAC,OAAO,KAAK,QAAQ,qBAAqB,IAAI,EAAE,CAAC,QAAQ;AAAA,IAC5E,oBAAoB,CAAC,OAAO,KAAK,QAAQ,qBAAqB,IAAI,EAAE,CAAC,SAAS;AAAA,IAE9E,WAAW,CAAC,WAAW,KAAK,QAAQ,aAAa,IAAI,MAAM,CAAC,QAAQ;AAAA,EACtE;AACF;;;ANlES;AAzBT,IAAM,UAAM,4BAAmC,IAAI;AAE5C,SAAS,qBAAqB,OAAkC;AACrE,QAAM,EAAE,MAAM,UAAU,gBAAgB,SAAS,SAAS,kBAAkB,SAAS,IAAI;AAMzF,QAAM,YAAQ,sBAAsB,MAAM;AACxC,UAAM,OAAO,YAAY,mBAAmB,EAAE,gBAAgB,SAAS,SAAS,iBAAiB,CAAC;AAClG,WAAO,EAAE,MAAM,SAAS,cAAc,IAAI,EAAE;AAAA,EAE9C,GAAG,CAAC,UAAU,gBAAgB,OAAO,CAAC;AAEtC,QAAM,eAAW,qBAA4B,IAAI;AACjD,8BAAU,MAAM;AAId,QAAI,SAAS,YAAY,MAAM,KAAM;AACrC,aAAS,UAAU,MAAM;AACzB,SAAK,MAAM,KAAK,QAAQ;AAAA,EAC1B,GAAG,CAAC,KAAK,CAAC;AAEV,SAAO,4CAAC,IAAI,UAAJ,EAAa,OAAe,UAAS;AAC/C;AAEA,SAAS,SAAuB;AAC9B,QAAM,UAAM,yBAAW,GAAG;AAC1B,MAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sDAAsD;AAChF,SAAO;AACT;AAEO,SAAS,iBAA+B;AAAE,SAAO,OAAO;AAAG;AAS3D,SAAS,UAAyB;AACvC,QAAM,EAAE,MAAM,QAAQ,IAAI,OAAO;AACjC,QAAM,eAAW,mCAAqB,KAAK,WAAW,KAAK,aAAa,KAAK,WAAW;AACxF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,WAAW,SAAS,WAAW;AAAA,IAC/B,iBAAiB,SAAS,WAAW;AAAA,IACrC;AAAA,IACA;AAAA,EACF;AACF;;;AOrEA,IAAAA,gBAAyD;AAazD,SAAS,QAAQ,KAAiC;AAChD,MAAI,eAAe,kBAAmB,QAAO;AAC7C,QAAM,SAAS;AACf,SAAO,IAAI,kBAAkB;AAAA,IAC3B,MAAM,QAAQ,QAAQ;AAAA,IACtB,SAAS,QAAQ,WAAW,OAAO,GAAG;AAAA,IACtC,YAAY,QAAQ,cAAc;AAAA,EACpC,CAAC;AACH;AAOA,SAAS,YAAe,OAAU,MAAwB,MAA8B;AACtF,QAAM,EAAE,QAAQ,cAAc,IAAI,QAAQ;AAC1C,QAAM,CAAC,MAAM,OAAO,QAAI,wBAAY,KAAK;AACzC,QAAM,CAAC,WAAW,YAAY,QAAI,wBAAS,WAAW,SAAS;AAC/D,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAAmC,IAAI;AACjE,QAAM,UAAM,sBAAO,CAAC;AACpB,QAAM,YAAQ,sBAAO,IAAI;AAEzB,+BAAU,MAAM;AAKd,UAAM,UAAU;AAChB,WAAO,MAAM;AAAE,YAAM,UAAU;AAAO,UAAI;AAAA,IAAW;AAAA,EACvD,GAAG,CAAC,CAAC;AAEL,QAAM,cAAU,2BAAY,YAAY;AACtC,UAAM,OAAO,EAAE,IAAI;AACnB,iBAAa,IAAI;AACjB,QAAI;AACF,YAAM,OAAO,MAAM,KAAK;AACxB,UAAI,CAAC,MAAM,WAAW,IAAI,YAAY,KAAM;AAC5C,cAAQ,IAAI;AACZ,eAAS,IAAI;AAAA,IACf,SAAS,KAAK;AACZ,UAAI,CAAC,MAAM,WAAW,IAAI,YAAY,KAAM;AAC5C,eAAS,QAAQ,GAAG,CAAC;AAAA,IACvB,UAAE;AACA,UAAI,MAAM,WAAW,IAAI,YAAY,KAAM,cAAa,KAAK;AAAA,IAC/D;AAAA,EAEF,GAAG,IAAI;AAEP,+BAAU,MAAM;AACd,QAAI,WAAW,iBAAiB;AAC9B,UAAI;AACJ,cAAQ,KAAK;AACb,eAAS,IAAI;AACb,mBAAa,WAAW,SAAS;AACjC;AAAA,IACF;AACA,SAAK,QAAQ;AAAA,EAEf,GAAG,CAAC,QAAQ,eAAe,OAAO,CAAC;AAEnC,SAAO,EAAE,MAAM,WAAW,OAAO,QAAQ;AAC3C;AAEO,SAAS,cAA0F;AACxG,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,QAAM,MAAM,YAA6B,MAAM,MAAM,QAAQ,WAAW,GAAG,CAAC,OAAO,CAAC;AACpF,QAAM,aAAS,2BAAY,OAAO,UAAwB;AACxD,UAAM,QAAQ,cAAc,KAAK;AACjC,UAAM,IAAI,QAAQ;AAAA,EACpB,GAAG,CAAC,SAAS,GAAG,CAAC;AACjB,SAAO,EAAE,GAAG,KAAK,OAAO;AAC1B;AAEO,SAAS,eAId;AACA,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,QAAM,MAAM,YAAuB,CAAC,GAAG,MAAM,QAAQ,cAAc,GAAG,CAAC,OAAO,CAAC;AAC/E,QAAM,QAAQ,OAAO,MAAwB;AAAE,UAAM;AAAG,UAAM,IAAI,QAAQ;AAAA,EAAG;AAC7E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,CAAC,UAAU,MAAM,QAAQ,cAAc,KAAK,CAAC;AAAA,IACrD,QAAQ,CAAC,IAAI,UAAU,MAAM,QAAQ,cAAc,IAAI,KAAK,CAAC;AAAA,IAC7D,QAAQ,CAAC,OAAO,MAAM,QAAQ,cAAc,EAAE,CAAC;AAAA,EACjD;AACF;AAEO,SAAS,gBAGd;AACA,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,QAAM,MAAM,YAA6B,CAAC,GAAG,MAAM,QAAQ,mBAAmB,GAAG,CAAC,OAAO,CAAC;AAC1F,QAAM,QAAQ,OAAO,MAAwB;AAAE,UAAM;AAAG,UAAM,IAAI,QAAQ;AAAA,EAAG;AAC7E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,YAAY,CAAC,OAAO,MAAM,QAAQ,wBAAwB,EAAE,CAAC;AAAA,IAC7D,QAAQ,CAAC,OAAO,MAAM,QAAQ,oBAAoB,EAAE,CAAC;AAAA,EACvD;AACF;AAEO,SAAS,UAAU,MAAgC;AACxD,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,SAAO,YAAqB,CAAC,GAAG,MAAM,QAAQ,WAAW,IAAI,GAAG,CAAC,SAAS,MAAM,QAAQ,MAAM,KAAK,CAAC;AACtG;AAEO,SAAS,YAAY,MAAkC;AAC5D,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,SAAO,YAAuB,CAAC,GAAG,MAAM,QAAQ,aAAa,IAAI,GAAG,CAAC,SAAS,MAAM,QAAQ,MAAM,KAAK,CAAC;AAC1G;AAEO,SAAS,iBAAiB,MAI/B;AACA,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,QAAM,MAAM;AAAA,IAA4B,CAAC;AAAA,IAAG,MAAM,QAAQ,kBAAkB,IAAI;AAAA,IAC9E,CAAC,SAAS,MAAM,QAAQ,MAAM,KAAK;AAAA,EAAC;AACtC,QAAM,QAAQ,OAAO,MAAwB;AAAE,UAAM;AAAG,UAAM,IAAI,QAAQ;AAAA,EAAG;AAC7E,SAAO;AAAA,IACL,GAAG;AAAA,IACH,QAAQ,CAAC,IAAI,SAAS,MAAM,QAAQ,mBAAmB,IAAI,IAAI,CAAC;AAAA,IAChE,OAAO,CAAC,OAAO,MAAM,QAAQ,kBAAkB,EAAE,CAAC;AAAA,IAClD,QAAQ,CAAC,OAAO,MAAM,QAAQ,mBAAmB,EAAE,CAAC;AAAA,EACtD;AACF;AAQO,SAAS,SAAS,IAAiD;AACxE,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,SAAO,YAAgC,MAAM,YAAa,KAAK,QAAQ,SAAS,EAAE,IAAI,MAAO,CAAC,SAAS,EAAE,CAAC;AAC5G;AAEO,SAAS,WAAW,IAA6C;AACtE,QAAM,EAAE,QAAQ,IAAI,QAAQ;AAC5B,SAAO,YAA4B,MAAM,YAAa,KAAK,QAAQ,WAAW,EAAE,IAAI,MAAO,CAAC,SAAS,EAAE,CAAC;AAC1G;","names":["import_react"]}
|
package/dist/react/index.d.cts
CHANGED
|
@@ -216,6 +216,24 @@ interface Page {
|
|
|
216
216
|
cursor?: string;
|
|
217
217
|
limit?: number;
|
|
218
218
|
}
|
|
219
|
+
/** What the payment widget needs to render a card form. Passed through from
|
|
220
|
+
* the processor — `buyerExternalIdentifier` in particular must reach the
|
|
221
|
+
* widget unchanged, because the session token pins it and the processor
|
|
222
|
+
* refuses a mismatch by rendering nothing at all. */
|
|
223
|
+
interface CardSetupSession {
|
|
224
|
+
embedToken: string;
|
|
225
|
+
checkoutSessionId: string;
|
|
226
|
+
amount: number;
|
|
227
|
+
currency: string;
|
|
228
|
+
merchantAccountId?: string;
|
|
229
|
+
environment?: string;
|
|
230
|
+
buyerExternalIdentifier: string;
|
|
231
|
+
country: string;
|
|
232
|
+
/** A ready-made page that hosts the card form, if you would rather redirect
|
|
233
|
+
* or iframe than embed the widget yourself. */
|
|
234
|
+
hostedUrl: string;
|
|
235
|
+
embedUrl: string;
|
|
236
|
+
}
|
|
219
237
|
interface ThrottleAccount {
|
|
220
238
|
getProfile(): Promise<Customer>;
|
|
221
239
|
updateProfile(patch: ProfilePatch): Promise<Customer>;
|
|
@@ -226,6 +244,12 @@ interface ThrottleAccount {
|
|
|
226
244
|
listPaymentMethods(): Promise<PaymentMethod[]>;
|
|
227
245
|
setDefaultPaymentMethod(id: string): Promise<PaymentMethod>;
|
|
228
246
|
deletePaymentMethod(id: string): Promise<void>;
|
|
247
|
+
/** Opens a processor-hosted session the buyer types their card into. The
|
|
248
|
+
* returned values are handed straight to the payment widget. */
|
|
249
|
+
startCardSetup(): Promise<CardSetupSession>;
|
|
250
|
+
/** Records the card the widget just vaulted, identified by the transaction
|
|
251
|
+
* the widget returns. Safe to call twice with the same id. */
|
|
252
|
+
confirmCardSetup(transactionId: string): Promise<PaymentMethod>;
|
|
229
253
|
listOrders(page?: Page): Promise<Order[]>;
|
|
230
254
|
getOrder(id: string): Promise<OrderDetail>;
|
|
231
255
|
listInvoices(page?: Page): Promise<Invoice[]>;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -216,6 +216,24 @@ interface Page {
|
|
|
216
216
|
cursor?: string;
|
|
217
217
|
limit?: number;
|
|
218
218
|
}
|
|
219
|
+
/** What the payment widget needs to render a card form. Passed through from
|
|
220
|
+
* the processor — `buyerExternalIdentifier` in particular must reach the
|
|
221
|
+
* widget unchanged, because the session token pins it and the processor
|
|
222
|
+
* refuses a mismatch by rendering nothing at all. */
|
|
223
|
+
interface CardSetupSession {
|
|
224
|
+
embedToken: string;
|
|
225
|
+
checkoutSessionId: string;
|
|
226
|
+
amount: number;
|
|
227
|
+
currency: string;
|
|
228
|
+
merchantAccountId?: string;
|
|
229
|
+
environment?: string;
|
|
230
|
+
buyerExternalIdentifier: string;
|
|
231
|
+
country: string;
|
|
232
|
+
/** A ready-made page that hosts the card form, if you would rather redirect
|
|
233
|
+
* or iframe than embed the widget yourself. */
|
|
234
|
+
hostedUrl: string;
|
|
235
|
+
embedUrl: string;
|
|
236
|
+
}
|
|
219
237
|
interface ThrottleAccount {
|
|
220
238
|
getProfile(): Promise<Customer>;
|
|
221
239
|
updateProfile(patch: ProfilePatch): Promise<Customer>;
|
|
@@ -226,6 +244,12 @@ interface ThrottleAccount {
|
|
|
226
244
|
listPaymentMethods(): Promise<PaymentMethod[]>;
|
|
227
245
|
setDefaultPaymentMethod(id: string): Promise<PaymentMethod>;
|
|
228
246
|
deletePaymentMethod(id: string): Promise<void>;
|
|
247
|
+
/** Opens a processor-hosted session the buyer types their card into. The
|
|
248
|
+
* returned values are handed straight to the payment widget. */
|
|
249
|
+
startCardSetup(): Promise<CardSetupSession>;
|
|
250
|
+
/** Records the card the widget just vaulted, identified by the transaction
|
|
251
|
+
* the widget returns. Safe to call twice with the same id. */
|
|
252
|
+
confirmCardSetup(transactionId: string): Promise<PaymentMethod>;
|
|
229
253
|
listOrders(page?: Page): Promise<Order[]>;
|
|
230
254
|
getOrder(id: string): Promise<OrderDetail>;
|
|
231
255
|
listInvoices(page?: Page): Promise<Invoice[]>;
|
package/dist/react/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
useOrders,
|
|
9
9
|
useSavedCards,
|
|
10
10
|
useSubscriptions
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-2DREGJUF.js";
|
|
12
12
|
import {
|
|
13
13
|
ThrottleAuthError,
|
|
14
14
|
ThrottleAuthProvider,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
memoryStorage,
|
|
19
19
|
useAuth,
|
|
20
20
|
useAuthContext
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-NCSTIPJF.js";
|
|
22
22
|
export {
|
|
23
23
|
ThrottleAuthError,
|
|
24
24
|
ThrottleAuthProvider,
|
package/dist/styles.css
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
edges, table rules, dividers) keep --throttle-color-border: they do not
|
|
15
15
|
identify a control, so the requirement does not apply and darkening
|
|
16
16
|
them would only add noise. */
|
|
17
|
+
/* The account dashboard paints nothing by default so it can drop into a
|
|
18
|
+
merchant's page without fighting their background. The accountSurface and
|
|
19
|
+
accountPadding appearance variables opt back into a card.
|
|
20
|
+
No backticks in this comment: the sheet is a TS template literal. */
|
|
21
|
+
--throttle-card-width: 400px;
|
|
22
|
+
--throttle-color-account-surface: transparent;
|
|
23
|
+
--throttle-account-padding: 0;
|
|
17
24
|
--throttle-color-border-control: #8b93a1;
|
|
18
25
|
--throttle-color-danger: #c2321f;
|
|
19
26
|
--throttle-color-danger-surface: #fef2f2;
|
|
@@ -112,7 +119,7 @@
|
|
|
112
119
|
box-shadow: var(--throttle-shadow-card);
|
|
113
120
|
padding: var(--throttle-space-5);
|
|
114
121
|
width: 100%;
|
|
115
|
-
max-width:
|
|
122
|
+
max-width: var(--throttle-card-width);
|
|
116
123
|
margin-inline: auto;
|
|
117
124
|
}
|
|
118
125
|
/* Inside a Modal the dialog already provides the surface — background, radius
|
|
@@ -162,7 +169,13 @@
|
|
|
162
169
|
/* Always mounted, so it reserves its line whether or not there is an error:
|
|
163
170
|
an error appearing on submit must not push the submit button out from
|
|
164
171
|
under the buyer's pointer. */
|
|
165
|
-
.throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs);
|
|
172
|
+
.throttle-field__error { color: var(--throttle-color-danger); font-size: var(--throttle-font-size-xs); line-height: 1rem; }
|
|
173
|
+
/* Reserved a full line under EVERY input even with nothing to say, which
|
|
174
|
+
with the stack gap put 28px of dead space beneath each field, on every
|
|
175
|
+
form and all seven account panels. The element stays mounted when empty
|
|
176
|
+
because role=alert only announces changes to a live region that was
|
|
177
|
+
already there; it just stops occupying a line. */
|
|
178
|
+
.throttle-field__error:empty { display: none; }
|
|
166
179
|
|
|
167
180
|
.throttle-field-group {
|
|
168
181
|
display: flex;
|
|
@@ -221,6 +234,11 @@
|
|
|
221
234
|
animation: throttle-spin 700ms linear infinite;
|
|
222
235
|
}
|
|
223
236
|
@keyframes throttle-spin { to { transform: rotate(360deg); } }
|
|
237
|
+
.throttle-button--loading { position: relative; }
|
|
238
|
+
.throttle-button__label { display: inline-flex; align-items: center; justify-content: center; gap: var(--throttle-space-2); }
|
|
239
|
+
.throttle-button__label--hidden { visibility: hidden; }
|
|
240
|
+
.throttle-spinner--centred { position: absolute; top: 50%; left: 50%; margin: -0.5em 0 0 -0.5em; }
|
|
241
|
+
|
|
224
242
|
@media (prefers-reduced-motion: reduce) { .throttle-spinner { animation-duration: 2.4s; } }
|
|
225
243
|
|
|
226
244
|
.throttle-skeleton {
|
|
@@ -303,7 +321,16 @@
|
|
|
303
321
|
/* Paints its own ground. A UserButton in a navbar should stay transparent, but
|
|
304
322
|
the dashboard is a large surface of themed text: in dark theme its foreground
|
|
305
323
|
is near-white, so on a light host page it was rendering white-on-white. */
|
|
306
|
-
.throttle-
|
|
324
|
+
.throttle-cardframe { width: 100%; min-height: 420px; border: 0; display: block; }
|
|
325
|
+
.throttle-centre { display: flex; align-items: center; justify-content: center; min-height: 160px; }
|
|
326
|
+
.throttle-section__title { font-size: var(--throttle-font-size-xs); text-transform: uppercase; letter-spacing: .06em; color: var(--throttle-color-muted-foreground); margin: 0 0 var(--throttle-space-2); font-weight: 600; }
|
|
327
|
+
.throttle-addresses { display: grid; grid-template-columns: 1fr 1fr; gap: var(--throttle-space-5); }
|
|
328
|
+
.throttle-addressblock__line { margin: 0 0 2px; }
|
|
329
|
+
.throttle-timeline { display: flex; flex-direction: column; gap: var(--throttle-space-2); }
|
|
330
|
+
.throttle-timeline__row { display: flex; align-items: center; gap: var(--throttle-space-2); }
|
|
331
|
+
.throttle-timeline__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--throttle-color-border-control); flex: none; }
|
|
332
|
+
.throttle-timeline__label { flex: 1; }
|
|
333
|
+
.throttle-account-root { container-type: inline-size; background: var(--throttle-color-account-surface); color: var(--throttle-color-foreground); padding: var(--throttle-account-padding); }
|
|
307
334
|
.throttle-account { display: flex; gap: var(--throttle-space-5); align-items: flex-start; }
|
|
308
335
|
.throttle-account__content { flex: 1; min-width: 0; }
|
|
309
336
|
.throttle-tabs__rail { flex: none; width: 220px; display: flex; flex-direction: column; gap: 2px; }
|
|
@@ -340,6 +367,7 @@
|
|
|
340
367
|
it shrinks the rail to its content width, so width:100% alone cannot make
|
|
341
368
|
it span. Stretch it explicitly when stacked. */
|
|
342
369
|
.throttle-account { flex-direction: column; align-items: stretch; gap: var(--throttle-space-4); }
|
|
370
|
+
.throttle-addresses { grid-template-columns: 1fr; }
|
|
343
371
|
.throttle-tabs__rail { width: 100%; }
|
|
344
372
|
}
|
|
345
373
|
|
package/package.json
CHANGED