@zoreal/oauth2-react 0.1.11 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -33
- package/dist/index.cjs +783 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -18
- package/dist/index.d.ts +72 -18
- package/dist/index.js +775 -106
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.tsx","../src/wire.ts","../src/ZorealLogin.tsx","../src/useZorealLogin.ts","../src/jwt.ts","../src/pairing.ts","../src/pkce.ts","../src/useZorealAutoLogin.ts","../src/logout.ts","../src/scopes.ts"],"sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport { DEFAULT_ISSUER } from './wire';\n\nexport interface ZorealOAuthProviderProps {\n /** From the ZOREAL dashboard: the asset ID. */\n clientId: string;\n /** Override the provider origin. Sandbox and self-hosted testing only. */\n issuer?: string;\n /** BCP 47. Drives button text and the pairing page. */\n locale?: string;\n children: ReactNode;\n}\n\nexport interface ZorealOAuthContextProps {\n clientId: string;\n issuer: string;\n locale?: string;\n}\n\nconst ZorealOAuthContext = createContext<ZorealOAuthContextProps | null>(null);\n\nexport function ZorealOAuthProvider({\n clientId,\n issuer = DEFAULT_ISSUER,\n locale,\n children,\n}: ZorealOAuthProviderProps) {\n const value = useMemo(\n () => ({ clientId, issuer: issuer.replace(/\\/$/, ''), locale }),\n [clientId, issuer, locale]\n );\n return <ZorealOAuthContext.Provider value={value}>{children}</ZorealOAuthContext.Provider>;\n}\n\nexport function useZorealOAuth(): ZorealOAuthContextProps {\n const ctx = useContext(ZorealOAuthContext);\n if (!ctx) {\n throw new Error(\n 'useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. ' +\n 'Wrap your app (or the part that logs in) in the provider.'\n );\n }\n return ctx;\n}\n","/**\n * The wire protocol between this package and the ZOREAL OpenID Provider.\n *\n * VERSIONED: a shipped version keeps working until the provider explicitly\n * refuses it, and when it does, the reason is surfaced verbatim. Both the wire\n * version and the package version travel on every pairing request so a refusal\n * can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (the dashboard):\n *\n * POST /pair start a pairing request. Body carries the\n * authorize parameters plus PKCE challenge.\n * Returns { request_id, pair_url, expires_in }\n * or, for prompt=none with a live consented\n * session, { code } immediately.\n * GET /pair/:id/status poll: pending | claimed |\n * approved (with code) | denied | expired |\n * enrolling. Over-polling cancels the request\n * rather than throttling it, so the cadence\n * below is not a suggestion.\n * GET /pair/:id/qr.svg the QR image for the pairing URL, served by\n * the provider so the pairing surface stays\n * changeable at runtime and\n * this package keeps zero dependencies.\n * POST /token the code exchange. Browser-direct mode uses\n * it directly with PKCE and no client secret;\n * auth-code mode leaves it to the RP backend.\n */\n\nexport const WIRE_VERSION = 1;\nexport const SDK_VERSION = '0.1.11';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** Pending TTL is short. Poll gently; over-polling cancels the request. */\nexport const POLL_INTERVAL_MS = 2000;\n/** Enrolling extends the window well beyond a normal login; poll slower. */\nexport const POLL_INTERVAL_ENROLLING_MS = 5000;\n\nexport interface PairCreated {\n request_id: string;\n /** https://zoreal.com/qr/<request_id>. The same URL in QR and app link. */\n pair_url: string;\n expires_in: number;\n}\n\nexport interface PairImmediate {\n /** prompt=none resolved silently: consented sector, live session. */\n code: string;\n}\n\nexport type PairStartResponse = PairCreated | PairImmediate;\n\nexport interface PairStatusResponse {\n status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';\n code?: string;\n expires_in?: number;\n enrolment_deadline?: number;\n /** The provider's reason on denial or refusal. Surfaced verbatim, never rewritten. */\n error?: string;\n error_description?: string;\n}\n\nexport interface TokenResponse {\n id_token: string;\n access_token?: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n","import { useMemo, type CSSProperties } from 'react';\nimport { useZorealFlow, type ActivePairing } from './useZorealLogin';\nimport type { NonOAuthError, ZorealLoginProps } from './types';\n\n/**\n * The drop-in button. It receives no access token, so it returns the\n * pseudonymous identity only; personal data needs the auth-code flow.\n *\n * The copy is neutral: the button asserts nothing about a person who has not\n * yet authenticated. Styling is inline and self-contained; no stylesheet, no\n * font, no external asset, because this renders on a sign-in page.\n */\n\nconst TEXTS: Record<NonNullable<ZorealLoginProps['text']>, string> = {\n continue_with: 'Continue with ZOREAL',\n signin_with: 'Sign in with ZOREAL',\n signup_with: 'Sign up with ZOREAL',\n signin: 'Sign in',\n};\n\nconst SIZES = {\n large: { height: 44, font: 15, pad: 20 },\n medium: { height: 38, font: 14, pad: 16 },\n small: { height: 32, font: 12, pad: 12 },\n} as const;\n\n/** The ZOREAL mark, in currentColor so every theme carries it correctly. */\nconst Mark = ({ size }: { size: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"8.4 7.4 62.2 62.2\"\n fill=\"currentColor\"\n fillRule=\"evenodd\"\n aria-hidden\n focusable=\"false\"\n >\n <path d=\"M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z\" />\n <path d=\"M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z\" />\n <path d=\"M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z\" />\n <path d=\"M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z\" />\n <path d=\"M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z\" />\n <path d=\"M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z\" />\n <path d=\"M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z\" />\n </svg>\n);\n\nconst PairingPanel = ({ pairing }: { pairing: ActivePairing }) => {\n const { status } = pairing.state;\n const line =\n status === 'claimed'\n ? 'Approve the login in your ZOREAL ID app.'\n : status === 'enrolling'\n ? 'Finishing enrolment. This screen will continue by itself.'\n : pairing.appLink\n ? 'Continue in the ZOREAL ID app, then return to this tab.'\n : 'Scan with your phone camera or the ZOREAL ID app.';\n\n return (\n <div\n role=\"dialog\"\n aria-label=\"Log in with ZOREAL\"\n style={{\n marginTop: 8,\n padding: 16,\n width: 232,\n borderRadius: 12,\n border: '1px solid rgba(128,128,128,0.35)',\n background: 'Canvas',\n color: 'CanvasText',\n textAlign: 'center',\n fontFamily: 'inherit',\n }}\n >\n {!pairing.appLink && (\n <img\n src={pairing.qrUrl}\n alt={`QR code for ${pairing.pairUrl}`}\n width={200}\n height={200}\n style={{ display: 'block', margin: '0 auto', borderRadius: 8 }}\n />\n )}\n <p style={{ margin: '10px 0 0', fontSize: 12, lineHeight: 1.5 }}>{line}</p>\n <button\n type=\"button\"\n onClick={pairing.cancel}\n style={{\n marginTop: 10,\n border: 'none',\n background: 'none',\n color: 'inherit',\n opacity: 0.6,\n fontSize: 12,\n cursor: 'pointer',\n textDecoration: 'underline',\n }}\n >\n Cancel\n </button>\n </div>\n );\n};\n\nexport function ZorealLogin(props: ZorealLoginProps) {\n const {\n onSuccess,\n onError,\n containerProps,\n type = 'standard',\n theme = 'filled',\n size = 'large',\n text = 'continue_with',\n shape = 'rectangular',\n logo_alignment = 'left',\n width,\n click_listener,\n ...request\n } = props;\n\n const { login, internals } = useZorealFlow({\n ...request,\n flow: 'browser-direct',\n onCredential: onSuccess,\n onError: (e) => onError?.({ type: 'unknown', description: e.description ?? e.error }),\n onNonOAuthError: (e: NonOAuthError) => onError?.(e),\n });\n\n const s = SIZES[size];\n const style: CSSProperties = useMemo(\n () => ({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: logo_alignment === 'center' ? 'center' : 'flex-start',\n gap: 10,\n height: s.height,\n padding: `0 ${s.pad}px`,\n width,\n fontSize: s.font,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: shape === 'pill' ? s.height / 2 : shape === 'square' ? 4 : 8,\n ...(theme === 'outline'\n ? { background: 'transparent', color: 'inherit', border: '1px solid rgba(128,128,128,0.5)' }\n : theme === 'filled_black'\n ? { background: '#111', color: '#fff', border: '1px solid #111' }\n : { background: '#00b4d9', color: '#fff', border: '1px solid #00b4d9' }),\n }),\n [logo_alignment, s, shape, theme, width]\n );\n\n return (\n <div {...containerProps}>\n <button\n type=\"button\"\n style={style}\n onClick={() => {\n click_listener?.();\n login();\n }}\n >\n <Mark size={Math.round(s.font * 1.25)} />\n {type === 'standard' && TEXTS[text]}\n </button>\n {internals.pairing && !internals.pairing.appLink && (\n <PairingPanel pairing={internals.pairing} />\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useZorealOAuth } from './context';\nimport { unsafeClaims } from './jwt';\nimport {\n FlowAbandonedError,\n OAuthFlowError,\n exchangeCode,\n isMobileUserAgent,\n pollUntilApproved,\n startPairing,\n} from './pairing';\nimport { challengeS256, generateState, generateVerifier } from './pkce';\nimport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginRequestOptions,\n} from './types';\n\nexport interface ActivePairing {\n requestId: string;\n pairUrl: string;\n qrUrl: string;\n state: PairingState;\n /** True when display resolved to the app link rather than the QR. */\n appLink: boolean;\n cancel: () => void;\n}\n\ninterface FlowInternals {\n /** Non-null while a pairing is on screen. ZorealLogin renders from this. */\n pairing: ActivePairing | null;\n}\n\n/**\n * The internal option shape: one flow discriminator, one success callback per\n * mode. The public API keeps Google's single overloaded onSuccess; this type\n * exists because an intersection of those two signatures is uninhabitable, and\n * the mapping from public to internal happens once, in useZorealLogin.\n */\nexport interface InternalFlowOptions extends ZorealLoginRequestOptions {\n flow: 'browser-direct' | 'auth-code';\n redirect_uri?: string;\n onCredential?: (response: ZorealCredentialResponse) => void;\n onCode?: (response: ZorealCodeResponse) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n}\n\n/**\n * The one flow, shared by the hook and the button. Starts a pairing, exposes\n * it for rendering, polls, and finishes per mode: browser-direct exchanges the\n * code here (public client, PKCE, no secret) and hands over an ID token;\n * auth-code hands the code and the PKCE verifier to the caller, whose backend\n * does the exchange with its client authentication.\n */\nexport function useZorealFlow(options: InternalFlowOptions): {\n login: () => void;\n internals: FlowInternals;\n} {\n const { clientId, issuer, locale } = useZorealOAuth();\n const [pairing, setPairing] = useState<ActivePairing | null>(null);\n const abortRef = useRef<AbortController | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // A component unmounting mid-login must stop the poll: the provider cancels\n // over-polled requests, and an orphaned interval is exactly how one happens.\n useEffect(() => () => abortRef.current?.abort(), []);\n\n const login = useCallback(() => {\n const opts = optionsRef.current;\n const run = async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const flow = opts.flow;\n const verifier = generateVerifier();\n const state = generateState();\n const nonce = generateState();\n\n try {\n const started = await startPairing(issuer, {\n client_id: clientId,\n scope: opts.scope ?? 'openid',\n state,\n nonce,\n code_challenge: await challengeS256(verifier),\n redirect_uri: flow === 'auth-code' ? opts.redirect_uri : undefined,\n acr_values: Array.isArray(opts.acr_values)\n ? opts.acr_values.join(' ')\n : opts.acr_values,\n max_age: opts.max_age,\n prompt: opts.prompt,\n locale,\n });\n\n let code: string;\n let selectBy: SelectBy = 'device';\n\n if ('code' in started) {\n // prompt=none resolved silently: consented sector, live session.\n code = started.code;\n selectBy = 'session';\n } else {\n const useAppLink =\n opts.display === 'link' || (opts.display !== 'qr' && isMobileUserAgent());\n selectBy = useAppLink ? 'app_link' : 'qr';\n\n const cancel = () => {\n controller.abort();\n setPairing(null);\n };\n // Everything a caller-rendered pairing UI needs, on every state it\n // sees: the QR flow cannot complete unless SOMETHING renders\n // pairUrl, and for the auth-code flow that something is the caller.\n const surface = {\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n appLink: useAppLink,\n cancel,\n };\n const active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: surface.pairUrl,\n qrUrl: surface.qrUrl,\n state: { status: 'pending', expiresIn: started.expires_in, ...surface },\n appLink: useAppLink,\n cancel,\n };\n setPairing(active);\n // The initial state, immediately: the first poll response is one\n // round-trip away, and a UI that waits for it opens visibly empty.\n opts.onPairingStateChange?.(active.state);\n\n if (useAppLink) {\n // The universal link, in the same tab: the app claims it, and with\n // no app installed the same URL is the real pairing page which can\n // enrol (02 sections 3 and 4). A popup here would be blocked more\n // often than it would help.\n window.location.assign(started.pair_url);\n }\n\n code = await pollUntilApproved(\n issuer,\n started.request_id,\n (s) => {\n const enriched = { ...s, ...surface };\n setPairing((p) =>\n p && p.requestId === started.request_id ? { ...p, state: enriched } : p\n );\n opts.onPairingStateChange?.(enriched);\n },\n controller.signal\n );\n }\n\n setPairing(null);\n\n if (flow === 'auth-code') {\n opts.onCode?.({\n code,\n scope: opts.scope ?? 'openid',\n app_state: opts.app_state,\n code_verifier: verifier,\n nonce,\n });\n return;\n }\n\n const tokens = await exchangeCode(issuer, {\n code,\n code_verifier: verifier,\n client_id: clientId,\n });\n const claims = unsafeClaims(tokens.id_token);\n const response: ZorealCredentialResponse = {\n credential: tokens.id_token,\n clientId,\n select_by: selectBy,\n acr: (claims.acr as AcrValue) ?? 'zoreal.device',\n };\n opts.onCredential?.(response);\n } catch (e) {\n setPairing(null);\n if (e instanceof DOMException && e.name === 'AbortError') return;\n if (e instanceof FlowAbandonedError) {\n opts.onNonOAuthError?.(e.reason);\n return;\n }\n if (e instanceof OAuthFlowError) {\n opts.onError?.({ error: e.error, description: e.description });\n return;\n }\n opts.onNonOAuthError?.({\n type: 'unknown',\n description: e instanceof Error ? e.message : String(e),\n });\n }\n };\n void run();\n }, [clientId, issuer, locale]);\n\n return { login, internals: { pairing } };\n}\n\nexport function useZorealLogin(\n options: { flow?: 'browser-direct' } & BrowserDirectFlowOptions\n): () => void;\nexport function useZorealLogin(options: { flow: 'auth-code' } & AuthCodeFlowOptions): () => void;\nexport function useZorealLogin(\n options: ({ flow?: 'browser-direct' | 'auth-code' } & ZorealLoginRequestOptions) &\n Partial<Pick<AuthCodeFlowOptions, 'redirect_uri' | 'ux_mode'>> & {\n onSuccess?: (response: never) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n }\n): () => void {\n if (options.ux_mode === 'redirect') {\n // v1 supports the popup shape only: the code and PKCE verifier go to your\n // onSuccess and from there to your backend over TLS. A redirect would have\n // to carry the verifier in a URL, which is a credential in every access\n // log on the path. Refused loudly rather than implemented badly.\n throw new Error(\n \"@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default \" +\n \"'popup' shape and post the code and code_verifier from onSuccess to your backend.\"\n );\n }\n const flow = options.flow ?? 'browser-direct';\n return useZorealFlow({\n ...options,\n flow,\n onCredential:\n flow === 'browser-direct'\n ? (options.onSuccess as unknown as (r: ZorealCredentialResponse) => void)\n : undefined,\n onCode:\n flow === 'auth-code'\n ? (options.onSuccess as unknown as (r: ZorealCodeResponse) => void)\n : undefined,\n }).login;\n}\n","/**\n * Reads claims OUT of an ID token without verifying it.\n *\n * That is not a shortcut, it is the design: this code runs in a browser the\n * threat model assumes is attacker-controlled (02), so a signature check here\n * proves nothing to anyone. The token is verified where verification means\n * something: server-side against the JWKS. What this parser feeds is\n * convenience fields (acr on the response object) that the types document as\n * convenience, with the token staying the authority.\n */\n\nexport function unsafeClaims(idToken: string): Record<string, unknown> {\n try {\n const payload = idToken.split('.')[1] ?? '';\n const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n return JSON.parse(\n new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))\n );\n } catch {\n return {};\n }\n}\n","/**\n * The pairing channel, client side. wire.ts pins the endpoints.\n *\n * The browser polls; the phone never talks to the browser. Everything here is\n * therefore plain fetch against the issuer, CORS-gated on the client's\n * authorized origins, with the poll cadence fixed: the provider cancels an\n * over-polling request rather than throttling it, so a \"retry\n * faster on error\" strategy here would kill the login it is trying to save.\n */\n\nimport {\n POLL_INTERVAL_ENROLLING_MS,\n POLL_INTERVAL_MS,\n SDK_VERSION,\n WIRE_VERSION,\n type PairStartResponse,\n type PairStatusResponse,\n type TokenResponse,\n} from './wire';\nimport type { ErrorCode, NonOAuthError, PairingState } from './types';\n\nexport class OAuthFlowError extends Error {\n constructor(\n public error: ErrorCode,\n public description?: string\n ) {\n super(description ?? error);\n }\n}\n\nexport class FlowAbandonedError extends Error {\n constructor(public reason: NonOAuthError) {\n super(reason.description ?? reason.type);\n }\n}\n\nexport interface StartPairingParams {\n client_id: string;\n scope: string;\n state: string;\n nonce: string;\n code_challenge: string;\n redirect_uri?: string;\n acr_values?: string;\n max_age?: number;\n prompt?: string;\n locale?: string;\n}\n\nasync function parseJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function startPairing(\n issuer: string,\n params: StartPairingParams\n): Promise<PairStartResponse> {\n const response = await fetch(`${issuer}/pair`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...params,\n code_challenge_method: 'S256',\n wire_version: WIRE_VERSION,\n sdk: `@zoreal/oauth2-react/${SDK_VERSION}`,\n }),\n });\n\n const body = await parseJson(response);\n if (!response.ok) {\n // The provider's words, verbatim. A refused package version arrives here,\n // and rewriting its reason would hide the only signal telling an integrator\n // to upgrade.\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n (body.error_description as string) ?? `The provider refused the request (${response.status})`\n );\n }\n return body as unknown as PairStartResponse;\n}\n\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n // An already-aborted signal never fires its abort event, so check first\n // or the sleep runs to term and the poll takes one extra swing.\n if (signal?.aborted) {\n reject(new DOMException('aborted', 'AbortError'));\n return;\n }\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('aborted', 'AbortError'));\n });\n });\n\n/**\n * Polls until the request resolves. Returns the authorization code.\n * Throws FlowAbandonedError for the human outcomes (denied, expired,\n * enrolment abandoned) and OAuthFlowError for protocol ones.\n */\nexport async function pollUntilApproved(\n issuer: string,\n requestId: string,\n onState?: (state: PairingState) => void,\n signal?: AbortSignal\n): Promise<string> {\n for (;;) {\n const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {\n signal,\n });\n const body = (await parseJson(response)) as unknown as PairStatusResponse;\n\n if (!response.ok) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Pairing status failed (${response.status})`\n );\n }\n\n onState?.({\n status: body.status,\n expiresIn: body.expires_in,\n enrolmentDeadline: body.enrolment_deadline,\n });\n\n switch (body.status) {\n case 'approved':\n if (!body.code) {\n throw new OAuthFlowError('server_error', 'approved with no authorization code');\n }\n return body.code;\n case 'denied':\n throw new FlowAbandonedError({ type: 'request_denied', description: body.error_description });\n case 'expired':\n throw new FlowAbandonedError({ type: 'request_expired', description: body.error_description });\n case 'cancelled':\n // The provider cancels an over-polled or abandoned request outright\n // (its pairing rows have a real cancelled state). Before 0.1.4 this\n // fell through to the default branch and polled a dead request\n // forever.\n throw new FlowAbandonedError({\n type: 'request_expired',\n description: body.error_description ?? 'the provider cancelled the pairing request',\n });\n case 'enrolling':\n await sleep(POLL_INTERVAL_ENROLLING_MS, signal);\n break;\n default:\n await sleep(POLL_INTERVAL_MS, signal);\n }\n }\n}\n\n/**\n * The code exchange, browser-direct mode only: a public client, PKCE and no\n * secret. What comes back can only ever be the pseudonymous tier, by\n * construction rather than by rule: personal data lives at /userinfo behind an\n * access token this mode is never issued, because personal-data scopes are\n * refused for public clients at the pairing step.\n */\nexport async function exchangeCode(\n issuer: string,\n input: { code: string; code_verifier: string; client_id: string }\n): Promise<TokenResponse> {\n const response = await fetch(`${issuer}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code,\n code_verifier: input.code_verifier,\n client_id: input.client_id,\n }),\n });\n\n const body = (await parseJson(response)) as unknown as TokenResponse;\n if (!response.ok || body.error) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Token exchange failed (${response.status})`\n );\n }\n return body;\n}\n\n/** A mobile user agent gets the app link, not a QR of its own screen. */\nexport function isMobileUserAgent(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /android|iphone|ipad|ipod/i.test(navigator.userAgent);\n}\n","/**\n * PKCE, S256 only: mandatory for every client, confidential ones included. There is no plain fallback and there must never\n * be one; a provider seeing method=plain is seeing a bug or an attack.\n */\n\nconst VERIFIER_BYTES = 32; // 43 base64url chars, the RFC 7636 minimum length\n\nconst base64url = (bytes: Uint8Array): string =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nexport function generateVerifier(): string {\n const bytes = new Uint8Array(VERIFIER_BYTES);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\nexport async function challengeS256(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n\nexport function generateState(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n","import { useEffect, useRef } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport type { UseZorealAutoLoginOptions } from './types';\n\n/**\n * Silent re-auth with prompt=none. NOT One Tap and never could be: there is no\n * ZOREAL session cookie in the browser to read, the credential is on a phone.\n * This succeeds only for a returning user at a consented sector with a live\n * session, the resulting acr is zoreal.session with an empty amr, and a\n * relying party that needs a live human must not build on this hook.\n *\n * PRIVACY NOTE: mounting this on a page sends a request to ZOREAL on page\n * load, before the user does anything. The hook is therefore conservative: it\n * fires once per mount, never retries, and does nothing when `disabled`.\n */\nexport function useZorealAutoLogin(options: UseZorealAutoLoginOptions): void {\n const { login } = useZorealFlow({\n flow: 'browser-direct',\n scope: options.scope,\n prompt: 'none',\n onCredential: options.onSuccess,\n onError: (e) => {\n // The provider's honest answer when no silent session exists (the\n // common case): unavailable, not an error, and never surfaced.\n const quiet = ['login_required', 'consent_required', 'interaction_required'];\n if (quiet.includes(e.error)) {\n options.onUnavailable?.();\n } else {\n options.onError?.({ type: 'unknown', description: e.description ?? e.error });\n }\n },\n onNonOAuthError: (e) => options.onError?.(e),\n });\n\n const fired = useRef(false);\n useEffect(() => {\n if (options.disabled || fired.current) return;\n fired.current = true;\n login();\n }, [options.disabled, login]);\n}\n","/**\n * Clears SDK-held local state. Named for parity with googleLogout and, like\n * it, LOCAL ONLY: it does not end the holder's ZOREAL session, which lives on\n * their phone and at the provider. A relying party that believes this signs\n * the user out of ZOREAL has a security misunderstanding, not a naming\n * complaint. The relying party's own session is the relying\n * party's to end.\n *\n * The SDK deliberately persists nothing (no localStorage, no cookies), so\n * today this has nothing to clear and exists as the stable API surface for a\n * future that does.\n */\nexport function zorealLogout(): void {\n // Intentionally empty until the SDK holds state worth clearing.\n}\n","import type { ZorealCodeResponse } from './types';\n\n/** Mirrors hasGrantedAllScopesGoogle for name-for-name portability. */\nexport function hasGrantedAllScopesZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].every((s) => granted.has(s));\n}\n\nexport function hasGrantedAnyScopeZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].some((s) => granted.has(s));\n}\n"],"mappings":"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","names":["useMemo","jsx","useMemo","useEffect","useRef","useRef","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/wire.ts","../src/PairingModal.tsx","../src/mark.tsx","../src/lockup.tsx","../src/i18n.ts","../src/styles.ts","../src/ZorealLogin.tsx","../src/useZorealLogin.ts","../src/jwt.ts","../src/pairing.ts","../src/pkce.ts","../src/useZorealAutoLogin.ts","../src/logout.ts","../src/scopes.ts"],"sourcesContent":["import { createContext, useContext, useMemo, useState, type ReactNode } from 'react';\nimport { DEFAULT_ISSUER } from './wire';\nimport { PairingModal } from './PairingModal';\nimport type { PairingState, PairingUI, ZorealTheme } from './types';\n\nexport interface ZorealOAuthProviderProps {\n /** From the ZOREAL dashboard: the asset ID. */\n clientId: string;\n /** Override the provider origin. Sandbox and self-hosted testing only. */\n issuer?: string;\n /**\n * BCP 47. Drives button text, the pairing page, AND the pairing modal's own\n * copy — pass the language your app is currently showing, so the browser and\n * the phone say the same thing.\n */\n locale?: string;\n /** Colour scheme for the pairing modal. Defaults to following the OS. */\n theme?: ZorealTheme;\n /**\n * Who renders the QR. Defaults to 'modal': the SDK draws it. Set 'none' only\n * if you are rendering your own from `onPairingStateChange`.\n */\n pairingUI?: PairingUI;\n /**\n * How long the modal stays open before giving up and cancelling, in ms.\n * Defaults to 120000. The provider's own expiry wins when it is shorter.\n */\n pairingTimeoutMs?: number;\n children: ReactNode;\n}\n\nexport interface ZorealOAuthContextProps {\n clientId: string;\n issuer: string;\n locale?: string;\n}\n\n/** What the flow hands the provider so it can draw the pairing. */\nexport interface HostedPairing {\n state: PairingState;\n qrUrl: string;\n cancel: () => void;\n}\n\nconst ZorealOAuthContext = createContext<ZorealOAuthContextProps | null>(null);\n\n/**\n * Internal channel from the flow to the provider.\n *\n * The modal has to be rendered by the provider rather than by the hook, because\n * `useZorealLogin` returns a function, not an element: there is nowhere for a\n * hook to put a dialog. Publishing up to the provider is what lets an\n * integrator get the whole pairing UI without writing (or importing) anything.\n *\n * Null when `pairingUI` is 'none', which is also how the flow knows to stay out\n * of the way and let the caller render.\n */\nconst PairingHostContext = createContext<((pairing: HostedPairing | null) => void) | null>(null);\n\nexport function useZorealPairingHost() {\n return useContext(PairingHostContext);\n}\n\nexport function ZorealOAuthProvider({\n clientId,\n issuer = DEFAULT_ISSUER,\n locale,\n theme = 'auto',\n pairingUI = 'modal',\n pairingTimeoutMs,\n children,\n}: ZorealOAuthProviderProps) {\n const [pairing, setPairing] = useState<HostedPairing | null>(null);\n\n const value = useMemo(\n () => ({ clientId, issuer: issuer.replace(/\\/$/, ''), locale }),\n [clientId, issuer, locale]\n );\n\n const host = pairingUI === 'modal' ? setPairing : null;\n\n return (\n <ZorealOAuthContext.Provider value={value}>\n <PairingHostContext.Provider value={host}>\n {children}\n {pairing && (\n <PairingModal\n state={pairing.state}\n qrUrl={pairing.qrUrl}\n onCancel={pairing.cancel}\n locale={locale}\n theme={theme}\n timeoutMs={pairingTimeoutMs}\n />\n )}\n </PairingHostContext.Provider>\n </ZorealOAuthContext.Provider>\n );\n}\n\nexport function useZorealOAuth(): ZorealOAuthContextProps {\n const ctx = useContext(ZorealOAuthContext);\n if (!ctx) {\n throw new Error(\n 'useZorealOAuth must be used inside <ZorealOAuthProvider clientId=...>. ' +\n 'Wrap your app (or the part that logs in) in the provider.'\n );\n }\n return ctx;\n}\n","/**\n * The wire protocol between this package and the ZOREAL OpenID Provider.\n *\n * VERSIONED: a shipped version keeps working until the provider explicitly\n * refuses it, and when it does, the reason is surfaced verbatim. Both the wire\n * version and the package version travel on every pairing request so a refusal\n * can be precise.\n *\n * Endpoints, all relative to the issuer and all CORS-gated on the client's\n * authorized JavaScript origins (the dashboard):\n *\n * POST /pair start a pairing request. Body carries the\n * authorize parameters plus PKCE challenge.\n * Returns { request_id, pair_url, expires_in }\n * or, for prompt=none with a live consented\n * session, { code } immediately.\n * GET /pair/:id/status poll: pending | claimed |\n * approved (with code) | denied | expired |\n * enrolling. Over-polling cancels the request\n * rather than throttling it, so the cadence\n * below is not a suggestion.\n * GET /pair/:id/qr.svg the QR image for the pairing URL, served by\n * the provider so the pairing surface stays\n * changeable at runtime and\n * this package keeps zero dependencies.\n * POST /token the code exchange. Browser-direct mode uses\n * it directly with PKCE and no client secret;\n * auth-code mode leaves it to the RP backend.\n */\n\nexport const WIRE_VERSION = 1;\nexport const SDK_VERSION = '0.2.6';\nexport const DEFAULT_ISSUER = 'https://id.zoreal.com';\n\n/** Pending TTL is short. Poll gently; over-polling cancels the request. */\nexport const POLL_INTERVAL_MS = 2000;\n/** Enrolling extends the window well beyond a normal login; poll slower. */\nexport const POLL_INTERVAL_ENROLLING_MS = 5000;\n\nexport interface PairCreated {\n request_id: string;\n /** https://zoreal.com/qr/<request_id>. The same URL in QR and app link. */\n pair_url: string;\n expires_in: number;\n}\n\nexport interface PairImmediate {\n /** prompt=none resolved silently: consented sector, live session. */\n code: string;\n}\n\nexport type PairStartResponse = PairCreated | PairImmediate;\n\nexport interface PairStatusResponse {\n status: 'pending' | 'claimed' | 'approved' | 'denied' | 'expired' | 'cancelled' | 'enrolling';\n code?: string;\n expires_in?: number;\n enrolment_deadline?: number;\n /** The provider's reason on denial or refusal. Surfaced verbatim, never rewritten. */\n error?: string;\n error_description?: string;\n}\n\nexport interface TokenResponse {\n id_token: string;\n access_token?: string;\n token_type?: string;\n expires_in?: number;\n scope?: string;\n error?: string;\n error_description?: string;\n}\n","import { useEffect, useId, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ZorealLockup } from './lockup';\nimport { interpolate, isRtl, strings } from './i18n';\nimport { cx, ensureStyles } from './styles';\nimport type { PairingState, ZorealTheme } from './types';\n\n/**\n * The pairing modal, rendered by the SDK rather than by every integrator.\n *\n * It is a modal and not an inline card because the handshake is blocking,\n * time-boxed and happens on a second device: there is nothing useful to do on\n * the page until it resolves, and an inline panel below a button competes with\n * the rest of a sign-in form for the person's attention at the one moment they\n * need to look at their phone.\n */\n\n/** Our own cap on how long a pairing sits on screen. See `pairingTimeoutMs`. */\nexport const DEFAULT_PAIRING_TIMEOUT_MS = 120_000;\n\n/** Below this the countdown changes colour: it stops being background\n * information and starts being a prompt to hurry. */\nconst URGENT_SECONDS = 20;\n\nfunction mmss(totalSeconds: number): string {\n const m = Math.floor(totalSeconds / 60);\n const s = totalSeconds % 60;\n return `${m}:${String(s).padStart(2, '0')}`;\n}\n\n/* Icons are inlined rather than pulled from an icon package: this renders on\n someone else's sign-in page, and a UI dependency here would be inherited by\n every host app that installs the SDK. */\nconst IconClose = () => (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" aria-hidden focusable=\"false\">\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n);\n\nconst IconPhone = () => (\n <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.8\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden focusable=\"false\">\n <rect x=\"6\" y=\"2\" width=\"12\" height=\"20\" rx=\"2.5\" />\n <path d=\"M11 18.5h2\" />\n </svg>\n);\n\nconst IconShield = () => (\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden focusable=\"false\">\n <path d=\"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z\" />\n <path d=\"m9 12 2 2 4-4\" />\n </svg>\n);\n\nexport interface PairingModalProps {\n state: PairingState;\n qrUrl: string;\n onCancel: () => void;\n locale?: string;\n theme?: ZorealTheme;\n timeoutMs?: number;\n}\n\nexport function PairingModal({\n state,\n qrUrl,\n onCancel,\n locale,\n theme = 'auto',\n timeoutMs = DEFAULT_PAIRING_TIMEOUT_MS,\n}: PairingModalProps) {\n const t = strings(locale);\n const titleId = useId();\n const closeRef = useRef<HTMLButtonElement>(null);\n\n // A deadline, not a decremented counter. Background tabs throttle timers, so\n // a counter that subtracts one per tick drifts and comes back lying about how\n // much time is left; reading the clock each tick self-corrects.\n const deadlineRef = useRef(0);\n const [remaining, setRemaining] = useState(Math.round(timeoutMs / 1000));\n\n // `claimed` = the request is now waiting in the holder's app; `enrolling` =\n // a first-time holder finishing ZOREAL ID setup. In both the QR has done its\n // job and the action has moved to the phone.\n const settled = state.status === 'claimed' || state.status === 'enrolling';\n\n const onCancelRef = useRef(onCancel);\n onCancelRef.current = onCancel;\n\n useEffect(() => {\n ensureStyles();\n }, []);\n\n useEffect(() => {\n // Never claim more time than the provider will actually honour: if the\n // server's own window is shorter than our cap, the server wins.\n const serverMs = typeof state.expiresIn === 'number' ? state.expiresIn * 1000 : Infinity;\n deadlineRef.current = Date.now() + Math.min(timeoutMs, serverMs);\n setRemaining(Math.round(Math.min(timeoutMs, serverMs) / 1000));\n\n const id = setInterval(() => {\n const left = Math.max(0, Math.ceil((deadlineRef.current - Date.now()) / 1000));\n setRemaining(left);\n if (left === 0) {\n // Stop the tick before cancelling. Unmount clears it anyway, but only\n // after this render commits, and a timer that keeps firing `cancel`\n // once a second in between is a race waiting to be inherited.\n clearInterval(id);\n onCancelRef.current();\n }\n }, 1000);\n return () => clearInterval(id);\n // Deliberately keyed on the FIRST expiresIn only: re-running on every poll\n // would restart the countdown each tick and it would never reach zero.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [timeoutMs]);\n\n // Escape closes, page scroll locks, and focus moves into the dialog, so the\n // panel is reachable and dismissable without a mouse.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') onCancelRef.current();\n };\n document.addEventListener('keydown', onKey);\n const previous = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n closeRef.current?.focus();\n return () => {\n document.removeEventListener('keydown', onKey);\n document.body.style.overflow = previous;\n };\n }, []);\n\n if (typeof document === 'undefined') return null;\n\n const body =\n state.status === 'enrolling'\n ? t.bodyEnrolling\n : settled\n ? t.bodyApprove\n : t.bodyScan;\n\n return createPortal(\n <div className={`${cx('root')} ${cx('scrim')}`} data-theme={theme} onClick={onCancel}>\n <div\n className={cx('card')}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n dir={isRtl(locale) ? 'rtl' : 'ltr'}\n onClick={(e) => e.stopPropagation()}\n >\n <button ref={closeRef} type=\"button\" className={cx('close')} aria-label={t.close} onClick={onCancel}>\n <IconClose />\n </button>\n\n <div className={cx('body')}>\n <ZorealLockup height={44} className={cx('lockup')} />\n\n <h2 id={titleId} className={cx('title')}>\n {settled ? t.titleApprove : t.title}\n </h2>\n <p className={cx('body-text')}>{body}</p>\n\n <div className={cx('qr-well')}>\n <img className={cx('qr')} data-spent={settled} src={qrUrl} alt={t.qrAlt} width={180} height={180} />\n {settled && (\n <span className={cx('qr-overlay')}>\n <span className={cx('qr-badge')}>\n <IconPhone />\n </span>\n </span>\n )}\n </div>\n\n <div className={cx('status')}>\n <span className={cx('dot')}>\n <i />\n <i />\n </span>\n {settled ? t.waitingApproval : t.waiting}\n </div>\n <p className={cx('timer')} data-urgent={remaining <= URGENT_SECONDS}>\n {interpolate(t.expiresIn, mmss(remaining))}\n </p>\n </div>\n\n {/* The QR is on screen because this person is being asked to use a\n phone app, and some of them do not have it yet. Without this the\n panel reads as \"scan this with something I do not have\", and the\n flow dead-ends at the one moment it can still be recovered: the\n same code is also the app's download link. */}\n <div className={cx('help')}>\n <p className={cx('help-title')}>{t.noIdTitle}</p>\n <p className={cx('help-body')}>{t.noIdBody}</p>\n </div>\n\n <div className={cx('footer')}>\n <button type=\"button\" className={cx('cancel')} onClick={onCancel}>\n {t.cancel}\n </button>\n <p className={cx('secured')}>\n <IconShield />\n {t.secured}\n </p>\n </div>\n </div>\n </div>,\n document.body\n );\n}\n","/**\n * The ZOREAL mark. Geometry is the tight crop made for small sizes.\n *\n * Two colour modes, because the mark has two jobs. On the button it is\n * `currentColor`, so it inherits whatever the host theme puts on the label. In\n * the pairing modal it is the brand blue, because there it identifies WHOSE\n * request the person is being asked to approve, and an identity check is\n * exactly the wrong place for a mark that changes colour with the page.\n */\nexport const ZOREAL_BLUE = '#00b4d9';\n\nexport function ZorealMark({\n size = 18,\n brand = false,\n className,\n}: {\n size?: number;\n /** Paint the brand blue instead of inheriting currentColor. */\n brand?: boolean;\n className?: string;\n}) {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"8.4 7.4 62.2 62.2\"\n fill={brand ? ZOREAL_BLUE : 'currentColor'}\n fillRule=\"evenodd\"\n aria-hidden\n focusable=\"false\"\n className={className}\n >\n <path d=\"M56.1,32.9c.6-3.1-.8-6.4-3.7-8.1l-18-10.4,5.2-3,15.4,8.9c5.4,3.1,7.7,9.6,5.8,15.3-.3.8-.6,1.6-1.1,2.4-.4.7-.9,1.4-1.5,2.1-1.3,1.4-2.9,2.6-4.6,3.3-3.6,1.5-7.9,1.4-11.6-.7l-8.9-5.1c-2.9-1.7-6.5-1.3-8.9.8-.6.6-1.2,1.2-1.7,2-.5.8-.8,1.7-.9,2.5-.6,3.1.9,6.4,3.7,8.1l18,10.4-5.2,3-15.4-8.9c-5.4-3.1-7.7-9.6-5.8-15.3.2-.8.6-1.6,1-2.4.5-.7,1-1.4,1.5-2.1,1.3-1.4,2.9-2.6,4.7-3.3,3.6-1.6,7.8-1.4,11.5.6l8.9,5.2c3,1.7,6.6,1.3,8.9-.8.6-.6,1.2-1.2,1.7-2,.4-.8.7-1.7.9-2.5Z\" />\n <path d=\"M68.7,44.2c-.7,1.2-2.3,1.7-3.5.9-1.3-.7-1.7-2.3-1-3.5.7-1.3,2.3-1.7,3.5-1,1.3.7,1.7,2.3,1,3.6Z\" />\n <path d=\"M25.6,21.3c5.1-.8,10.4,0,15.3,2.9l1.2.7h0c1.2.7,1.6,2.3.9,3.5s-2.3,1.7-3.5.9l-1.2-.7c-4.2-2.4-9.1-3-13.5-1.9-1.6.4-3.1,1.1-4.5,1.9h0c-1.2.7-2.8.3-3.5-1-.7-1.2-.3-2.8.9-3.5,0,0,.1,0,.3-.1.3-.1.6-.4,1-.5,2.1-1.1,4.4-1.7,6.7-2.2Z\" />\n <path d=\"M9.6,31.8c.7-1.2,2.4-1.6,3.5-.8,1.2.7,1.6,2.4.8,3.5-.8,1.2-2.4,1.6-3.6.8-1.2-.8-1.5-2.4-.7-3.5Z\" />\n <path d=\"M46.2,30.3c.7-1.3,2.3-1.7,3.5-1,1.2.7,1.7,2.3.9,3.6-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5Z\" />\n <path d=\"M52.1,54.5c-5,.9-10.4,0-15.3-2.8l-1.2-.7h0c-1.2-.7-1.7-2.3-.9-3.5s2.3-1.7,3.5-.9l1.2.7c4.3,2.4,9.1,3,13.6,1.9,1.6-.4,3.1-1.1,4.5-1.9h0c1.2-.7,2.8-.3,3.5.9.7,1.3.3,2.9-.9,3.6-.1,0-.2,0-.3,0-.4.2-.7.4-1.1.6-2.1,1-4.3,1.7-6.7,2.1Z\" />\n <path d=\"M31.4,45.6c-.7,1.2-2.3,1.7-3.5.9s-1.7-2.3-.9-3.5,2.3-1.7,3.5-.9,1.7,2.3.9,3.5Z\" />\n </svg>\n );\n}\n","import { ZOREAL_BLUE } from './mark';\n\n/**\n * The full ZOREAL lockup (mark + wordmark), from zoreal-web's\n * `images/logo/zoreal-lockup.svg`, inlined so the modal ships no external asset\n * and renders identically offline and behind a strict CSP.\n *\n * One deliberate change from the source file: the wordmark, `#0e104f` in the\n * master, is `currentColor` here. The lockup sits on the host's page in either\n * theme, and a fixed near-black wordmark disappears against a dark card. The\n * mark keeps the brand blue in both themes — it reads on either ground, and it\n * is the part that identifies whose sign-in this is.\n */\nexport function ZorealLockup({ height = 22, className }: { height?: number; className?: string }) {\n return (\n <svg\n height={height}\n width={height * (240 / 58.5)}\n viewBox=\"0 0 240 58.5\"\n role=\"img\"\n aria-label=\"ZOREAL\"\n focusable=\"false\"\n className={className}\n >\n <path\n fill=\"currentColor\"\n d=\"M205,40.5h15.3v-3.5h-11.5v-18.4h-3.8v21.8ZM157,22.2v5.6h10.9v3.5h-10.9v5.8h12.5v3.5h-16.3v-21.8h16.2v3.5h-12.4ZM141.4,25.8c0,1.1-.4,2-1.2,2.7-.8.7-1.9,1-3.3,1h-5.6v-7.3h5.6c1.4,0,2.6.3,3.4.9.8.6,1.2,1.5,1.2,2.7ZM146,40.5l-5.9-8.3c.8-.2,1.4-.5,2.1-.9s1.2-.9,1.7-1.4c.4-.5.8-1.2,1.1-1.9s.4-1.5.4-2.4-.1-2-.5-2.9c-.4-.9-.9-1.6-1.7-2.2-.6-.6-1.5-1-2.5-1.4-1-.3-2.2-.4-3.4-.4h-9.8v21.8h3.8v-7.6h4.8l5.4,7.6h4.5ZM115.7,29.7c0,1.1-.1,2.1-.5,3s-.9,1.7-1.5,2.4-1.4,1.2-2.4,1.7c-.9.4-1.9.6-3,.6s-2.1-.1-3-.6-1.7-1-2.4-1.7-1.2-1.5-1.5-2.4-.5-1.9-.5-3,.1-2.1.5-3,.9-1.7,1.5-2.4,1.4-1.2,2.4-1.7c.9-.4,1.9-.6,3-.6s2.1.2,3,.6c.9.4,1.7,1,2.3,1.7.6.6,1.2,1.5,1.6,2.4s.5,1.9.5,3ZM119.7,29.6c0-1.5-.3-3-.8-4.4-.6-1.4-1.4-2.5-2.4-3.6-1-1-2.2-1.8-3.6-2.4-1.4-.6-3-.9-4.6-.9s-3.2.4-4.6.9c-1.4.6-2.7,1.4-3.7,2.4s-1.8,2.2-2.4,3.6c-.5,1.4-.8,2.8-.8,4.4s.3,3,.8,4.4c.6,1.4,1.4,2.5,2.4,3.6,1,1,2.2,1.8,3.6,2.4,1.4.6,3,.9,4.6.9s3.2-.4,4.6-.9c1.4-.6,2.6-1.4,3.7-2.4,1-1,1.8-2.2,2.4-3.6.5-1.4.8-2.9.8-4.4ZM86.1,22.1l-13,15.6v2.8h17.9v-3.4h-12.9l12.9-15.6v-2.8h-17.5v3.4h12.5ZM188.5,18.5h-3.5l-9.6,22h4c3.7-8.8,3.4-8,7.4-17.4,3.7,8.8,3.9,9.1,7.4,17.4h4l-9.6-22Z\"\n />\n <g fill={ZOREAL_BLUE} fillRule=\"evenodd\">\n <path d=\"M52,25.7c.4-2-.5-4.2-2.5-5.4l-11.8-6.8,3.4-2,10.1,5.9c3.6,2,5.1,6.3,3.8,10.1-.2.5-.4,1-.7,1.6-.3.5-.6.9-1,1.4-.9.9-1.9,1.7-3,2.2-2.4,1-5.2.9-7.6-.5l-5.9-3.4c-1.9-1.1-4.3-.9-5.9.5-.4.4-.8.8-1.1,1.3-.3.5-.5,1.1-.6,1.7-.4,2,.6,4.2,2.5,5.4l11.8,6.8-3.4,2-10.1-5.9c-3.6-2-5.1-6.3-3.8-10.1.1-.5.4-1,.7-1.6.3-.5.7-.9,1-1.4.9-.9,1.9-1.7,3.1-2.2,2.4-1,5.2-.9,7.6.4l5.9,3.4c1.9,1.1,4.3.9,5.9-.5.4-.4.8-.8,1.1-1.3.3-.5.5-1.1.6-1.7Z\" />\n <path d=\"M60.3,33.1c-.5.8-1.5,1.1-2.3.6-.9-.5-1.1-1.5-.7-2.3.5-.9,1.5-1.1,2.3-.7.9.5,1.1,1.5.7,2.4Z\" />\n <path d=\"M31.9,18c3.4-.5,6.9,0,10,1.9l.8.5h0c.8.5,1,1.5.6,2.3s-1.5,1.1-2.3.6l-.8-.5c-2.8-1.6-6-1.9-8.9-1.2-1,.3-2,.7-3,1.2h0c-.8.5-1.8.2-2.3-.7-.5-.8-.2-1.8.6-2.3,0,0,0,0,.2,0,.2,0,.4-.2.7-.3,1.4-.7,2.9-1.1,4.4-1.4Z\" />\n <path d=\"M21.4,24.9c.5-.8,1.6-1,2.3-.5.8.5,1,1.6.5,2.3-.5.8-1.6,1-2.4.5-.8-.5-1-1.6-.5-2.3Z\" />\n <path d=\"M45.5,23.9c.5-.9,1.5-1.1,2.3-.7.8.5,1.1,1.5.6,2.4-.5.8-1.5,1.1-2.3.6s-1.1-1.5-.6-2.3Z\" />\n <path d=\"M49.4,39.9c-3.3.6-6.9,0-10-1.8l-.8-.5h0c-.8-.5-1.1-1.5-.6-2.3s1.5-1.1,2.3-.6l.8.5c2.8,1.6,6,1.9,9,1.2,1-.3,2-.7,3-1.2h0c.8-.5,1.8-.2,2.3.6.5.9.2,1.9-.6,2.4,0,0-.1,0-.2,0-.2.1-.5.3-.7.4-1.4.7-2.8,1.1-4.4,1.4Z\" />\n <path d=\"M35.8,34c-.5.8-1.5,1.1-2.3.6s-1.1-1.5-.6-2.3,1.5-1.1,2.3-.6,1.1,1.5.6,2.3Z\" />\n </g>\n </svg>\n );\n}\n","/**\n * Pairing-modal copy, carried by the SDK.\n *\n * The modal is rendered by this package, so its strings have to ship with it:\n * an integrator cannot translate a component they never write, and asking every\n * one of them to re-supply the same fifteen strings is how a sign-in screen\n * ends up half-English in production.\n *\n * No i18n runtime. A frozen record and one `{time}` substitution is the whole\n * requirement, and a dependency here would be inherited by every host app.\n *\n * Locales match the set ZOREAL's own pairing page serves, so the phone and the\n * browser say the same thing in the same language. `strings()` resolves BCP 47\n * down to that set; anything unknown falls back to English rather than\n * rendering a key.\n */\n\nexport interface PairingStrings {\n /** Dialog title while the code is still unscanned. */\n title: string;\n /** Dialog title once the request is waiting in the app. */\n titleApprove: string;\n bodyScan: string;\n bodyApprove: string;\n bodyEnrolling: string;\n waiting: string;\n waitingApproval: string;\n /** Carries `{time}`, substituted with mm:ss. */\n expiresIn: string;\n secured: string;\n noIdTitle: string;\n noIdBody: string;\n cancel: string;\n close: string;\n qrAlt: string;\n}\n\nconst en: PairingStrings = {\n title: 'Scan to sign in',\n titleApprove: 'Approve on your phone',\n bodyScan: 'Scan with your phone camera or the ZOREAL ID app.',\n bodyApprove: 'Approve the login in your ZOREAL ID app.',\n bodyEnrolling: 'Finish setting up ZOREAL ID on your phone, then approve the login.',\n waiting: 'Waiting for scan',\n waitingApproval: 'Waiting for approval',\n expiresIn: 'Expires in {time}',\n secured: 'Proof-of-Human verification by ZOREAL',\n noIdTitle: 'No ZOREAL ID yet?',\n noIdBody: 'Scan the same code to download the app and create one for free. It only takes a minute.',\n cancel: 'Cancel',\n close: 'Close',\n qrAlt: 'QR code to sign in with ZOREAL',\n};\n\nconst TRANSLATIONS: Record<string, PairingStrings> = {\n en,\n sv: {\n title: 'Skanna för att logga in',\n titleApprove: 'Godkänn på telefonen',\n bodyScan: 'Skanna med telefonens kamera eller ZOREAL ID-appen.',\n bodyApprove: 'Godkänn inloggningen i ZOREAL ID-appen.',\n bodyEnrolling: 'Slutför konfigurationen av ZOREAL ID på telefonen och godkänn sedan inloggningen.',\n waiting: 'Väntar på skanning',\n waitingApproval: 'Väntar på godkännande',\n expiresIn: 'Upphör om {time}',\n secured: 'Proof-of-Human-verifiering av ZOREAL',\n noIdTitle: 'Har du inget ZOREAL ID?',\n noIdBody: 'Skanna samma kod för att ladda ner appen och skapa ett gratis. Det tar bara en minut.',\n cancel: 'Avbryt',\n close: 'Stäng',\n qrAlt: 'QR-kod för att logga in med ZOREAL',\n },\n es: {\n title: 'Escanea para iniciar sesión',\n titleApprove: 'Apruébalo en tu teléfono',\n bodyScan: 'Escanea con la cámara de tu teléfono o con la app ZOREAL ID.',\n bodyApprove: 'Aprueba el inicio de sesión en tu app ZOREAL ID.',\n bodyEnrolling: 'Termina de configurar ZOREAL ID en tu teléfono y luego aprueba el inicio de sesión.',\n waiting: 'Esperando el escaneo',\n waitingApproval: 'Esperando aprobación',\n expiresIn: 'Caduca en {time}',\n secured: 'Verificación Proof-of-Human de ZOREAL',\n noIdTitle: '¿Aún no tienes ZOREAL ID?',\n noIdBody: 'Escanea el mismo código para descargar la app y crear una gratis. Solo toma un minuto.',\n cancel: 'Cancelar',\n close: 'Cerrar',\n qrAlt: 'Código QR para iniciar sesión con ZOREAL',\n },\n pt: {\n title: 'Digitalize para entrar',\n titleApprove: 'Aprove no seu telefone',\n bodyScan: 'Digitalize com a câmera do seu telefone ou com o app ZOREAL ID.',\n bodyApprove: 'Aprove o login no app ZOREAL ID.',\n bodyEnrolling: 'Termine de configurar o ZOREAL ID no seu telefone e depois aprove o login.',\n waiting: 'Aguardando digitalização',\n waitingApproval: 'Aguardando aprovação',\n expiresIn: 'Expira em {time}',\n secured: 'Verificação Proof-of-Human da ZOREAL',\n noIdTitle: 'Ainda não tem ZOREAL ID?',\n noIdBody: 'Digitalize o mesmo código para baixar o app e criar uma conta grátis. Leva só um minuto.',\n cancel: 'Cancelar',\n close: 'Fechar',\n qrAlt: 'Código QR para entrar com ZOREAL',\n },\n fr: {\n title: 'Scannez pour vous connecter',\n titleApprove: 'Approuvez sur votre téléphone',\n bodyScan: \"Scannez avec l'appareil photo de votre téléphone ou l'app ZOREAL ID.\",\n bodyApprove: 'Approuvez la connexion dans votre app ZOREAL ID.',\n bodyEnrolling: 'Terminez la configuration de ZOREAL ID sur votre téléphone, puis approuvez la connexion.',\n waiting: 'En attente du scan',\n waitingApproval: \"En attente d'approbation\",\n expiresIn: 'Expire dans {time}',\n secured: 'Vérification Proof-of-Human par ZOREAL',\n noIdTitle: \"Pas encore de ZOREAL ID ?\",\n noIdBody: \"Scannez le même code pour télécharger l'app et en créer un gratuitement. Cela prend une minute.\",\n cancel: 'Annuler',\n close: 'Fermer',\n qrAlt: 'Code QR pour se connecter avec ZOREAL',\n },\n de: {\n title: 'Zum Anmelden scannen',\n titleApprove: 'Auf dem Handy bestätigen',\n bodyScan: 'Mit der Handykamera oder der ZOREAL ID App scannen.',\n bodyApprove: 'Anmeldung in der ZOREAL ID App bestätigen.',\n bodyEnrolling: 'ZOREAL ID auf dem Handy fertig einrichten und dann die Anmeldung bestätigen.',\n waiting: 'Warten auf Scan',\n waitingApproval: 'Warten auf Bestätigung',\n expiresIn: 'Läuft ab in {time}',\n secured: 'Proof-of-Human-Verifizierung von ZOREAL',\n noIdTitle: 'Noch keine ZOREAL ID?',\n noIdBody: 'Denselben Code scannen, um die App zu laden und kostenlos eine zu erstellen. Dauert nur eine Minute.',\n cancel: 'Abbrechen',\n close: 'Schließen',\n qrAlt: 'QR-Code für die Anmeldung mit ZOREAL',\n },\n ru: {\n title: 'Отсканируйте, чтобы войти',\n titleApprove: 'Подтвердите на телефоне',\n bodyScan: 'Отсканируйте камерой телефона или через приложение ZOREAL ID.',\n bodyApprove: 'Подтвердите вход в приложении ZOREAL ID.',\n bodyEnrolling: 'Завершите настройку ZOREAL ID на телефоне, затем подтвердите вход.',\n waiting: 'Ожидание сканирования',\n waitingApproval: 'Ожидание подтверждения',\n expiresIn: 'Истекает через {time}',\n secured: 'Проверка Proof-of-Human от ZOREAL',\n noIdTitle: 'Ещё нет ZOREAL ID?',\n noIdBody: 'Отсканируйте тот же код, чтобы скачать приложение и создать его бесплатно. Это займёт минуту.',\n cancel: 'Отмена',\n close: 'Закрыть',\n qrAlt: 'QR-код для входа через ZOREAL',\n },\n ja: {\n title: 'スキャンしてログイン',\n titleApprove: 'スマートフォンで承認',\n bodyScan: 'スマートフォンのカメラまたはZOREAL IDアプリでスキャンしてください。',\n bodyApprove: 'ZOREAL IDアプリでログインを承認してください。',\n bodyEnrolling: 'スマートフォンでZOREAL IDの設定を完了し、ログインを承認してください。',\n waiting: 'スキャン待ち',\n waitingApproval: '承認待ち',\n expiresIn: '有効期限まで {time}',\n secured: 'ZOREALによるProof-of-Human認証',\n noIdTitle: 'ZOREAL IDをお持ちでないですか?',\n noIdBody: '同じコードをスキャンしてアプリをダウンロードし、無料で作成できます。1分ほどで完了します。',\n cancel: 'キャンセル',\n close: '閉じる',\n qrAlt: 'ZOREALでログインするためのQRコード',\n },\n hi: {\n title: 'साइन इन करने के लिए स्कैन करें',\n titleApprove: 'अपने फोन पर स्वीकृत करें',\n bodyScan: 'अपने फोन के कैमरे या ZOREAL ID ऐप से स्कैन करें।',\n bodyApprove: 'अपने ZOREAL ID ऐप में लॉगिन स्वीकृत करें।',\n bodyEnrolling: 'अपने फोन पर ZOREAL ID सेटअप पूरा करें, फिर लॉगिन स्वीकृत करें।',\n waiting: 'स्कैन की प्रतीक्षा है',\n waitingApproval: 'स्वीकृति की प्रतीक्षा है',\n expiresIn: '{time} में समाप्त',\n secured: 'ZOREAL द्वारा Proof-of-Human सत्यापन',\n noIdTitle: 'अभी तक ZOREAL ID नहीं है?',\n noIdBody: 'ऐप डाउनलोड करने और मुफ्त में एक बनाने के लिए वही कोड स्कैन करें। इसमें बस एक मिनट लगता है।',\n cancel: 'रद्द करें',\n close: 'बंद करें',\n qrAlt: 'ZOREAL से साइन इन करने के लिए QR कोड',\n },\n zhs: {\n title: '扫码登录',\n titleApprove: '在手机上批准',\n bodyScan: '使用手机相机或 ZOREAL ID 应用扫描。',\n bodyApprove: '请在 ZOREAL ID 应用中批准登录。',\n bodyEnrolling: '请在手机上完成 ZOREAL ID 设置,然后批准登录。',\n waiting: '等待扫描',\n waitingApproval: '等待批准',\n expiresIn: '{time} 后失效',\n secured: '由 ZOREAL 提供的 Proof-of-Human 验证',\n noIdTitle: '还没有 ZOREAL ID?',\n noIdBody: '扫描同一个二维码即可下载应用并免费创建,只需一分钟。',\n cancel: '取消',\n close: '关闭',\n qrAlt: '使用 ZOREAL 登录的二维码',\n },\n zht: {\n title: '掃碼登入',\n titleApprove: '在手機上核准',\n bodyScan: '使用手機相機或 ZOREAL ID 應用程式掃描。',\n bodyApprove: '請在 ZOREAL ID 應用程式中核准登入。',\n bodyEnrolling: '請在手機上完成 ZOREAL ID 設定,然後核准登入。',\n waiting: '等待掃描',\n waitingApproval: '等待核准',\n expiresIn: '{time} 後失效',\n secured: '由 ZOREAL 提供的 Proof-of-Human 驗證',\n noIdTitle: '還沒有 ZOREAL ID?',\n noIdBody: '掃描同一個 QR code 即可下載應用程式並免費建立,只需一分鐘。',\n cancel: '取消',\n close: '關閉',\n qrAlt: '使用 ZOREAL 登入的 QR code',\n },\n ar: {\n title: 'امسح لتسجيل الدخول',\n titleApprove: 'وافق على هاتفك',\n bodyScan: 'امسح باستخدام كاميرا هاتفك أو تطبيق ZOREAL ID.',\n bodyApprove: 'وافق على تسجيل الدخول في تطبيق ZOREAL ID.',\n bodyEnrolling: 'أكمل إعداد ZOREAL ID على هاتفك، ثم وافق على تسجيل الدخول.',\n waiting: 'في انتظار المسح',\n waitingApproval: 'في انتظار الموافقة',\n expiresIn: 'تنتهي الصلاحية خلال {time}',\n secured: 'التحقق من Proof-of-Human بواسطة ZOREAL',\n noIdTitle: 'ليس لديك ZOREAL ID بعد؟',\n noIdBody: 'امسح الرمز نفسه لتنزيل التطبيق وإنشاء حساب مجاني. يستغرق الأمر دقيقة واحدة فقط.',\n cancel: 'إلغاء',\n close: 'إغلاق',\n qrAlt: 'رمز QR لتسجيل الدخول باستخدام ZOREAL',\n },\n ko: {\n title: '스캔하여 로그인',\n titleApprove: '휴대폰에서 승인',\n bodyScan: '휴대폰 카메라 또는 ZOREAL ID 앱으로 스캔하세요.',\n bodyApprove: 'ZOREAL ID 앱에서 로그인을 승인하세요.',\n bodyEnrolling: '휴대폰에서 ZOREAL ID 설정을 완료한 후 로그인을 승인하세요.',\n waiting: '스캔 대기 중',\n waitingApproval: '승인 대기 중',\n expiresIn: '{time} 후 만료',\n secured: 'ZOREAL의 Proof-of-Human 인증',\n noIdTitle: '아직 ZOREAL ID가 없으신가요?',\n noIdBody: '같은 코드를 스캔해 앱을 내려받고 무료로 만드세요. 1분이면 됩니다.',\n cancel: '취소',\n close: '닫기',\n qrAlt: 'ZOREAL로 로그인하기 위한 QR 코드',\n },\n};\n\n/** Locales whose script runs right to left, so the dialog flips with `dir`. */\nconst RTL = new Set(['ar', 'he', 'fa', 'ur']);\n\n/**\n * One BCP 47 tag to a translation, or undefined if we do not carry it.\n *\n * Chinese is the only case needing more than the primary subtag: `zh-Hans` /\n * `zh-CN` / `zh-SG` are Simplified, everything else `zh` is treated as\n * Traditional, matching how the pairing page splits them.\n */\nfunction lookup(locale: string): PairingStrings | undefined {\n const tag = locale.toLowerCase().replace(/_/g, '-');\n const primary = tag.split('-')[0];\n\n if (primary === 'zh') {\n const simplified = /(^|-)(hans|cn|sg|my)(-|$)/.test(tag);\n return TRANSLATIONS[simplified ? 'zhs' : 'zht'];\n }\n return TRANSLATIONS[tag] ?? TRANSLATIONS[primary];\n}\n\n/**\n * What the browser says the person reads, best first. `languages` is the whole\n * ordered preference list, which matters: someone whose first choice we do not\n * carry may well have a second we do, and falling straight to English would\n * skip it.\n */\nfunction browserLocales(): string[] {\n if (typeof navigator === 'undefined') return [];\n const nav = navigator as Navigator & { languages?: readonly string[] };\n if (nav.languages && nav.languages.length) return [...nav.languages];\n return nav.language ? [nav.language] : [];\n}\n\n/**\n * The strings to render.\n *\n * An explicit `locale` (from the provider) wins outright: the host app knows\n * which language it is currently showing, and the modal must not disagree with\n * the page it opened on. With none given we follow the browser's own preference\n * list, so an integrator who never sets `locale` still gets a translated modal\n * instead of English-by-default. Anything we do not carry falls back to English\n * rather than rendering a key.\n */\nexport function strings(locale?: string): PairingStrings {\n if (locale) return lookup(locale) ?? en;\n for (const candidate of browserLocales()) {\n const hit = lookup(candidate);\n if (hit) return hit;\n }\n return en;\n}\n\nexport function isRtl(locale?: string): boolean {\n const tag = locale ?? browserLocales()[0];\n if (!tag) return false;\n return RTL.has(tag.toLowerCase().replace(/_/g, '-').split('-')[0]);\n}\n\n/** The one substitution the copy needs. */\nexport function interpolate(template: string, time: string): string {\n return template.replace('{time}', time);\n}\n","/**\n * The pairing modal's stylesheet, injected once on first mount.\n *\n * Why a stylesheet and not inline styles: the modal needs hover, focus-visible,\n * keyframes, `prefers-color-scheme` and `prefers-reduced-motion`. None of those\n * exist as inline style properties, and a component that silently drops its\n * focus ring and its reduced-motion fallback is not shippable in a sign-in\n * flow.\n *\n * Why injected and not a `.css` file the integrator imports: a required import\n * step is a required support ticket. Plenty of hosts (Next.js app dir, CRA,\n * plain Vite, an app with no CSS pipeline at all) treat package CSS\n * differently, and the modal has to look the same in all of them.\n *\n * Every selector is prefixed `zrl-` and every declaration is scoped under one\n * of those classes, so nothing here can reach the host's markup. Values are\n * literal rather than inherited for the same reason: a host page with an\n * aggressive reset must not be able to break the layout of a dialog the person\n * is being asked to authenticate in. Font family is the one exception — it\n * inherits the host's UI font so the modal belongs to the page it opens on.\n */\n\nconst PREFIX = 'zrl';\nexport const cx = (name: string) => `${PREFIX}-${name}`;\n\nexport const STYLE_ELEMENT_ID = 'zoreal-pairing-styles';\n\n/**\n * Palette. `light`/`dark` force a theme, `auto` follows the OS. The tokens are\n * defined three times rather than once with overrides so a forced theme never\n * depends on media-query specificity to win.\n */\nconst LIGHT = `\n --zrl-scrim: rgba(16, 18, 27, 0.45);\n --zrl-surface: #ffffff;\n --zrl-surface-sunken: #f6f7f9;\n --zrl-ink: #16181c;\n --zrl-ink-soft: #4a4f57;\n --zrl-ink-mute: #6b7078;\n --zrl-line: #e4e6ea;\n --zrl-line-soft: #eef0f3;\n --zrl-accent: #00b4d9;\n --zrl-accent-soft: #dcf3fa;\n --zrl-accent-ink: #04698a;\n --zrl-urgent: #b4761a;\n --zrl-qr-bg: #ffffff;\n --zrl-shadow: 0 1px 2px rgba(16, 18, 27, 0.06), 0 20px 50px -12px rgba(16, 18, 27, 0.3);\n --zrl-ring: rgba(16, 18, 27, 0.07);\n`;\n\nconst DARK = `\n --zrl-scrim: rgba(0, 0, 0, 0.62);\n --zrl-surface: #17191d;\n --zrl-surface-sunken: #1f2226;\n --zrl-ink: #f4f5f7;\n --zrl-ink-soft: #b3b8c0;\n --zrl-ink-mute: #8b9199;\n --zrl-line: #2c3036;\n --zrl-line-soft: #24272c;\n --zrl-accent: #34c9e8;\n --zrl-accent-soft: #0d3b47;\n --zrl-accent-ink: #7fdcf0;\n --zrl-urgent: #e0a952;\n /* The QR well stays white in dark mode on purpose: a scanner needs the\n quiet-zone contrast, and an inverted QR fails on a good number of phone\n cameras. It reads as a deliberate light panel, not a theming miss. */\n --zrl-qr-bg: #ffffff;\n --zrl-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 20px 50px -12px rgba(0, 0, 0, 0.65);\n --zrl-ring: rgba(255, 255, 255, 0.1);\n`;\n\nexport const CSS = `\n.${PREFIX}-root { ${LIGHT} }\n.${PREFIX}-root[data-theme=\"dark\"] { ${DARK} }\n@media (prefers-color-scheme: dark) {\n .${PREFIX}-root[data-theme=\"auto\"] { ${DARK} }\n}\n\n.${PREFIX}-scrim {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n display: grid;\n place-items: center;\n overflow-y: auto;\n padding: 16px;\n background: var(--zrl-scrim);\n backdrop-filter: blur(3px);\n -webkit-backdrop-filter: blur(3px);\n font-family: inherit;\n animation: ${PREFIX}-fade 200ms ease-out both;\n}\n\n.${PREFIX}-card {\n position: relative;\n box-sizing: border-box;\n width: 100%;\n max-width: 380px;\n border-radius: 16px;\n background: var(--zrl-surface);\n color: var(--zrl-ink);\n box-shadow: var(--zrl-shadow);\n outline: 1px solid var(--zrl-ring);\n outline-offset: -1px;\n text-align: center;\n animation: ${PREFIX}-rise 300ms cubic-bezier(0.23, 1, 0.32, 1) both;\n}\n\n.${PREFIX}-body { padding: 28px 24px 20px; }\n\n.${PREFIX}-lockup { display: block; margin: 0 auto; color: var(--zrl-ink); }\n\n.${PREFIX}-title {\n margin: 18px 0 0;\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n line-height: 1.3;\n color: var(--zrl-ink);\n}\n\n.${PREFIX}-body-text {\n margin: 6px auto 0;\n max-width: 30ch;\n font-size: 14px;\n line-height: 1.55;\n color: var(--zrl-ink-soft);\n}\n\n.${PREFIX}-qr-well {\n position: relative;\n display: grid;\n place-items: center;\n box-sizing: border-box;\n width: 204px;\n height: 204px;\n margin: 20px auto 0;\n padding: 12px;\n border: 1px solid var(--zrl-line);\n border-radius: 16px;\n background: var(--zrl-qr-bg);\n}\n\n.${PREFIX}-qr {\n display: block;\n width: 100%;\n height: 100%;\n border-radius: 8px;\n transition: filter 300ms cubic-bezier(0.23, 1, 0.32, 1),\n opacity 300ms cubic-bezier(0.23, 1, 0.32, 1),\n transform 300ms cubic-bezier(0.23, 1, 0.32, 1);\n}\n\n/* Once the code is claimed the QR is spent. Blurring it out rather than\n swapping it keeps one object on screen through the state change, so the eye\n reads a transformation instead of two things trading places. */\n.${PREFIX}-qr[data-spent=\"true\"] { opacity: 0.2; filter: blur(3px); transform: scale(0.96); }\n\n.${PREFIX}-qr-overlay {\n position: absolute;\n inset: 0;\n display: grid;\n place-items: center;\n animation: ${PREFIX}-fade 200ms ease-out both;\n}\n\n.${PREFIX}-qr-badge {\n display: grid;\n place-items: center;\n width: 56px;\n height: 56px;\n border-radius: 999px;\n background: var(--zrl-accent-soft);\n color: var(--zrl-accent-ink);\n}\n\n.${PREFIX}-status {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n margin-top: 20px;\n font-size: 14px;\n font-weight: 500;\n color: var(--zrl-ink);\n}\n\n.${PREFIX}-dot { position: relative; display: grid; place-items: center; width: 8px; height: 8px; }\n.${PREFIX}-dot i {\n position: absolute;\n width: 8px;\n height: 8px;\n border-radius: 999px;\n background: var(--zrl-accent);\n font-style: normal;\n}\n.${PREFIX}-dot i:first-child { animation: ${PREFIX}-ping 1.8s cubic-bezier(0.23, 1, 0.32, 1) infinite; }\n\n.${PREFIX}-timer {\n margin: 4px 0 0;\n font-size: 12px;\n font-variant-numeric: tabular-nums;\n color: var(--zrl-ink-mute);\n transition: color 200ms ease-out;\n}\n.${PREFIX}-timer[data-urgent=\"true\"] { color: var(--zrl-urgent); }\n\n.${PREFIX}-help {\n padding: 14px 24px;\n border-top: 1px solid var(--zrl-line-soft);\n background: var(--zrl-surface-sunken);\n border-radius: 0;\n}\n.${PREFIX}-help-title { margin: 0; font-size: 12px; font-weight: 600; color: var(--zrl-ink); }\n.${PREFIX}-help-body {\n margin: 4px auto 0;\n max-width: 34ch;\n font-size: 12px;\n line-height: 1.55;\n color: var(--zrl-ink-soft);\n}\n\n.${PREFIX}-footer { padding: 12px; border-top: 1px solid var(--zrl-line-soft); }\n\n.${PREFIX}-cancel {\n display: block;\n width: 100%;\n padding: 10px;\n border: 0;\n border-radius: 12px;\n background: transparent;\n font: inherit;\n font-size: 14px;\n font-weight: 500;\n color: var(--zrl-ink-soft);\n cursor: pointer;\n transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;\n}\n.${PREFIX}-cancel:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }\n.${PREFIX}-cancel:active { transform: scale(0.99); }\n\n.${PREFIX}-secured {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n margin: 6px 0 0;\n font-size: 12px;\n color: var(--zrl-ink-mute);\n}\n\n.${PREFIX}-close {\n position: absolute;\n top: 12px;\n inset-inline-end: 12px;\n display: grid;\n place-items: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: 0;\n border-radius: 8px;\n background: transparent;\n color: var(--zrl-ink-mute);\n cursor: pointer;\n transition: background-color 150ms ease-out, color 150ms ease-out, transform 150ms ease-out;\n}\n.${PREFIX}-close:hover { background: var(--zrl-surface-sunken); color: var(--zrl-ink); }\n.${PREFIX}-close:active { transform: scale(0.95); }\n\n.${PREFIX}-card :focus-visible {\n outline: 2px solid var(--zrl-accent);\n outline-offset: 2px;\n}\n\n@keyframes ${PREFIX}-fade { from { opacity: 0 } to { opacity: 1 } }\n@keyframes ${PREFIX}-rise {\n from { opacity: 0; transform: translateY(10px) scale(0.98) }\n to { opacity: 1; transform: none }\n}\n@keyframes ${PREFIX}-ping {\n 0% { transform: scale(1); opacity: 0.5 }\n 70%, 100% { transform: scale(2.6); opacity: 0 }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .${PREFIX}-scrim,\n .${PREFIX}-card,\n .${PREFIX}-qr-overlay { animation: none }\n .${PREFIX}-dot i:first-child { animation: none; opacity: 0.35 }\n .${PREFIX}-qr,\n .${PREFIX}-cancel,\n .${PREFIX}-close,\n .${PREFIX}-timer { transition: none }\n}\n`;\n\n/**\n * Injected at module scope on first import in a DOM, not per render: the tag is\n * idempotent by id, so a host with two provider instances (or a hot reload)\n * still ends up with exactly one.\n */\nexport function ensureStyles(): void {\n if (typeof document === 'undefined') return;\n if (document.getElementById(STYLE_ELEMENT_ID)) return;\n const el = document.createElement('style');\n el.id = STYLE_ELEMENT_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n}\n","import { useMemo, type CSSProperties } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport { ZorealMark } from './mark';\nimport type { NonOAuthError, ZorealLoginProps } from './types';\n\n/**\n * The drop-in button. It receives no access token, so it returns the\n * pseudonymous identity only; personal data needs the auth-code flow.\n *\n * The copy is neutral: the button asserts nothing about a person who has not\n * yet authenticated. Styling is inline and self-contained; no stylesheet, no\n * font, no external asset, because this renders on a sign-in page.\n *\n * The QR itself is no longer drawn here. `ZorealOAuthProvider` renders the\n * pairing modal for every flow, so the button and `useZorealLogin` get the\n * same dialog and it only had to be designed, translated and made accessible\n * once. Opt out with `pairingUI=\"none\"` on the provider.\n */\n\nconst TEXTS: Record<NonNullable<ZorealLoginProps['text']>, string> = {\n continue_with: 'Continue with ZOREAL',\n signin_with: 'Sign in with ZOREAL',\n signup_with: 'Sign up with ZOREAL',\n signin: 'Sign in',\n};\n\nconst SIZES = {\n large: { height: 44, font: 15, pad: 20 },\n medium: { height: 38, font: 14, pad: 16 },\n small: { height: 32, font: 12, pad: 12 },\n} as const;\n\nexport function ZorealLogin(props: ZorealLoginProps) {\n const {\n onSuccess,\n onError,\n containerProps,\n type = 'standard',\n theme = 'filled',\n size = 'large',\n text = 'continue_with',\n shape = 'rectangular',\n logo_alignment = 'left',\n width,\n click_listener,\n ...request\n } = props;\n\n const { login } = useZorealFlow({\n ...request,\n flow: 'browser-direct',\n onCredential: onSuccess,\n onError: (e) => onError?.({ type: 'unknown', description: e.description ?? e.error }),\n onNonOAuthError: (e: NonOAuthError) => onError?.(e),\n });\n\n const s = SIZES[size];\n const style: CSSProperties = useMemo(\n () => ({\n display: 'inline-flex',\n alignItems: 'center',\n justifyContent: logo_alignment === 'center' ? 'center' : 'flex-start',\n gap: 10,\n height: s.height,\n padding: `0 ${s.pad}px`,\n width,\n fontSize: s.font,\n fontFamily: 'inherit',\n fontWeight: 500,\n cursor: 'pointer',\n borderRadius: shape === 'pill' ? s.height / 2 : shape === 'square' ? 4 : 8,\n ...(theme === 'outline'\n ? { background: 'transparent', color: 'inherit', border: '1px solid rgba(128,128,128,0.5)' }\n : theme === 'filled_black'\n ? { background: '#111', color: '#fff', border: '1px solid #111' }\n : { background: '#00b4d9', color: '#fff', border: '1px solid #00b4d9' }),\n }),\n [logo_alignment, s, shape, theme, width]\n );\n\n return (\n <div {...containerProps}>\n <button\n type=\"button\"\n style={style}\n onClick={() => {\n click_listener?.();\n login();\n }}\n >\n <ZorealMark size={Math.round(s.font * 1.25)} />\n {type === 'standard' && TEXTS[text]}\n </button>\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useZorealOAuth, useZorealPairingHost } from './context';\nimport { unsafeClaims } from './jwt';\nimport {\n FlowAbandonedError,\n OAuthFlowError,\n exchangeCode,\n isMobileUserAgent,\n pollUntilApproved,\n startPairing,\n} from './pairing';\nimport { challengeS256, generateState, generateVerifier } from './pkce';\nimport type {\n AcrValue,\n AuthCodeFlowOptions,\n BrowserDirectFlowOptions,\n ErrorCode,\n NonOAuthError,\n PairingState,\n SelectBy,\n ZorealCodeResponse,\n ZorealCredentialResponse,\n ZorealLoginRequestOptions,\n} from './types';\n\nexport interface ActivePairing {\n requestId: string;\n pairUrl: string;\n qrUrl: string;\n state: PairingState;\n /** True when display resolved to the app link rather than the QR. */\n appLink: boolean;\n cancel: () => void;\n}\n\ninterface FlowInternals {\n /** Non-null while a pairing is on screen. ZorealLogin renders from this. */\n pairing: ActivePairing | null;\n}\n\n/**\n * The internal option shape: one flow discriminator, one success callback per\n * mode. The public API keeps Google's single overloaded onSuccess; this type\n * exists because an intersection of those two signatures is uninhabitable, and\n * the mapping from public to internal happens once, in useZorealLogin.\n */\nexport interface InternalFlowOptions extends ZorealLoginRequestOptions {\n flow: 'browser-direct' | 'auth-code';\n redirect_uri?: string;\n onCredential?: (response: ZorealCredentialResponse) => void;\n onCode?: (response: ZorealCodeResponse) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n}\n\n/**\n * The one flow, shared by the hook and the button. Starts a pairing, exposes\n * it for rendering, polls, and finishes per mode: browser-direct exchanges the\n * code here (public client, PKCE, no secret) and hands over an ID token;\n * auth-code hands the code and the PKCE verifier to the caller, whose backend\n * does the exchange with its client authentication.\n */\nexport function useZorealFlow(options: InternalFlowOptions): {\n login: () => void;\n internals: FlowInternals;\n} {\n const { clientId, issuer, locale } = useZorealOAuth();\n const [pairing, setPairing] = useState<ActivePairing | null>(null);\n // Null when the provider is set to pairingUI: 'none', which is the caller\n // saying they render the QR themselves. Held in a ref so `login` keeps its\n // identity across renders.\n const publish = useZorealPairingHost();\n const publishRef = useRef(publish);\n publishRef.current = publish;\n const abortRef = useRef<AbortController | null>(null);\n const optionsRef = useRef(options);\n optionsRef.current = options;\n\n // A component unmounting mid-login must stop the poll: the provider cancels\n // over-polled requests, and an orphaned interval is exactly how one happens.\n useEffect(\n () => () => {\n abortRef.current?.abort();\n publishRef.current?.(null);\n },\n []\n );\n\n const login = useCallback(() => {\n const opts = optionsRef.current;\n const run = async () => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n const flow = opts.flow;\n const verifier = generateVerifier();\n const state = generateState();\n const nonce = generateState();\n\n try {\n const started = await startPairing(issuer, {\n client_id: clientId,\n scope: opts.scope ?? 'openid',\n state,\n nonce,\n code_challenge: await challengeS256(verifier),\n redirect_uri: flow === 'auth-code' ? opts.redirect_uri : undefined,\n acr_values: Array.isArray(opts.acr_values)\n ? opts.acr_values.join(' ')\n : opts.acr_values,\n max_age: opts.max_age,\n prompt: opts.prompt,\n locale,\n });\n\n let code: string;\n let selectBy: SelectBy = 'device';\n\n if ('code' in started) {\n // prompt=none resolved silently: consented sector, live session.\n code = started.code;\n selectBy = 'session';\n } else {\n const useAppLink =\n opts.display === 'link' || (opts.display !== 'qr' && isMobileUserAgent());\n selectBy = useAppLink ? 'app_link' : 'qr';\n\n const cancel = () => {\n controller.abort();\n setPairing(null);\n publishRef.current?.(null);\n };\n // Everything a caller-rendered pairing UI needs, on every state it\n // sees: the QR flow cannot complete unless SOMETHING renders\n // pairUrl, and for the auth-code flow that something is the caller.\n const surface = {\n pairUrl: started.pair_url,\n qrUrl: `${issuer}/pair/${encodeURIComponent(started.request_id)}/qr.svg`,\n appLink: useAppLink,\n cancel,\n };\n const active: ActivePairing = {\n requestId: started.request_id,\n pairUrl: surface.pairUrl,\n qrUrl: surface.qrUrl,\n state: { status: 'pending', expiresIn: started.expires_in, ...surface },\n appLink: useAppLink,\n cancel,\n };\n setPairing(active);\n if (!useAppLink) {\n publishRef.current?.({ state: active.state, qrUrl: surface.qrUrl, cancel });\n }\n // The initial state, immediately: the first poll response is one\n // round-trip away, and a UI that waits for it opens visibly empty.\n opts.onPairingStateChange?.(active.state);\n\n if (useAppLink) {\n // The universal link, in the same tab: the app claims it, and with\n // no app installed the same URL is the real pairing page which can\n // enrol (02 sections 3 and 4). A popup here would be blocked more\n // often than it would help.\n window.location.assign(started.pair_url);\n }\n\n code = await pollUntilApproved(\n issuer,\n started.request_id,\n (s) => {\n const enriched = { ...s, ...surface };\n setPairing((p) =>\n p && p.requestId === started.request_id ? { ...p, state: enriched } : p\n );\n if (!useAppLink) {\n publishRef.current?.({ state: enriched, qrUrl: surface.qrUrl, cancel });\n }\n opts.onPairingStateChange?.(enriched);\n },\n controller.signal\n );\n }\n\n setPairing(null);\n publishRef.current?.(null);\n\n if (flow === 'auth-code') {\n opts.onCode?.({\n code,\n scope: opts.scope ?? 'openid',\n app_state: opts.app_state,\n code_verifier: verifier,\n nonce,\n });\n return;\n }\n\n const tokens = await exchangeCode(issuer, {\n code,\n code_verifier: verifier,\n client_id: clientId,\n });\n const claims = unsafeClaims(tokens.id_token);\n const response: ZorealCredentialResponse = {\n credential: tokens.id_token,\n clientId,\n select_by: selectBy,\n acr: (claims.acr as AcrValue) ?? 'zoreal.device',\n };\n opts.onCredential?.(response);\n } catch (e) {\n setPairing(null);\n publishRef.current?.(null);\n if (e instanceof DOMException && e.name === 'AbortError') return;\n if (e instanceof FlowAbandonedError) {\n opts.onNonOAuthError?.(e.reason);\n return;\n }\n if (e instanceof OAuthFlowError) {\n opts.onError?.({ error: e.error, description: e.description });\n return;\n }\n opts.onNonOAuthError?.({\n type: 'unknown',\n description: e instanceof Error ? e.message : String(e),\n });\n }\n };\n void run();\n }, [clientId, issuer, locale]);\n\n return { login, internals: { pairing } };\n}\n\nexport function useZorealLogin(\n options: { flow?: 'browser-direct' } & BrowserDirectFlowOptions\n): () => void;\nexport function useZorealLogin(options: { flow: 'auth-code' } & AuthCodeFlowOptions): () => void;\nexport function useZorealLogin(\n options: ({ flow?: 'browser-direct' | 'auth-code' } & ZorealLoginRequestOptions) &\n Partial<Pick<AuthCodeFlowOptions, 'redirect_uri' | 'ux_mode'>> & {\n onSuccess?: (response: never) => void;\n onError?: (error: Pick<NonOAuthError, 'description'> & { error: ErrorCode }) => void;\n onNonOAuthError?: (error: NonOAuthError) => void;\n }\n): () => void {\n if (options.ux_mode === 'redirect') {\n // v1 supports the popup shape only: the code and PKCE verifier go to your\n // onSuccess and from there to your backend over TLS. A redirect would have\n // to carry the verifier in a URL, which is a credential in every access\n // log on the path. Refused loudly rather than implemented badly.\n throw new Error(\n \"@zoreal/oauth2-react: ux_mode 'redirect' is not supported in v1. Use the default \" +\n \"'popup' shape and post the code and code_verifier from onSuccess to your backend.\"\n );\n }\n const flow = options.flow ?? 'browser-direct';\n return useZorealFlow({\n ...options,\n flow,\n onCredential:\n flow === 'browser-direct'\n ? (options.onSuccess as unknown as (r: ZorealCredentialResponse) => void)\n : undefined,\n onCode:\n flow === 'auth-code'\n ? (options.onSuccess as unknown as (r: ZorealCodeResponse) => void)\n : undefined,\n }).login;\n}\n","/**\n * Reads claims OUT of an ID token without verifying it.\n *\n * That is not a shortcut, it is the design: this code runs in a browser the\n * threat model assumes is attacker-controlled (02), so a signature check here\n * proves nothing to anyone. The token is verified where verification means\n * something: server-side against the JWKS. What this parser feeds is\n * convenience fields (acr on the response object) that the types document as\n * convenience, with the token staying the authority.\n */\n\nexport function unsafeClaims(idToken: string): Record<string, unknown> {\n try {\n const payload = idToken.split('.')[1] ?? '';\n const b64 = payload.replace(/-/g, '+').replace(/_/g, '/');\n const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);\n return JSON.parse(\n new TextDecoder().decode(Uint8Array.from(atob(padded), (c) => c.charCodeAt(0)))\n );\n } catch {\n return {};\n }\n}\n","/**\n * The pairing channel, client side. wire.ts pins the endpoints.\n *\n * The browser polls; the phone never talks to the browser. Everything here is\n * therefore plain fetch against the issuer, CORS-gated on the client's\n * authorized origins, with the poll cadence fixed: the provider cancels an\n * over-polling request rather than throttling it, so a \"retry\n * faster on error\" strategy here would kill the login it is trying to save.\n */\n\nimport {\n POLL_INTERVAL_ENROLLING_MS,\n POLL_INTERVAL_MS,\n SDK_VERSION,\n WIRE_VERSION,\n type PairStartResponse,\n type PairStatusResponse,\n type TokenResponse,\n} from './wire';\nimport type { ErrorCode, NonOAuthError, PairingState } from './types';\n\nexport class OAuthFlowError extends Error {\n constructor(\n public error: ErrorCode,\n public description?: string\n ) {\n super(description ?? error);\n }\n}\n\nexport class FlowAbandonedError extends Error {\n constructor(public reason: NonOAuthError) {\n super(reason.description ?? reason.type);\n }\n}\n\nexport interface StartPairingParams {\n client_id: string;\n scope: string;\n state: string;\n nonce: string;\n code_challenge: string;\n redirect_uri?: string;\n acr_values?: string;\n max_age?: number;\n prompt?: string;\n locale?: string;\n}\n\nasync function parseJson(response: Response): Promise<Record<string, unknown>> {\n try {\n return (await response.json()) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nexport async function startPairing(\n issuer: string,\n params: StartPairingParams\n): Promise<PairStartResponse> {\n const response = await fetch(`${issuer}/pair`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n ...params,\n code_challenge_method: 'S256',\n wire_version: WIRE_VERSION,\n sdk: `@zoreal/oauth2-react/${SDK_VERSION}`,\n }),\n });\n\n const body = await parseJson(response);\n if (!response.ok) {\n // The provider's words, verbatim. A refused package version arrives here,\n // and rewriting its reason would hide the only signal telling an integrator\n // to upgrade.\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n (body.error_description as string) ?? `The provider refused the request (${response.status})`\n );\n }\n return body as unknown as PairStartResponse;\n}\n\nconst sleep = (ms: number, signal?: AbortSignal) =>\n new Promise<void>((resolve, reject) => {\n // An already-aborted signal never fires its abort event, so check first\n // or the sleep runs to term and the poll takes one extra swing.\n if (signal?.aborted) {\n reject(new DOMException('aborted', 'AbortError'));\n return;\n }\n const t = setTimeout(resolve, ms);\n signal?.addEventListener('abort', () => {\n clearTimeout(t);\n reject(new DOMException('aborted', 'AbortError'));\n });\n });\n\n/**\n * Polls until the request resolves. Returns the authorization code.\n * Throws FlowAbandonedError for the human outcomes (denied, expired,\n * enrolment abandoned) and OAuthFlowError for protocol ones.\n */\nexport async function pollUntilApproved(\n issuer: string,\n requestId: string,\n onState?: (state: PairingState) => void,\n signal?: AbortSignal\n): Promise<string> {\n for (;;) {\n const response = await fetch(`${issuer}/pair/${encodeURIComponent(requestId)}/status`, {\n signal,\n });\n const body = (await parseJson(response)) as unknown as PairStatusResponse;\n\n if (!response.ok) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Pairing status failed (${response.status})`\n );\n }\n\n onState?.({\n status: body.status,\n expiresIn: body.expires_in,\n enrolmentDeadline: body.enrolment_deadline,\n });\n\n switch (body.status) {\n case 'approved':\n if (!body.code) {\n throw new OAuthFlowError('server_error', 'approved with no authorization code');\n }\n return body.code;\n case 'denied':\n throw new FlowAbandonedError({ type: 'request_denied', description: body.error_description });\n case 'expired':\n throw new FlowAbandonedError({ type: 'request_expired', description: body.error_description });\n case 'cancelled':\n // The provider cancels an over-polled or abandoned request outright\n // (its pairing rows have a real cancelled state). Before 0.1.4 this\n // fell through to the default branch and polled a dead request\n // forever.\n throw new FlowAbandonedError({\n type: 'request_expired',\n description: body.error_description ?? 'the provider cancelled the pairing request',\n });\n case 'enrolling':\n await sleep(POLL_INTERVAL_ENROLLING_MS, signal);\n break;\n default:\n await sleep(POLL_INTERVAL_MS, signal);\n }\n }\n}\n\n/**\n * The code exchange, browser-direct mode only: a public client, PKCE and no\n * secret. What comes back can only ever be the pseudonymous tier, by\n * construction rather than by rule: personal data lives at /userinfo behind an\n * access token this mode is never issued, because personal-data scopes are\n * refused for public clients at the pairing step.\n */\nexport async function exchangeCode(\n issuer: string,\n input: { code: string; code_verifier: string; client_id: string }\n): Promise<TokenResponse> {\n const response = await fetch(`${issuer}/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: input.code,\n code_verifier: input.code_verifier,\n client_id: input.client_id,\n }),\n });\n\n const body = (await parseJson(response)) as unknown as TokenResponse;\n if (!response.ok || body.error) {\n throw new OAuthFlowError(\n (body.error as ErrorCode) ?? 'server_error',\n body.error_description ?? `Token exchange failed (${response.status})`\n );\n }\n return body;\n}\n\n/** A mobile user agent gets the app link, not a QR of its own screen. */\nexport function isMobileUserAgent(): boolean {\n if (typeof navigator === 'undefined') return false;\n return /android|iphone|ipad|ipod/i.test(navigator.userAgent);\n}\n","/**\n * PKCE, S256 only: mandatory for every client, confidential ones included. There is no plain fallback and there must never\n * be one; a provider seeing method=plain is seeing a bug or an attack.\n */\n\nconst VERIFIER_BYTES = 32; // 43 base64url chars, the RFC 7636 minimum length\n\nconst base64url = (bytes: Uint8Array): string =>\n btoa(String.fromCharCode(...bytes))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n\nexport function generateVerifier(): string {\n const bytes = new Uint8Array(VERIFIER_BYTES);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n\nexport async function challengeS256(verifier: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));\n return base64url(new Uint8Array(digest));\n}\n\nexport function generateState(): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n return base64url(bytes);\n}\n","import { useEffect, useRef } from 'react';\nimport { useZorealFlow } from './useZorealLogin';\nimport type { UseZorealAutoLoginOptions } from './types';\n\n/**\n * Silent re-auth with prompt=none. NOT One Tap and never could be: there is no\n * ZOREAL session cookie in the browser to read, the credential is on a phone.\n * This succeeds only for a returning user at a consented sector with a live\n * session, the resulting acr is zoreal.session with an empty amr, and a\n * relying party that needs a live human must not build on this hook.\n *\n * PRIVACY NOTE: mounting this on a page sends a request to ZOREAL on page\n * load, before the user does anything. The hook is therefore conservative: it\n * fires once per mount, never retries, and does nothing when `disabled`.\n */\nexport function useZorealAutoLogin(options: UseZorealAutoLoginOptions): void {\n const { login } = useZorealFlow({\n flow: 'browser-direct',\n scope: options.scope,\n prompt: 'none',\n onCredential: options.onSuccess,\n onError: (e) => {\n // The provider's honest answer when no silent session exists (the\n // common case): unavailable, not an error, and never surfaced.\n const quiet = ['login_required', 'consent_required', 'interaction_required'];\n if (quiet.includes(e.error)) {\n options.onUnavailable?.();\n } else {\n options.onError?.({ type: 'unknown', description: e.description ?? e.error });\n }\n },\n onNonOAuthError: (e) => options.onError?.(e),\n });\n\n const fired = useRef(false);\n useEffect(() => {\n if (options.disabled || fired.current) return;\n fired.current = true;\n login();\n }, [options.disabled, login]);\n}\n","/**\n * Clears SDK-held local state. Named for parity with googleLogout and, like\n * it, LOCAL ONLY: it does not end the holder's ZOREAL session, which lives on\n * their phone and at the provider. A relying party that believes this signs\n * the user out of ZOREAL has a security misunderstanding, not a naming\n * complaint. The relying party's own session is the relying\n * party's to end.\n *\n * The SDK deliberately persists nothing (no localStorage, no cookies), so\n * today this has nothing to clear and exists as the stable API surface for a\n * future that does.\n */\nexport function zorealLogout(): void {\n // Intentionally empty until the SDK holds state worth clearing.\n}\n","import type { ZorealCodeResponse } from './types';\n\n/** Mirrors hasGrantedAllScopesGoogle for name-for-name portability. */\nexport function hasGrantedAllScopesZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].every((s) => granted.has(s));\n}\n\nexport function hasGrantedAnyScopeZoreal(\n response: Pick<ZorealCodeResponse, 'scope'>,\n firstScope: string,\n ...restScopes: string[]\n): boolean {\n const granted = new Set((response.scope ?? '').split(/\\s+/).filter(Boolean));\n return [firstScope, ...restScopes].some((s) => granted.has(s));\n}\n"],"mappings":"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","names":["useState","jsx","jsxs","jsx","jsxs","jsx","jsxs","useState","useMemo","useEffect","useRef","useState","useState","useRef","useEffect","jsx","jsxs","useMemo","useEffect","useRef","useRef","useEffect"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zoreal/oauth2-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Login with ZOREAL for React. A ZOREAL Verified Proof-of-Human behind every sign-in.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,10 +43,14 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/react": "^19.0.0",
|
|
46
|
+
"@types/react-dom": "^19.2.5",
|
|
46
47
|
"react": "^19.0.0",
|
|
47
48
|
"react-dom": "^19.0.0",
|
|
48
49
|
"tsup": "^8.3.0",
|
|
49
50
|
"typescript": "^5.7.0",
|
|
50
51
|
"vitest": "^3.0.0"
|
|
52
|
+
},
|
|
53
|
+
"overrides": {
|
|
54
|
+
"esbuild": "^0.28.1"
|
|
51
55
|
}
|
|
52
56
|
}
|