@authrobo/react 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx","../src/icons.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport { PROVIDER_META, type Method } from \"./icons\";\n\nexport type { Method } from \"./icons\";\nexport { PROVIDER_META } from \"./icons\";\n\nexport interface AuthRoboConfig {\n /** Your AuthRobo instance, e.g. \"https://authrobo.com\". */\n issuerUrl: string;\n /** Your app's client_id (arc_...). */\n clientId: string;\n /** Where AuthRobo returns the one-time `code` after sign-in. */\n redirectUri: string;\n}\n\n/** Shape returned by GET /api/v1/config. */\nexport interface AppConfig {\n name: string;\n brandColor: string | null;\n logoUrl: string | null;\n methods: Method[];\n}\n\nconst ConfigContext = createContext<AuthRoboConfig | null>(null);\n\nexport function AuthRoboProvider({\n config,\n children,\n}: {\n config: AuthRoboConfig;\n children: React.ReactNode;\n}) {\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;\n}\n\nfunction useResolvedConfig(override?: AuthRoboConfig): AuthRoboConfig {\n const ctx = useContext(ConfigContext);\n const c = override ?? ctx;\n if (!c) {\n throw new Error(\n \"@authrobo/react: wrap your app in <AuthRoboProvider config={...}> or pass a `config` prop.\",\n );\n }\n return c;\n}\n\nfunction randomState(): string {\n const b = new Uint8Array(16);\n (globalThis.crypto ?? (globalThis as { crypto: Crypto }).crypto).getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// Email-based methods open the hosted login page (which collects the address);\n// social providers redirect straight into their OAuth start endpoint.\nconst EMAIL_METHODS: readonly Method[] = [\"password\", \"magic_link\"];\n\n/**\n * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.\n * Social methods hit /api/v1/auth/<provider>/start; \"password\" and \"magic_link\"\n * open the hosted email login page (which renders whichever of the two the app\n * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your\n * backend exchanges (with your client_secret) for tokens.\n */\nexport function startSignIn(\n config: AuthRoboConfig,\n method: Method,\n opts?: { state?: string; loginHint?: string },\n): void {\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const state = opts?.state ?? randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", state);\n } catch {\n /* storage unavailable — state is still sent, just not persisted for a check */\n }\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n state,\n });\n if (opts?.loginHint) params.set(\"login_hint\", opts.loginHint);\n // Remember the method so we can badge it as \"Last used\" next time. Scoped per\n // client_id so different apps on the same origin don't clobber each other.\n try {\n localStorage.setItem(lastMethodKey(config.clientId), method);\n } catch {\n /* storage unavailable — badge just won't show next time */\n }\n const url = EMAIL_METHODS.includes(method)\n ? `${base}/login?${params.toString()}`\n : `${base}/api/v1/auth/${method}/start?${params.toString()}`;\n window.location.assign(url);\n}\n\nconst lastMethodKey = (clientId: string) => `authrobo:last-method:${clientId}`;\n\n/** The method last used to sign in for this client (from a prior startSignIn). */\nexport function useLastMethod(clientId: string): Method | null {\n const [last, setLast] = useState<Method | null>(null);\n useEffect(() => {\n try {\n setLast((localStorage.getItem(lastMethodKey(clientId)) as Method | null) ?? null);\n } catch {\n /* storage unavailable */\n }\n }, [clientId]);\n return last;\n}\n\n/** Fetch an app's public config (branding + enabled methods) for `clientId`. */\nexport function useAppConfig(config?: AuthRoboConfig): {\n data: AppConfig | null;\n loading: boolean;\n error: string | null;\n} {\n const c = useResolvedConfig(config);\n const [state, setState] = useState<{ data: AppConfig | null; loading: boolean; error: string | null }>({\n data: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n let active = true;\n setState({ data: null, loading: true, error: null });\n const base = c.issuerUrl.replace(/\\/$/, \"\");\n fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(c.clientId)}`)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))))\n .then((j) => {\n if (!active) return;\n setState({\n data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },\n loading: false,\n error: null,\n });\n })\n .catch((e) => active && setState({ data: null, loading: false, error: String(e?.message ?? e) }));\n return () => {\n active = false;\n };\n }, [c.issuerUrl, c.clientId]);\n\n return state;\n}\n\n/* ----------------------------- Styling ---------------------------------- */\n\nconst STYLE_ID = \"authrobo-react-styles\";\n\n// Theme-aware: tokens default to a soft light palette, flip to dark on the OS\n// preference, and can be forced either way by a `data-theme` attribute on any\n// ancestor (`<html data-theme=\"dark\">`) — the same hook AuthRobo's own hosted\n// pages use, so the injected widgets match a host app that drives its theme\n// that way. The explicit attribute wins over the OS media query by specificity.\nconst TOKENS_LIGHT = `\n --authrobo-btn-bg:#fff;--authrobo-btn-fg:#111827;--authrobo-btn-border:#d1d5db;\n --authrobo-btn-hover-bg:#f8fafc;--authrobo-btn-hover-border:#9ca3af;\n --authrobo-card-bg:#fff;--authrobo-title-fg:#0f172a;--authrobo-muted:#64748b;\n --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;\n --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;\n --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);\n --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;`;\nconst TOKENS_DARK = `\n --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;\n --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;\n --authrobo-card-bg:#0b1424;--authrobo-title-fg:#f1f5fb;--authrobo-muted:#93a4bd;\n --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;\n --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;\n --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);\n --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;`;\n// Elements that either read tokens directly or need them for their descendants\n// (a standalone button has no stack ancestor; the overlay can't inherit from\n// its own card child), so every token-owning root declares the full set.\nconst TOKEN_ROOTS =\n \".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title\";\nconst scoped = (prefix: string, tokens: string) =>\n TOKEN_ROOTS.split(\",\")\n .map((s) => `${prefix}${s.trim()}`)\n .join(\",\") + `{${tokens}}`;\n\nconst CSS = `\n${scoped(\"\", TOKENS_LIGHT)}\n@media (prefers-color-scheme:dark){${scoped(\"\", TOKENS_DARK)}}\n${scoped('[data-theme=\"light\"] ', TOKENS_LIGHT)}\n${scoped('[data-theme=\"dark\"] ', TOKENS_DARK)}\n.authrobo-btn{position:relative;display:flex;width:100%;align-items:center;justify-content:center;gap:10px;\n border:1px solid var(--authrobo-btn-border);background:var(--authrobo-btn-bg);color:var(--authrobo-btn-fg);border-radius:10px;padding:10px 14px;\n font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;\n transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}\n.authrobo-btn:hover{background:var(--authrobo-btn-hover-bg);border-color:var(--authrobo-btn-hover-border);}\n.authrobo-btn:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}\n.authrobo-last{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;\n font:600 10px/1 system-ui,sans-serif;letter-spacing:.02em;padding:3px 7px;border-radius:999px;\n background:var(--authrobo-last-bg);color:var(--authrobo-last-fg);}\n.authrobo-stack{display:flex;flex-direction:column;gap:10px;}\n.authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}\n.authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}\n.authrobo-divider::before,.authrobo-divider::after{content:\"\";height:1px;flex:1;background:var(--authrobo-divider-line);}\n.authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;\n justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);\n animation:authrobo-fade .16s ease-out both;}\n.authrobo-card{position:relative;width:100%;max-width:360px;background:var(--authrobo-card-bg);border-radius:16px;\n padding:24px;box-shadow:0 20px 60px -12px var(--authrobo-card-shadow);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}\n.authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;\n color:var(--authrobo-close);font-size:18px;line-height:1;padding:4px;}\n.authrobo-close:hover{color:var(--authrobo-close-hover);}\n@keyframes authrobo-fade{from{opacity:0}to{opacity:1}}\n@keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}\n@media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}\n`;\n\nfunction useInjectStyles() {\n useEffect(() => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n}\n\n/* ----------------------------- Button ----------------------------------- */\n\nexport function AuthRoboButton({\n provider,\n config,\n label,\n className,\n style,\n onClick,\n lastUsed,\n}: {\n provider: Method;\n config?: AuthRoboConfig;\n label?: string;\n className?: string;\n style?: React.CSSProperties;\n onClick?: () => void;\n /** Show a \"Last used\" badge (the method the visitor signed in with before). */\n lastUsed?: boolean;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const meta = PROVIDER_META[provider];\n return (\n <button\n type=\"button\"\n className={className ? `authrobo-btn ${className}` : \"authrobo-btn\"}\n style={style}\n onClick={() => {\n onClick?.();\n startSignIn(c, provider);\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\" }}>{meta.icon}</span>\n {label ?? `Continue with ${meta.label}`}\n {lastUsed && <span className=\"authrobo-last\">Last used</span>}\n </button>\n );\n}\n\n/* ---------------------- Auth modal / inline panel ----------------------- */\n\nexport function AuthRoboAuth({\n config,\n mode = \"inline\",\n open = true,\n onClose,\n title,\n methods: only,\n}: {\n config?: AuthRoboConfig;\n /** \"inline\" renders in place; \"modal\" renders a centered overlay. */\n mode?: \"inline\" | \"modal\";\n /** Only used in modal mode. */\n open?: boolean;\n onClose?: () => void;\n /** Heading; defaults to \"Sign in to <app name>\". */\n title?: string;\n /** Restrict/reorder which methods to show (defaults to all enabled ones). */\n methods?: Method[];\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const { data, loading, error } = useAppConfig(c);\n const lastMethod = useLastMethod(c.clientId);\n\n useEffect(() => {\n if (mode !== \"modal\" || !open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose?.();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [mode, open, onClose]);\n\n if (mode === \"modal\" && !open) return null;\n\n const available = data?.methods ?? [];\n const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(\n (m) => m in PROVIDER_META,\n );\n const social = shown.filter((m) => !EMAIL_METHODS.includes(m));\n const email = shown.filter((m) => EMAIL_METHODS.includes(m));\n // Only worth pointing out the last-used method when there's a choice to make.\n const badge = (m: Method) => shown.length > 1 && m === lastMethod;\n\n const panel = (\n <div className=\"authrobo-stack\">\n {(data?.logoUrl || data?.name) && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, marginBottom: 4 }}>\n {data?.logoUrl && (\n <img src={data.logoUrl} alt=\"\" width={28} height={28} style={{ borderRadius: 6 }} />\n )}\n <span className=\"authrobo-title\">\n {title ?? `Sign in${data?.name ? ` to ${data.name}` : \"\"}`}\n </span>\n </div>\n )}\n\n {loading && <p className=\"authrobo-muted\">Loading sign-in options…</p>}\n {error && <p className=\"authrobo-error\">Couldn&apos;t load sign-in options.</p>}\n\n {social.map((m) => (\n <AuthRoboButton key={m} provider={m} config={c} lastUsed={badge(m)} />\n ))}\n {email.length > 0 && social.length > 0 && <div className=\"authrobo-divider\">or</div>}\n {email.map((m) => (\n <AuthRoboButton\n key={m}\n provider={m}\n config={c}\n label={m === \"magic_link\" ? \"Email me a sign-in link\" : \"Continue with email\"}\n lastUsed={badge(m)}\n />\n ))}\n\n {!loading && !error && shown.length === 0 && (\n <p className=\"authrobo-muted\">No sign-in methods are enabled for this app.</p>\n )}\n </div>\n );\n\n if (mode === \"inline\") return panel;\n\n return (\n <div className=\"authrobo-overlay\" onClick={() => onClose?.()}>\n <div className=\"authrobo-card\" role=\"dialog\" aria-modal=\"true\" onClick={(e) => e.stopPropagation()}>\n <button type=\"button\" className=\"authrobo-close\" aria-label=\"Close\" onClick={() => onClose?.()}>\n ✕\n </button>\n {panel}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\n\n// Sign-in methods AuthRobo can surface. \"password\" and \"magic_link\" route to\n// the hosted email login page; the rest are social OAuth providers.\nexport type Method = \"google\" | \"linkedin\" | \"github\" | \"apple\" | \"password\" | \"magic_link\";\n\nconst sz = { width: 18, height: 18, \"aria-hidden\": true } as const;\n\nfunction GoogleIcon() {\n return (\n <svg viewBox=\"0 0 48 48\" {...sz}>\n <path fill=\"#FFC107\" d=\"M43.6 20.5H42V20H24v8h11.3C33.7 32.9 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z\" />\n <path fill=\"#FF3D00\" d=\"M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z\" />\n <path fill=\"#4CAF50\" d=\"M24 44c5.2 0 10-2 13.6-5.2l-6.3-5.2C29.2 35 26.7 36 24 36c-5.3 0-9.7-3.4-11.3-8.1l-6.5 5C9.5 39.6 16.2 44 24 44z\" />\n <path fill=\"#1976D2\" d=\"M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.6l6.3 5.2C41.9 36 44 30.5 44 24c0-1.3-.1-2.3-.4-3.5z\" />\n </svg>\n );\n}\n\nfunction LinkedInIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#0A66C2\">\n <path d=\"M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z\" />\n </svg>\n );\n}\n\nfunction GitHubIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#181717\">\n <path d=\"M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-6a4.7 4.7 0 0 1 1.2-3.2c-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2a4.7 4.7 0 0 1 1.2 3.2c0 4.7-2.8 5.7-5.5 6 .4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .3\" />\n </svg>\n );\n}\n\nfunction AppleIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"#000\">\n <path d=\"M17.05 12.04c-.03-2.7 2.2-4 2.3-4.06-1.25-1.84-3.2-2.09-3.9-2.12-1.66-.17-3.24.98-4.08.98-.84 0-2.14-.96-3.52-.93-1.81.03-3.48 1.05-4.41 2.67-1.88 3.27-.48 8.1 1.35 10.76.9 1.3 1.97 2.76 3.38 2.71 1.36-.05 1.87-.88 3.51-.88 1.64 0 2.1.88 3.53.85 1.46-.03 2.38-1.33 3.27-2.63 1.03-1.51 1.46-2.97 1.48-3.05-.03-.01-2.84-1.09-2.87-4.32M14.4 4.2c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.13 1.61-.69.8-1.29 2.07-1.13 3.29 1.19.09 2.41-.61 3.16-1.5\" />\n </svg>\n );\n}\n\nfunction MailIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"none\" stroke=\"#334155\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m3 7 9 6 9-6\" />\n </svg>\n );\n}\n\nexport const PROVIDER_META: Record<Method, { label: string; icon: React.ReactNode }> = {\n google: { label: \"Google\", icon: <GoogleIcon /> },\n linkedin: { label: \"LinkedIn\", icon: <LinkedInIcon /> },\n github: { label: \"GitHub\", icon: <GitHubIcon /> },\n apple: { label: \"Apple\", icon: <AppleIcon /> },\n password: { label: \"email\", icon: <MailIcon /> },\n magic_link: { label: \"magic link\", icon: <MailIcon /> },\n};\n"],"mappings":"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","names":["import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.tsx","../src/icons.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { BRAND_STYLE, PROVIDER_META, WHITE_MARKS, type Method } from \"./icons\";\n\nexport type { Method } from \"./icons\";\nexport { PROVIDER_META, BRAND_STYLE } from \"./icons\";\n\n/** Which hosted screen an email flow should open. */\nexport type Screen = \"login\" | \"register\" | \"magic\";\n\n/**\n * How to render social provider buttons.\n * - \"neutral\" (default): one consistent button chrome (themeable), brand-coloured icon.\n * - \"branded\": each button wears its provider's colours — LinkedIn blue, GitHub\n * black, Google white, Facebook blue, Apple black — with a legible glyph.\n */\nexport type ButtonStyle = \"neutral\" | \"branded\";\n\nexport interface AuthRoboConfig {\n /** Your AuthRobo instance, e.g. \"https://authrobo.com\". */\n issuerUrl: string;\n /** Your app's client_id (arc_...). */\n clientId: string;\n /** Where AuthRobo returns the one-time `code` after sign-in. */\n redirectUri: string;\n}\n\n/** Shape returned by GET /api/v1/config. */\nexport interface AppConfig {\n name: string;\n brandColor: string | null;\n logoUrl: string | null;\n methods: Method[];\n}\n\nconst ConfigContext = createContext<AuthRoboConfig | null>(null);\n\nexport function AuthRoboProvider({\n config,\n children,\n}: {\n config: AuthRoboConfig;\n children: React.ReactNode;\n}) {\n // Warm the app-config cache as soon as the provider mounts (i.e. on app start),\n // so the enabled sign-in methods are already resolved by the time a login modal\n // or page is opened — no request-on-open, no loading flash.\n useEffect(() => {\n prefetchAppConfig(config);\n }, [config.issuerUrl, config.clientId]);\n return <ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>;\n}\n\nfunction useResolvedConfig(override?: AuthRoboConfig): AuthRoboConfig {\n const ctx = useContext(ConfigContext);\n const c = override ?? ctx;\n if (!c) {\n throw new Error(\n \"@authrobo/react: wrap your app in <AuthRoboProvider config={...}> or pass a `config` prop.\",\n );\n }\n return c;\n}\n\nfunction randomState(): string {\n const b = new Uint8Array(16);\n (globalThis.crypto ?? (globalThis as { crypto: Crypto }).crypto).getRandomValues(b);\n return Array.from(b, (x) => x.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\n// Email-based methods open a hosted page (which collects the address + password\n// or mails a link); social providers redirect straight into their OAuth start.\nconst EMAIL_METHODS: readonly Method[] = [\"password\", \"magic_link\"];\n\n/** Options accepted by startSignIn and the button/panel components. */\nexport interface StartOptions {\n state?: string;\n /** Prefill the email on the hosted page. */\n loginHint?: string;\n /** For email flows: which hosted screen to open (login / register / magic). */\n screen?: Screen;\n /**\n * Route the redirect through YOUR server instead of straight to AuthRobo.\n * When set, the browser navigates to `${startPath}/<method>/start` — your\n * route sets a first-party CSRF state cookie + the real redirect_uri, then\n * bounces to AuthRobo. This is the right choice for apps that keep the session\n * in httpOnly cookies (the code exchange happens server-side with your\n * client_secret). Leave unset to redirect to AuthRobo directly.\n */\n startPath?: string;\n}\n\n/**\n * Begin sign-in: navigate the browser to the hosted flow for `method`.\n *\n * - Social methods hit /api/v1/auth/<provider>/start.\n * - \"password\" opens the hosted /login (or /register when `screen: \"register\"`).\n * - \"magic_link\" opens /login?screen=magic (the passwordless view).\n *\n * With `startPath`, all of the above are proxied through your own server route\n * (`${startPath}/<method>/start`) so the flow stays first-party. Either way,\n * AuthRobo returns to your callback with a one-time `code` your backend\n * exchanges (with your client_secret) for tokens.\n */\nexport function startSignIn(config: AuthRoboConfig, method: Method, opts?: StartOptions): void {\n // Remember the method so we can badge it \"Last used\" next time (per client_id).\n try {\n localStorage.setItem(lastMethodKey(config.clientId), method);\n } catch {\n /* storage unavailable — badge just won't show next time */\n }\n\n // App-proxied: hand off to the consumer's own server, which owns state +\n // redirect_uri. Nothing sensitive is decided client-side here.\n if (opts?.startPath) {\n const p = new URLSearchParams();\n if (opts.loginHint) p.set(\"login_hint\", opts.loginHint);\n if (opts.screen) p.set(\"screen\", opts.screen);\n const qs = p.toString();\n window.location.assign(`${opts.startPath.replace(/\\/$/, \"\")}/${method}/start${qs ? `?${qs}` : \"\"}`);\n return;\n }\n\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const state = opts?.state ?? randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", state);\n } catch {\n /* storage unavailable — state is still sent, just not persisted for a check */\n }\n const params = new URLSearchParams({\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n state,\n });\n if (opts?.loginHint) params.set(\"login_hint\", opts.loginHint);\n\n let path: string;\n if (method === \"magic_link\") {\n params.set(\"screen\", \"magic\");\n path = \"/login\";\n } else if (method === \"password\") {\n path = opts?.screen === \"register\" ? \"/register\" : \"/login\";\n } else {\n path = `/api/v1/auth/${method}/start`;\n }\n window.location.assign(`${base}${path}?${params.toString()}`);\n}\n\nconst lastMethodKey = (clientId: string) => `authrobo:last-method:${clientId}`;\n\n/** The method last used to sign in for this client (from a prior startSignIn). */\nexport function useLastMethod(clientId: string): Method | null {\n const [last, setLast] = useState<Method | null>(null);\n useEffect(() => {\n try {\n setLast((localStorage.getItem(lastMethodKey(clientId)) as Method | null) ?? null);\n } catch {\n /* storage unavailable */\n }\n }, [clientId]);\n return last;\n}\n\n/* ------------------------- App-config cache ----------------------------- */\n// A tiny module-level store so the app's public config (branding + enabled\n// methods) is fetched at most once per (issuer, clientId) and shared across every\n// hook/component. Prefetched on provider mount; read synchronously thereafter.\n\ntype ConfigState = { data: AppConfig | null; loading: boolean; error: string | null };\n\nconst LOADING_STATE: ConfigState = { data: null, loading: true, error: null };\n\ntype ConfigEntry = { state: ConfigState; promise?: Promise<void>; listeners: Set<() => void> };\n\nconst configStore = new Map<string, ConfigEntry>();\nconst configKey = (c: AuthRoboConfig) => `${c.issuerUrl.replace(/\\/$/, \"\")}|${c.clientId}`;\n\nfunction configEntry(key: string): ConfigEntry {\n let e = configStore.get(key);\n if (!e) {\n e = { state: LOADING_STATE, listeners: new Set() };\n configStore.set(key, e);\n }\n return e;\n}\n\n/**\n * Fetch (once) and cache an app's public config. Safe to call eagerly on app\n * start — concurrent calls dedupe onto the same in-flight request, and a resolved\n * entry returns immediately. Returns the in-flight promise (or a resolved one).\n */\nexport function prefetchAppConfig(config: AuthRoboConfig): Promise<void> {\n if (typeof window === \"undefined\") return Promise.resolve(); // no-op during SSR\n const key = configKey(config);\n const e = configEntry(key);\n if (e.state.data || e.promise) return e.promise ?? Promise.resolve();\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n e.promise = fetch(`${base}/api/v1/config?client_id=${encodeURIComponent(config.clientId)}`)\n .then((r) => (r.ok ? r.json() : Promise.reject(new Error(`config ${r.status}`))))\n .then((j) => {\n e.state = {\n data: { name: j.name, brandColor: j.brand_color, logoUrl: j.logo_url, methods: j.methods },\n loading: false,\n error: null,\n };\n })\n .catch((err) => {\n e.state = { data: null, loading: false, error: String(err?.message ?? err) };\n })\n .finally(() => {\n e.promise = undefined;\n e.listeners.forEach((l) => l());\n });\n return e.promise;\n}\n\n/** Fetch an app's public config (branding + enabled methods) for `clientId`. */\nexport function useAppConfig(config?: AuthRoboConfig): ConfigState {\n const c = useResolvedConfig(config);\n const key = configKey(c);\n\n const subscribe = useCallback(\n (cb: () => void) => {\n const e = configEntry(key);\n e.listeners.add(cb);\n return () => {\n e.listeners.delete(cb);\n };\n },\n [key],\n );\n const getSnapshot = useCallback(() => configEntry(key).state, [key]);\n const state = useSyncExternalStore(subscribe, getSnapshot, () => LOADING_STATE);\n\n // Kick off the fetch if the provider didn't already (e.g. no <AuthRoboProvider>).\n useEffect(() => {\n prefetchAppConfig(c);\n }, [c.issuerUrl, c.clientId]);\n\n return state;\n}\n\n/* ----------------------------- Styling ---------------------------------- */\n\nconst STYLE_ID = \"authrobo-react-styles\";\n\n// Theme-aware: tokens default to a soft light palette, flip to dark on the OS\n// preference, and can be forced either way by a `data-theme` attribute on any\n// ancestor (`<html data-theme=\"dark\">`). A host app can also override any single\n// token inline (e.g. `--authrobo-accent`) to brand the injected widgets.\nconst TOKENS_LIGHT = `\n --authrobo-btn-bg:#fff;--authrobo-btn-fg:#111827;--authrobo-btn-border:#d1d5db;\n --authrobo-btn-hover-bg:#f8fafc;--authrobo-btn-hover-border:#9ca3af;\n --authrobo-card-bg:#fff;--authrobo-title-fg:#0f172a;--authrobo-muted:#64748b;\n --authrobo-error:#b91c1c;--authrobo-divider-fg:#9ca3af;--authrobo-divider-line:#e5e7eb;\n --authrobo-overlay:rgba(2,6,23,.55);--authrobo-close:#94a3b8;--authrobo-close-hover:#334155;\n --authrobo-focus:#2b8aff;--authrobo-card-shadow:rgba(2,6,23,.4);\n --authrobo-last-bg:#e7efff;--authrobo-last-fg:#2563eb;\n --authrobo-input-bg:#fff;--authrobo-input-fg:#111827;\n --authrobo-tabs-bg:#f1f5f9;--authrobo-accent:#4338ca;--authrobo-accent-fg:#fff;`;\nconst TOKENS_DARK = `\n --authrobo-btn-bg:#0e1626;--authrobo-btn-fg:#e6edf8;--authrobo-btn-border:#28374f;\n --authrobo-btn-hover-bg:#16233a;--authrobo-btn-hover-border:#3a4d6b;\n --authrobo-card-bg:#0b1424;--authrobo-title-fg:#f1f5fb;--authrobo-muted:#93a4bd;\n --authrobo-error:#f87171;--authrobo-divider-fg:#64748b;--authrobo-divider-line:#223049;\n --authrobo-overlay:rgba(2,6,23,.72);--authrobo-close:#64748b;--authrobo-close-hover:#cbd5e1;\n --authrobo-focus:#46c8ff;--authrobo-card-shadow:rgba(0,0,0,.6);\n --authrobo-last-bg:rgba(70,160,255,.18);--authrobo-last-fg:#8fc4ff;\n --authrobo-input-bg:#0e1626;--authrobo-input-fg:#e6edf8;\n --authrobo-tabs-bg:#0e1626;--authrobo-accent:#4338ca;--authrobo-accent-fg:#fff;`;\n// Elements that either read tokens directly or need them for their descendants.\nconst TOKEN_ROOTS =\n \".authrobo-btn,.authrobo-stack,.authrobo-card,.authrobo-overlay,.authrobo-divider,.authrobo-title\";\nconst scoped = (prefix: string, tokens: string) =>\n TOKEN_ROOTS.split(\",\")\n .map((s) => `${prefix}${s.trim()}`)\n .join(\",\") + `{${tokens}}`;\n\nconst CSS = `\n${scoped(\"\", TOKENS_LIGHT)}\n@media (prefers-color-scheme:dark){${scoped(\"\", TOKENS_DARK)}}\n${scoped('[data-theme=\"light\"] ', TOKENS_LIGHT)}\n${scoped('[data-theme=\"dark\"] ', TOKENS_DARK)}\n.authrobo-btn{position:relative;display:flex;width:100%;align-items:center;justify-content:center;gap:10px;\n border:1px solid var(--authrobo-btn-border);background:var(--authrobo-btn-bg);color:var(--authrobo-btn-fg);border-radius:10px;padding:10px 14px;\n font:600 14px/1.2 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;\n transition:background .15s ease,border-color .15s ease,box-shadow .15s ease;}\n.authrobo-btn:hover{background:var(--authrobo-btn-hover-bg);border-color:var(--authrobo-btn-hover-border);}\n.authrobo-btn:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}\n.authrobo-last{position:absolute;right:8px;top:50%;transform:translateY(-50%);pointer-events:none;\n font:600 10px/1 system-ui,sans-serif;letter-spacing:.02em;padding:3px 7px;border-radius:999px;\n background:var(--authrobo-last-bg);color:var(--authrobo-last-fg);}\n.authrobo-stack{display:flex;flex-direction:column;gap:10px;}\n.authrobo-title{font:700 16px system-ui,sans-serif;color:var(--authrobo-title-fg);}\n.authrobo-muted{color:var(--authrobo-muted);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-error{color:var(--authrobo-error);font:14px system-ui,sans-serif;margin:0;}\n.authrobo-divider{display:flex;align-items:center;gap:10px;color:var(--authrobo-divider-fg);font:500 12px system-ui,sans-serif;}\n.authrobo-divider::before,.authrobo-divider::after{content:\"\";height:1px;flex:1;background:var(--authrobo-divider-line);}\n.authrobo-tabs{display:flex;gap:4px;padding:4px;border:1px solid var(--authrobo-btn-border);\n background:var(--authrobo-tabs-bg);border-radius:10px;}\n.authrobo-tab{flex:1;border:0;background:transparent;border-radius:7px;padding:8px 0;cursor:pointer;\n font:600 13px system-ui,sans-serif;color:var(--authrobo-muted);transition:background .15s ease,color .15s ease;}\n.authrobo-tab:hover{color:var(--authrobo-title-fg);}\n.authrobo-tab[aria-selected=\"true\"]{background:var(--authrobo-accent);color:var(--authrobo-accent-fg);}\n.authrobo-tab:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}\n.authrobo-form{display:flex;flex-direction:column;gap:10px;}\n.authrobo-label{font:500 12.5px system-ui,sans-serif;color:var(--authrobo-muted);}\n.authrobo-input{width:100%;box-sizing:border-box;margin-top:6px;border:1px solid var(--authrobo-btn-border);\n background:var(--authrobo-input-bg);color:var(--authrobo-input-fg);border-radius:10px;padding:10px 12px;\n font:14px system-ui,sans-serif;}\n.authrobo-input:focus{outline:none;border-color:var(--authrobo-accent);}\n.authrobo-primary{display:flex;width:100%;align-items:center;justify-content:center;gap:8px;border:0;\n background:var(--authrobo-accent);color:var(--authrobo-accent-fg);border-radius:10px;padding:11px 14px;\n font:700 14px system-ui,sans-serif;cursor:pointer;transition:filter .15s ease;}\n.authrobo-primary:hover{filter:brightness(.96);}\n.authrobo-primary:focus-visible{outline:2px solid var(--authrobo-focus);outline-offset:2px;}\n.authrobo-primary:disabled{opacity:.7;cursor:default;}\n.authrobo-btn-brand{transition:filter .15s ease,box-shadow .15s ease;}\n.authrobo-btn-brand:hover{filter:brightness(.96);}\n.authrobo-spinner{display:inline-block;width:1.15em;height:1.15em;vertical-align:-0.2em;border-radius:50%;\n border:2px solid color-mix(in srgb, currentColor 24%, transparent);border-top-color:currentColor;\n animation:authrobo-spin .7s linear infinite;}\n.authrobo-loading{display:flex;align-items:center;gap:10px;padding:4px 0;}\n@keyframes authrobo-spin{to{transform:rotate(360deg)}}\n@media (prefers-reduced-motion: reduce){.authrobo-spinner{animation-duration:1.4s}}\n.authrobo-overlay{position:fixed;inset:0;z-index:2147483000;display:flex;align-items:center;\n justify-content:center;padding:16px;background:var(--authrobo-overlay);backdrop-filter:blur(2px);\n animation:authrobo-fade .16s ease-out both;}\n.authrobo-card{position:relative;width:100%;max-width:360px;background:var(--authrobo-card-bg);border-radius:16px;\n padding:24px;box-shadow:0 20px 60px -12px var(--authrobo-card-shadow);animation:authrobo-pop .2s cubic-bezier(.16,1,.3,1) both;}\n.authrobo-close{position:absolute;top:12px;right:12px;border:0;background:transparent;cursor:pointer;\n color:var(--authrobo-close);font-size:18px;line-height:1;padding:4px;}\n.authrobo-close:hover{color:var(--authrobo-close-hover);}\n@keyframes authrobo-fade{from{opacity:0}to{opacity:1}}\n@keyframes authrobo-pop{from{opacity:0;transform:translateY(8px) scale(.97)}to{opacity:1;transform:none}}\n@media (prefers-reduced-motion: reduce){.authrobo-overlay,.authrobo-card{animation:none}}\n`;\n\nfunction useInjectStyles() {\n useEffect(() => {\n if (typeof document === \"undefined\" || document.getElementById(STYLE_ID)) return;\n const el = document.createElement(\"style\");\n el.id = STYLE_ID;\n el.textContent = CSS;\n document.head.appendChild(el);\n }, []);\n}\n\n/** A theme-aware loading spinner. Colour follows the surrounding text (currentColor). */\nexport function Spinner({ style, className }: { style?: React.CSSProperties; className?: string }) {\n return (\n <span\n className={className ? `authrobo-spinner ${className}` : \"authrobo-spinner\"}\n role=\"status\"\n aria-label=\"Loading\"\n style={style}\n />\n );\n}\n\n/* ----------------------------- Button ----------------------------------- */\n\nexport function AuthRoboButton({\n provider,\n config,\n label,\n className,\n style,\n onClick,\n lastUsed,\n startPath,\n buttonStyle = \"neutral\",\n}: {\n provider: Method;\n config?: AuthRoboConfig;\n label?: string;\n className?: string;\n style?: React.CSSProperties;\n onClick?: () => void;\n /** Show a \"Last used\" badge (the method the visitor signed in with before). */\n lastUsed?: boolean;\n /** Proxy the redirect through your own server (see StartOptions.startPath). */\n startPath?: string;\n /** \"neutral\" (default) or \"branded\" (provider-coloured button). */\n buttonStyle?: ButtonStyle;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const meta = PROVIDER_META[provider];\n const brand = buttonStyle === \"branded\" ? BRAND_STYLE[provider] : undefined;\n const icon = brand?.whiteIcon ? WHITE_MARKS[provider] ?? meta.icon : meta.icon;\n\n const classes = [\"authrobo-btn\", brand ? \"authrobo-btn-brand\" : \"\", className]\n .filter(Boolean)\n .join(\" \");\n const brandStyle: React.CSSProperties | undefined = brand\n ? { background: brand.bg, color: brand.fg, borderColor: brand.border }\n : undefined;\n\n return (\n <button\n type=\"button\"\n className={classes}\n style={{ ...brandStyle, ...style }}\n onClick={() => {\n onClick?.();\n startSignIn(c, provider, { startPath });\n }}\n >\n <span style={{ display: \"inline-flex\", alignItems: \"center\" }}>{icon}</span>\n {label ?? `Continue with ${meta.label}`}\n {lastUsed && <span className=\"authrobo-last\">Last used</span>}\n </button>\n );\n}\n\n/* ---------------------- Email switcher (tabs) --------------------------- */\n\n// Tab order is fixed: Magic Link first, then Register, then Login. Each tab is\n// filtered in only if its underlying method is enabled for the app. Every tab\n// collects exactly the fields that flow needs:\n// • Magic Link → email (a one-time link is emailed)\n// • Register → name + email + password (creates the account)\n// • Login → email + password\nconst EMAIL_TABS: {\n key: Screen;\n method: Method;\n label: string;\n cta: string;\n requires: Method;\n fields: { name: boolean; password: boolean };\n /** Server endpoint (relative to startPath) for the proxied JSON POST. */\n proxyPath: string;\n}[] = [\n { key: \"magic\", method: \"magic_link\", label: \"Magic Link\", cta: \"Email me a link\", requires: \"magic_link\", fields: { name: false, password: false }, proxyPath: \"magic\" },\n { key: \"register\", method: \"password\", label: \"Register\", cta: \"Create account\", requires: \"password\", fields: { name: true, password: true }, proxyPath: \"register\" },\n { key: \"login\", method: \"password\", label: \"Login\", cta: \"Sign in\", requires: \"password\", fields: { name: false, password: true }, proxyPath: \"login\" },\n];\n\n/**\n * The email tabs (Magic Link / Register / Login).\n *\n * Two submission modes:\n * - **Direct** (no `startPath`): a native cross-origin form POST straight to\n * AuthRobo — `authorize` for password login/register (→ returns to your\n * `redirectUri` with a one-time code your backend exchanges), or `magic/start`\n * for the link. Credentials go to AuthRobo, never to your own server.\n * - **Proxied** (`startPath` set): a same-origin JSON POST to\n * `${startPath}/{login|register|magic}` on YOUR server, which forwards to\n * AuthRobo and sets your httpOnly session cookies. On success `onSuccess()`\n * fires (no full-page redirect), so it slots into an existing modal.\n */\nfunction EmailSwitcher({\n config,\n methods,\n startPath,\n onSuccess,\n}: {\n config: AuthRoboConfig;\n methods: Method[];\n startPath?: string;\n onSuccess?: () => void;\n}) {\n const tabs = useMemo(() => EMAIL_TABS.filter((t) => methods.includes(t.requires)), [methods]);\n const [active, setActive] = useState<Screen>(tabs[0]?.key ?? \"login\");\n const [name, setName] = useState(\"\");\n const [email, setEmail] = useState(\"\");\n const [password, setPassword] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [sent, setSent] = useState(false);\n const stateRef = useRef<HTMLInputElement>(null);\n const proxy = !!startPath;\n\n // Keep the active tab valid if the enabled set changes under us.\n useEffect(() => {\n if (tabs.length && !tabs.some((t) => t.key === active)) setActive(tabs[0].key);\n }, [tabs, active]);\n\n if (!tabs.length) return null;\n const current = tabs.find((t) => t.key === active) ?? tabs[0];\n const base = config.issuerUrl.replace(/\\/$/, \"\");\n const directAction =\n current.key === \"magic\" ? `${base}/api/v1/auth/magic/start` : `${base}/api/v1/auth/authorize`;\n\n const selectTab = (key: Screen) => {\n setActive(key);\n setError(null);\n setSent(false);\n };\n\n // Proxied mode: JSON POST to the app's own route, then hand back to the host.\n async function proxySubmit(e: React.FormEvent) {\n e.preventDefault();\n setError(null);\n setLoading(true);\n try {\n const body: Record<string, string> = { email: email.trim() };\n if (current.fields.password) body.password = password;\n if (current.fields.name && name.trim()) body.name = name.trim();\n const res = await fetch(`${startPath!.replace(/\\/$/, \"\")}/${current.proxyPath}`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!res.ok) {\n const d = (await res.json().catch(() => ({}))) as { error?: string };\n throw new Error(d?.error || \"Something went wrong. Please try again.\");\n }\n try {\n localStorage.setItem(lastMethodKey(config.clientId), current.method);\n } catch {\n /* ignore */\n }\n if (current.key === \"magic\") setSent(true);\n else onSuccess?.();\n } catch (err) {\n setError(err instanceof Error ? err.message : \"Something went wrong.\");\n } finally {\n setLoading(false);\n }\n }\n\n // Direct mode: stamp a fresh CSRF state into the hidden field + sessionStorage,\n // then let the browser perform the native cross-origin form POST to AuthRobo.\n function directSubmit() {\n const s = randomState();\n try {\n sessionStorage.setItem(\"authrobo:state\", s);\n } catch {\n /* storage unavailable */\n }\n if (stateRef.current) stateRef.current.value = s;\n try {\n localStorage.setItem(lastMethodKey(config.clientId), current.method);\n } catch {\n /* ignore */\n }\n setLoading(true); // page is about to navigate away\n }\n\n if (sent) {\n return (\n <div className=\"authrobo-stack\" role=\"status\">\n <p className=\"authrobo-title\">Check your email</p>\n <p className=\"authrobo-muted\">\n We sent a sign-in link to <b>{email.trim()}</b>. It works once and expires shortly.\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"authrobo-stack\">\n {tabs.length > 1 && (\n <div className=\"authrobo-tabs\" role=\"tablist\" aria-label=\"Sign-in method\">\n {tabs.map((t) => (\n <button\n key={t.key}\n type=\"button\"\n role=\"tab\"\n aria-selected={current.key === t.key}\n className=\"authrobo-tab\"\n onClick={() => selectTab(t.key)}\n >\n {t.label}\n </button>\n ))}\n </div>\n )}\n <form\n className=\"authrobo-form\"\n method={proxy ? undefined : \"post\"}\n action={proxy ? undefined : directAction}\n onSubmit={proxy ? proxySubmit : directSubmit}\n >\n {!proxy && (\n <>\n <input type=\"hidden\" name=\"client_id\" value={config.clientId} />\n <input type=\"hidden\" name=\"redirect_uri\" value={config.redirectUri} />\n <input type=\"hidden\" name=\"state\" ref={stateRef} />\n {current.key !== \"magic\" && (\n <input type=\"hidden\" name=\"mode\" value={current.key === \"register\" ? \"register\" : \"login\"} />\n )}\n </>\n )}\n\n {current.fields.name && (\n <label className=\"authrobo-label\">\n Name\n <input\n className=\"authrobo-input\"\n name=\"name\"\n type=\"text\"\n autoComplete=\"name\"\n placeholder=\"Your name\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n />\n </label>\n )}\n <label className=\"authrobo-label\">\n Email\n <input\n className=\"authrobo-input\"\n name=\"email\"\n type=\"email\"\n required\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n </label>\n {current.fields.password && (\n <label className=\"authrobo-label\">\n Password\n <input\n className=\"authrobo-input\"\n name=\"password\"\n type=\"password\"\n required\n minLength={current.key === \"register\" ? 8 : undefined}\n autoComplete={current.key === \"register\" ? \"new-password\" : \"current-password\"}\n placeholder={current.key === \"register\" ? \"At least 8 characters\" : \"Your password\"}\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n />\n </label>\n )}\n\n {error && <p className=\"authrobo-error\">{error}</p>}\n\n <button type=\"submit\" className=\"authrobo-primary\" disabled={loading}>\n {loading ? (\n <>\n <Spinner /> Please wait…\n </>\n ) : (\n current.cta\n )}\n </button>\n </form>\n </div>\n );\n}\n\n/* ---------------------- Auth modal / inline panel ----------------------- */\n\nexport function AuthRoboAuth({\n config,\n mode = \"inline\",\n open = true,\n onClose,\n onSuccess,\n title,\n showHeader = true,\n methods: only,\n startPath,\n buttonStyle = \"neutral\",\n}: {\n config?: AuthRoboConfig;\n /** \"inline\" renders in place; \"modal\" renders a centered overlay. */\n mode?: \"inline\" | \"modal\";\n /** Only used in modal mode. */\n open?: boolean;\n onClose?: () => void;\n /**\n * Fires after a successful proxied (startPath) password login/register — the\n * session cookies are set and no page redirect happened, so refresh your auth\n * state and close the modal here. Not called for social/magic or direct mode\n * (those navigate the page).\n */\n onSuccess?: () => void;\n /** Heading; defaults to \"Sign in to <app name>\". */\n title?: string;\n /** Render the logo + title row. Set false when the host supplies its own heading. */\n showHeader?: boolean;\n /** Restrict/reorder which methods to show (defaults to all enabled ones). */\n methods?: Method[];\n /** Proxy every redirect through your own server (see StartOptions.startPath). */\n startPath?: string;\n /** \"neutral\" (default) or \"branded\" social buttons. */\n buttonStyle?: ButtonStyle;\n}) {\n useInjectStyles();\n const c = useResolvedConfig(config);\n const { data, loading, error } = useAppConfig(c);\n const lastMethod = useLastMethod(c.clientId);\n\n useEffect(() => {\n if (mode !== \"modal\" || !open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose?.();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [mode, open, onClose]);\n\n if (mode === \"modal\" && !open) return null;\n\n const available = data?.methods ?? [];\n const shown = (only ? only.filter((m) => available.includes(m)) : available).filter(\n (m) => m in PROVIDER_META,\n );\n const social = shown.filter((m) => !EMAIL_METHODS.includes(m));\n const email = shown.filter((m) => EMAIL_METHODS.includes(m));\n // Only worth pointing out the last-used method when there's a choice to make.\n const badge = (m: Method) => shown.length > 1 && m === lastMethod;\n\n const panel = (\n <div className=\"authrobo-stack\">\n {showHeader && (data?.logoUrl || data?.name) && (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, marginBottom: 4 }}>\n {data?.logoUrl && (\n <img src={data.logoUrl} alt=\"\" width={28} height={28} style={{ borderRadius: 6 }} />\n )}\n <span className=\"authrobo-title\">\n {title ?? `Sign in${data?.name ? ` to ${data.name}` : \"\"}`}\n </span>\n </div>\n )}\n\n {loading && (\n <div className=\"authrobo-loading\">\n <Spinner style={{ color: \"var(--authrobo-accent)\" }} />\n <span className=\"authrobo-muted\">Loading sign-in options…</span>\n </div>\n )}\n {error && <p className=\"authrobo-error\">Couldn&apos;t load sign-in options.</p>}\n\n {social.map((m) => (\n <AuthRoboButton\n key={m}\n provider={m}\n config={c}\n lastUsed={badge(m)}\n startPath={startPath}\n buttonStyle={buttonStyle}\n />\n ))}\n\n {email.length > 0 && social.length > 0 && <div className=\"authrobo-divider\">or</div>}\n\n {email.length > 0 && (\n <EmailSwitcher config={c} methods={email} startPath={startPath} onSuccess={onSuccess} />\n )}\n\n {!loading && !error && shown.length === 0 && (\n <p className=\"authrobo-muted\">No sign-in methods are enabled for this app.</p>\n )}\n </div>\n );\n\n if (mode === \"inline\") return panel;\n\n return (\n <div className=\"authrobo-overlay\" onClick={() => onClose?.()}>\n <div className=\"authrobo-card\" role=\"dialog\" aria-modal=\"true\" onClick={(e) => e.stopPropagation()}>\n <button type=\"button\" className=\"authrobo-close\" aria-label=\"Close\" onClick={() => onClose?.()}>\n ✕\n </button>\n {panel}\n </div>\n </div>\n );\n}\n","import * as React from \"react\";\n\n// Sign-in methods AuthRobo can surface. \"password\" and \"magic_link\" route to\n// the hosted email login page; the rest are social OAuth providers.\nexport type Method =\n | \"google\"\n | \"linkedin\"\n | \"github\"\n | \"apple\"\n | \"facebook\"\n | \"password\"\n | \"magic_link\";\n\nconst sz = { width: 18, height: 18, \"aria-hidden\": true } as const;\n\n// Single-colour brand glyphs share one path so we can also render a white\n// variant (for branded buttons whose background is the brand colour).\nconst LINKEDIN_PATH =\n \"M20.45 20.45h-3.56v-5.57c0-1.33-.02-3.04-1.85-3.04-1.85 0-2.13 1.45-2.13 2.94v5.67H9.35V9h3.41v1.56h.05c.48-.9 1.64-1.85 3.37-1.85 3.6 0 4.27 2.37 4.27 5.45v6.29zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.56V9h3.56v11.45zM22.22 0H1.77C.79 0 0 .77 0 1.72v20.56C0 23.23.79 24 1.77 24h20.45c.98 0 1.78-.77 1.78-1.72V1.72C24 .77 23.2 0 22.22 0z\";\nconst GITHUB_PATH =\n \"M12 .3a12 12 0 0 0-3.8 23.4c.6.1.8-.3.8-.6v-2c-3.3.7-4-1.6-4-1.6-.6-1.4-1.3-1.8-1.3-1.8-1.1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1.1 1.8 2.8 1.3 3.5 1 .1-.8.4-1.3.8-1.6-2.7-.3-5.5-1.3-5.5-6a4.7 4.7 0 0 1 1.2-3.2c-.1-.3-.5-1.5.1-3.2 0 0 1-.3 3.3 1.2a11.5 11.5 0 0 1 6 0c2.3-1.5 3.3-1.2 3.3-1.2.6 1.7.2 2.9.1 3.2a4.7 4.7 0 0 1 1.2 3.2c0 4.7-2.8 5.7-5.5 6 .4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6A12 12 0 0 0 12 .3\";\nconst APPLE_PATH =\n \"M17.05 12.04c-.03-2.7 2.2-4 2.3-4.06-1.25-1.84-3.2-2.09-3.9-2.12-1.66-.17-3.24.98-4.08.98-.84 0-2.14-.96-3.52-.93-1.81.03-3.48 1.05-4.41 2.67-1.88 3.27-.48 8.1 1.35 10.76.9 1.3 1.97 2.76 3.38 2.71 1.36-.05 1.87-.88 3.51-.88 1.64 0 2.1.88 3.53.85 1.46-.03 2.38-1.33 3.27-2.63 1.03-1.51 1.46-2.97 1.48-3.05-.03-.01-2.84-1.09-2.87-4.32M14.4 4.2c.74-.9 1.24-2.15 1.1-3.4-1.07.04-2.36.71-3.13 1.61-.69.8-1.29 2.07-1.13 3.29 1.19.09 2.41-.61 3.16-1.5\";\nconst FACEBOOK_PATH =\n \"M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.68.24 2.68.24v2.97h-1.51c-1.49 0-1.95.93-1.95 1.88v2.26h3.32l-.53 3.49h-2.79V24C19.61 23.1 24 18.1 24 12.07\";\n\nfunction Glyph({ d, fill }: { d: string; fill: string }) {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill={fill}>\n <path d={d} />\n </svg>\n );\n}\n\nfunction GoogleIcon() {\n return (\n <svg viewBox=\"0 0 48 48\" {...sz}>\n <path fill=\"#FFC107\" d=\"M43.6 20.5H42V20H24v8h11.3C33.7 32.9 29.3 36 24 36c-6.6 0-12-5.4-12-12s5.4-12 12-12c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 12.9 4 4 12.9 4 24s8.9 20 20 20 20-8.9 20-20c0-1.3-.1-2.3-.4-3.5z\" />\n <path fill=\"#FF3D00\" d=\"M6.3 14.7l6.6 4.8C14.7 16 19 13 24 13c3.1 0 5.9 1.2 8 3.1l5.7-5.7C34.6 6.1 29.6 4 24 4 16.3 4 9.7 8.3 6.3 14.7z\" />\n <path fill=\"#4CAF50\" d=\"M24 44c5.2 0 10-2 13.6-5.2l-6.3-5.2C29.2 35 26.7 36 24 36c-5.3 0-9.7-3.4-11.3-8.1l-6.5 5C9.5 39.6 16.2 44 24 44z\" />\n <path fill=\"#1976D2\" d=\"M43.6 20.5H42V20H24v8h11.3c-.8 2.2-2.2 4.1-4.1 5.6l6.3 5.2C41.9 36 44 30.5 44 24c0-1.3-.1-2.3-.4-3.5z\" />\n </svg>\n );\n}\n\nfunction MailIcon() {\n return (\n <svg viewBox=\"0 0 24 24\" {...sz} fill=\"none\" stroke=\"#334155\" strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <rect x=\"3\" y=\"5\" width=\"18\" height=\"14\" rx=\"2\" />\n <path d=\"m3 7 9 6 9-6\" />\n </svg>\n );\n}\n\nexport const PROVIDER_META: Record<Method, { label: string; icon: React.ReactNode }> = {\n google: { label: \"Google\", icon: <GoogleIcon /> },\n linkedin: { label: \"LinkedIn\", icon: <Glyph d={LINKEDIN_PATH} fill=\"#0A66C2\" /> },\n github: { label: \"GitHub\", icon: <Glyph d={GITHUB_PATH} fill=\"#181717\" /> },\n apple: { label: \"Apple\", icon: <Glyph d={APPLE_PATH} fill=\"#000\" /> },\n facebook: { label: \"Facebook\", icon: <Glyph d={FACEBOOK_PATH} fill=\"#1877F2\" /> },\n password: { label: \"email\", icon: <MailIcon /> },\n magic_link: { label: \"magic link\", icon: <MailIcon /> },\n};\n\n// White glyph variants — used by branded buttons whose background is the brand\n// colour (so a monochrome mark stays legible). Google keeps its multicolour mark.\nexport const WHITE_MARKS: Partial<Record<Method, React.ReactNode>> = {\n linkedin: <Glyph d={LINKEDIN_PATH} fill=\"#fff\" />,\n github: <Glyph d={GITHUB_PATH} fill=\"#fff\" />,\n apple: <Glyph d={APPLE_PATH} fill=\"#fff\" />,\n facebook: <Glyph d={FACEBOOK_PATH} fill=\"#fff\" />,\n};\n\n// Fully-branded button palette: background / text / border per provider. Used\n// when <AuthRoboAuth buttonStyle=\"branded\"> (or <AuthRoboButton buttonStyle=…>).\nexport const BRAND_STYLE: Partial<Record<Method, { bg: string; fg: string; border: string; whiteIcon?: boolean }>> = {\n google: { bg: \"#ffffff\", fg: \"#1a73e8\", border: \"#dadce0\" },\n linkedin: { bg: \"#0a66c2\", fg: \"#ffffff\", border: \"#0a66c2\", whiteIcon: true },\n github: { bg: \"#181717\", fg: \"#ffffff\", border: \"#181717\", whiteIcon: true },\n apple: { bg: \"#000000\", fg: \"#ffffff\", border: \"#000000\", whiteIcon: true },\n facebook: { bg: \"#1877f2\", fg: \"#ffffff\", border: \"#1877f2\", whiteIcon: true },\n};\n"],"mappings":"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","names":["import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -1,11 +1,26 @@
1
1
  import * as React from 'react';
2
2
 
3
- type Method = "google" | "linkedin" | "github" | "apple" | "password" | "magic_link";
3
+ type Method = "google" | "linkedin" | "github" | "apple" | "facebook" | "password" | "magic_link";
4
4
  declare const PROVIDER_META: Record<Method, {
5
5
  label: string;
6
6
  icon: React.ReactNode;
7
7
  }>;
8
+ declare const BRAND_STYLE: Partial<Record<Method, {
9
+ bg: string;
10
+ fg: string;
11
+ border: string;
12
+ whiteIcon?: boolean;
13
+ }>>;
8
14
 
15
+ /** Which hosted screen an email flow should open. */
16
+ type Screen = "login" | "register" | "magic";
17
+ /**
18
+ * How to render social provider buttons.
19
+ * - "neutral" (default): one consistent button chrome (themeable), brand-coloured icon.
20
+ * - "branded": each button wears its provider's colours — LinkedIn blue, GitHub
21
+ * black, Google white, Facebook blue, Apple black — with a legible glyph.
22
+ */
23
+ type ButtonStyle = "neutral" | "branded";
9
24
  interface AuthRoboConfig {
10
25
  /** Your AuthRobo instance, e.g. "https://authrobo.com". */
11
26
  issuerUrl: string;
@@ -25,26 +40,57 @@ declare function AuthRoboProvider({ config, children, }: {
25
40
  config: AuthRoboConfig;
26
41
  children: React.ReactNode;
27
42
  }): React.JSX.Element;
28
- /**
29
- * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.
30
- * Social methods hit /api/v1/auth/<provider>/start; "password" and "magic_link"
31
- * open the hosted email login page (which renders whichever of the two the app
32
- * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your
33
- * backend exchanges (with your client_secret) for tokens.
34
- */
35
- declare function startSignIn(config: AuthRoboConfig, method: Method, opts?: {
43
+ /** Options accepted by startSignIn and the button/panel components. */
44
+ interface StartOptions {
36
45
  state?: string;
46
+ /** Prefill the email on the hosted page. */
37
47
  loginHint?: string;
38
- }): void;
48
+ /** For email flows: which hosted screen to open (login / register / magic). */
49
+ screen?: Screen;
50
+ /**
51
+ * Route the redirect through YOUR server instead of straight to AuthRobo.
52
+ * When set, the browser navigates to `${startPath}/<method>/start` — your
53
+ * route sets a first-party CSRF state cookie + the real redirect_uri, then
54
+ * bounces to AuthRobo. This is the right choice for apps that keep the session
55
+ * in httpOnly cookies (the code exchange happens server-side with your
56
+ * client_secret). Leave unset to redirect to AuthRobo directly.
57
+ */
58
+ startPath?: string;
59
+ }
60
+ /**
61
+ * Begin sign-in: navigate the browser to the hosted flow for `method`.
62
+ *
63
+ * - Social methods hit /api/v1/auth/<provider>/start.
64
+ * - "password" opens the hosted /login (or /register when `screen: "register"`).
65
+ * - "magic_link" opens /login?screen=magic (the passwordless view).
66
+ *
67
+ * With `startPath`, all of the above are proxied through your own server route
68
+ * (`${startPath}/<method>/start`) so the flow stays first-party. Either way,
69
+ * AuthRobo returns to your callback with a one-time `code` your backend
70
+ * exchanges (with your client_secret) for tokens.
71
+ */
72
+ declare function startSignIn(config: AuthRoboConfig, method: Method, opts?: StartOptions): void;
39
73
  /** The method last used to sign in for this client (from a prior startSignIn). */
40
74
  declare function useLastMethod(clientId: string): Method | null;
41
- /** Fetch an app's public config (branding + enabled methods) for `clientId`. */
42
- declare function useAppConfig(config?: AuthRoboConfig): {
75
+ type ConfigState = {
43
76
  data: AppConfig | null;
44
77
  loading: boolean;
45
78
  error: string | null;
46
79
  };
47
- declare function AuthRoboButton({ provider, config, label, className, style, onClick, lastUsed, }: {
80
+ /**
81
+ * Fetch (once) and cache an app's public config. Safe to call eagerly on app
82
+ * start — concurrent calls dedupe onto the same in-flight request, and a resolved
83
+ * entry returns immediately. Returns the in-flight promise (or a resolved one).
84
+ */
85
+ declare function prefetchAppConfig(config: AuthRoboConfig): Promise<void>;
86
+ /** Fetch an app's public config (branding + enabled methods) for `clientId`. */
87
+ declare function useAppConfig(config?: AuthRoboConfig): ConfigState;
88
+ /** A theme-aware loading spinner. Colour follows the surrounding text (currentColor). */
89
+ declare function Spinner({ style, className }: {
90
+ style?: React.CSSProperties;
91
+ className?: string;
92
+ }): React.JSX.Element;
93
+ declare function AuthRoboButton({ provider, config, label, className, style, onClick, lastUsed, startPath, buttonStyle, }: {
48
94
  provider: Method;
49
95
  config?: AuthRoboConfig;
50
96
  label?: string;
@@ -53,18 +99,35 @@ declare function AuthRoboButton({ provider, config, label, className, style, onC
53
99
  onClick?: () => void;
54
100
  /** Show a "Last used" badge (the method the visitor signed in with before). */
55
101
  lastUsed?: boolean;
102
+ /** Proxy the redirect through your own server (see StartOptions.startPath). */
103
+ startPath?: string;
104
+ /** "neutral" (default) or "branded" (provider-coloured button). */
105
+ buttonStyle?: ButtonStyle;
56
106
  }): React.JSX.Element;
57
- declare function AuthRoboAuth({ config, mode, open, onClose, title, methods: only, }: {
107
+ declare function AuthRoboAuth({ config, mode, open, onClose, onSuccess, title, showHeader, methods: only, startPath, buttonStyle, }: {
58
108
  config?: AuthRoboConfig;
59
109
  /** "inline" renders in place; "modal" renders a centered overlay. */
60
110
  mode?: "inline" | "modal";
61
111
  /** Only used in modal mode. */
62
112
  open?: boolean;
63
113
  onClose?: () => void;
114
+ /**
115
+ * Fires after a successful proxied (startPath) password login/register — the
116
+ * session cookies are set and no page redirect happened, so refresh your auth
117
+ * state and close the modal here. Not called for social/magic or direct mode
118
+ * (those navigate the page).
119
+ */
120
+ onSuccess?: () => void;
64
121
  /** Heading; defaults to "Sign in to <app name>". */
65
122
  title?: string;
123
+ /** Render the logo + title row. Set false when the host supplies its own heading. */
124
+ showHeader?: boolean;
66
125
  /** Restrict/reorder which methods to show (defaults to all enabled ones). */
67
126
  methods?: Method[];
127
+ /** Proxy every redirect through your own server (see StartOptions.startPath). */
128
+ startPath?: string;
129
+ /** "neutral" (default) or "branded" social buttons. */
130
+ buttonStyle?: ButtonStyle;
68
131
  }): React.JSX.Element | null;
69
132
 
70
- export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, type Method, PROVIDER_META, startSignIn, useAppConfig, useLastMethod };
133
+ export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, BRAND_STYLE, type ButtonStyle, type Method, PROVIDER_META, type Screen, Spinner, type StartOptions, prefetchAppConfig, startSignIn, useAppConfig, useLastMethod };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,26 @@
1
1
  import * as React from 'react';
2
2
 
3
- type Method = "google" | "linkedin" | "github" | "apple" | "password" | "magic_link";
3
+ type Method = "google" | "linkedin" | "github" | "apple" | "facebook" | "password" | "magic_link";
4
4
  declare const PROVIDER_META: Record<Method, {
5
5
  label: string;
6
6
  icon: React.ReactNode;
7
7
  }>;
8
+ declare const BRAND_STYLE: Partial<Record<Method, {
9
+ bg: string;
10
+ fg: string;
11
+ border: string;
12
+ whiteIcon?: boolean;
13
+ }>>;
8
14
 
15
+ /** Which hosted screen an email flow should open. */
16
+ type Screen = "login" | "register" | "magic";
17
+ /**
18
+ * How to render social provider buttons.
19
+ * - "neutral" (default): one consistent button chrome (themeable), brand-coloured icon.
20
+ * - "branded": each button wears its provider's colours — LinkedIn blue, GitHub
21
+ * black, Google white, Facebook blue, Apple black — with a legible glyph.
22
+ */
23
+ type ButtonStyle = "neutral" | "branded";
9
24
  interface AuthRoboConfig {
10
25
  /** Your AuthRobo instance, e.g. "https://authrobo.com". */
11
26
  issuerUrl: string;
@@ -25,26 +40,57 @@ declare function AuthRoboProvider({ config, children, }: {
25
40
  config: AuthRoboConfig;
26
41
  children: React.ReactNode;
27
42
  }): React.JSX.Element;
28
- /**
29
- * Begin sign-in: navigate the browser to AuthRobo's hosted flow for `method`.
30
- * Social methods hit /api/v1/auth/<provider>/start; "password" and "magic_link"
31
- * open the hosted email login page (which renders whichever of the two the app
32
- * has enabled). AuthRobo returns to your `redirectUri` with a `code` that your
33
- * backend exchanges (with your client_secret) for tokens.
34
- */
35
- declare function startSignIn(config: AuthRoboConfig, method: Method, opts?: {
43
+ /** Options accepted by startSignIn and the button/panel components. */
44
+ interface StartOptions {
36
45
  state?: string;
46
+ /** Prefill the email on the hosted page. */
37
47
  loginHint?: string;
38
- }): void;
48
+ /** For email flows: which hosted screen to open (login / register / magic). */
49
+ screen?: Screen;
50
+ /**
51
+ * Route the redirect through YOUR server instead of straight to AuthRobo.
52
+ * When set, the browser navigates to `${startPath}/<method>/start` — your
53
+ * route sets a first-party CSRF state cookie + the real redirect_uri, then
54
+ * bounces to AuthRobo. This is the right choice for apps that keep the session
55
+ * in httpOnly cookies (the code exchange happens server-side with your
56
+ * client_secret). Leave unset to redirect to AuthRobo directly.
57
+ */
58
+ startPath?: string;
59
+ }
60
+ /**
61
+ * Begin sign-in: navigate the browser to the hosted flow for `method`.
62
+ *
63
+ * - Social methods hit /api/v1/auth/<provider>/start.
64
+ * - "password" opens the hosted /login (or /register when `screen: "register"`).
65
+ * - "magic_link" opens /login?screen=magic (the passwordless view).
66
+ *
67
+ * With `startPath`, all of the above are proxied through your own server route
68
+ * (`${startPath}/<method>/start`) so the flow stays first-party. Either way,
69
+ * AuthRobo returns to your callback with a one-time `code` your backend
70
+ * exchanges (with your client_secret) for tokens.
71
+ */
72
+ declare function startSignIn(config: AuthRoboConfig, method: Method, opts?: StartOptions): void;
39
73
  /** The method last used to sign in for this client (from a prior startSignIn). */
40
74
  declare function useLastMethod(clientId: string): Method | null;
41
- /** Fetch an app's public config (branding + enabled methods) for `clientId`. */
42
- declare function useAppConfig(config?: AuthRoboConfig): {
75
+ type ConfigState = {
43
76
  data: AppConfig | null;
44
77
  loading: boolean;
45
78
  error: string | null;
46
79
  };
47
- declare function AuthRoboButton({ provider, config, label, className, style, onClick, lastUsed, }: {
80
+ /**
81
+ * Fetch (once) and cache an app's public config. Safe to call eagerly on app
82
+ * start — concurrent calls dedupe onto the same in-flight request, and a resolved
83
+ * entry returns immediately. Returns the in-flight promise (or a resolved one).
84
+ */
85
+ declare function prefetchAppConfig(config: AuthRoboConfig): Promise<void>;
86
+ /** Fetch an app's public config (branding + enabled methods) for `clientId`. */
87
+ declare function useAppConfig(config?: AuthRoboConfig): ConfigState;
88
+ /** A theme-aware loading spinner. Colour follows the surrounding text (currentColor). */
89
+ declare function Spinner({ style, className }: {
90
+ style?: React.CSSProperties;
91
+ className?: string;
92
+ }): React.JSX.Element;
93
+ declare function AuthRoboButton({ provider, config, label, className, style, onClick, lastUsed, startPath, buttonStyle, }: {
48
94
  provider: Method;
49
95
  config?: AuthRoboConfig;
50
96
  label?: string;
@@ -53,18 +99,35 @@ declare function AuthRoboButton({ provider, config, label, className, style, onC
53
99
  onClick?: () => void;
54
100
  /** Show a "Last used" badge (the method the visitor signed in with before). */
55
101
  lastUsed?: boolean;
102
+ /** Proxy the redirect through your own server (see StartOptions.startPath). */
103
+ startPath?: string;
104
+ /** "neutral" (default) or "branded" (provider-coloured button). */
105
+ buttonStyle?: ButtonStyle;
56
106
  }): React.JSX.Element;
57
- declare function AuthRoboAuth({ config, mode, open, onClose, title, methods: only, }: {
107
+ declare function AuthRoboAuth({ config, mode, open, onClose, onSuccess, title, showHeader, methods: only, startPath, buttonStyle, }: {
58
108
  config?: AuthRoboConfig;
59
109
  /** "inline" renders in place; "modal" renders a centered overlay. */
60
110
  mode?: "inline" | "modal";
61
111
  /** Only used in modal mode. */
62
112
  open?: boolean;
63
113
  onClose?: () => void;
114
+ /**
115
+ * Fires after a successful proxied (startPath) password login/register — the
116
+ * session cookies are set and no page redirect happened, so refresh your auth
117
+ * state and close the modal here. Not called for social/magic or direct mode
118
+ * (those navigate the page).
119
+ */
120
+ onSuccess?: () => void;
64
121
  /** Heading; defaults to "Sign in to <app name>". */
65
122
  title?: string;
123
+ /** Render the logo + title row. Set false when the host supplies its own heading. */
124
+ showHeader?: boolean;
66
125
  /** Restrict/reorder which methods to show (defaults to all enabled ones). */
67
126
  methods?: Method[];
127
+ /** Proxy every redirect through your own server (see StartOptions.startPath). */
128
+ startPath?: string;
129
+ /** "neutral" (default) or "branded" social buttons. */
130
+ buttonStyle?: ButtonStyle;
68
131
  }): React.JSX.Element | null;
69
132
 
70
- export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, type Method, PROVIDER_META, startSignIn, useAppConfig, useLastMethod };
133
+ export { type AppConfig, AuthRoboAuth, AuthRoboButton, type AuthRoboConfig, AuthRoboProvider, BRAND_STYLE, type ButtonStyle, type Method, PROVIDER_META, type Screen, Spinner, type StartOptions, prefetchAppConfig, startSignIn, useAppConfig, useLastMethod };