{"version":3,"sources":["../../src/sharing/consumeHandoffToken.ts","../../src/sharing/createHandoffRouteHandler.ts","../../src/lib/message-protocol.ts","../../src/sharing/multiAccountAutoAdd.ts","../../src/sharing/HandoffLanding.tsx","../../src/components/AccountSwitcherProvider.tsx","../../src/hooks/useAccountSwitcher.ts","../../src/sharing/PrepEmbed.tsx"],"sourcesContent":["import type {\r\n  ConsumeHandoffTokenOptions,\r\n  HandoffFailureReason,\r\n  HandoffResult,\r\n} from './types';\r\n\r\nconst DEFAULT_TIMEOUT_MS = 5_000;\r\n\r\nfunction mapStatusToReason(status: number): HandoffFailureReason {\r\n  switch (status) {\r\n    case 410: return 'expired';\r\n    case 409: return 'consumed';\r\n    case 403: return 'wrong_target';\r\n    default:  return 'unknown';\r\n  }\r\n}\r\n\r\n/**\r\n * Server-side handoff token redemption.\r\n *\r\n * Calls the destination's own PHP backend at `POST /api/v1/inbound/handoff/exchange`\r\n * (controller mounted by `auth-service-helper` in phase D2c). The backend relays\r\n * to auth-service `POST /handoff-tokens/{token}/exchange` with its X-API-KEY,\r\n * then returns the resolved user + session token to this Next layer.\r\n *\r\n * Never call auth-service directly from here — keep service API keys in PHP.\r\n */\r\nexport async function consumeHandoffToken(\r\n  options: ConsumeHandoffTokenOptions,\r\n): Promise<HandoffResult> {\r\n  const { token, backendUrl, serviceApiKey, internalToken } = options;\r\n  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n\r\n  const controller = new AbortController();\r\n  const timer = setTimeout(() => controller.abort(), timeoutMs);\r\n\r\n  let response: Response;\r\n  try {\r\n    response = await fetch(`${backendUrl}/api/v1/inbound/handoff/exchange`, {\r\n      method: 'POST',\r\n      headers: {\r\n        'Content-Type': 'application/json',\r\n        Accept: 'application/json',\r\n        'X-API-KEY': serviceApiKey,\r\n        'X-Internal-Token': internalToken,\r\n      },\r\n      body: JSON.stringify({ token }),\r\n      signal: controller.signal,\r\n      cache: 'no-store',\r\n    });\r\n  } catch {\r\n    clearTimeout(timer);\r\n    return { ok: false, reason: 'network' };\r\n  }\r\n  clearTimeout(timer);\r\n\r\n  if (!response.ok) {\r\n    return { ok: false, reason: mapStatusToReason(response.status), status: response.status };\r\n  }\r\n\r\n  let body: unknown;\r\n  try {\r\n    body = await response.json();\r\n  } catch {\r\n    return { ok: false, reason: 'unknown', status: response.status };\r\n  }\r\n\r\n  if (!isExchangeBody(body)) {\r\n    return { ok: false, reason: 'unknown', status: response.status };\r\n  }\r\n\r\n  return {\r\n    ok: true,\r\n    user: {\r\n      uuid: body.user.uuid,\r\n      name: body.user.name,\r\n      email: body.user.email,\r\n    },\r\n    sessionToken: body.session_token,\r\n    shareId: body.share_id,\r\n    nextPath: body.next_path ?? '/',\r\n  };\r\n}\r\n\r\nfunction isExchangeBody(value: unknown): value is {\r\n  user: { uuid: string; name: string; email: string };\r\n  session_token: string;\r\n  share_id: string;\r\n  next_path?: string | null;\r\n} {\r\n  if (!value || typeof value !== 'object') return false;\r\n  const v = value as Record<string, unknown>;\r\n  if (typeof v.session_token !== 'string') return false;\r\n  if (typeof v.share_id !== 'string') return false;\r\n  const u = v.user as Record<string, unknown> | undefined;\r\n  if (!u || typeof u !== 'object') return false;\r\n  return (\r\n    typeof u.uuid === 'string' &&\r\n    typeof u.name === 'string' &&\r\n    typeof u.email === 'string'\r\n  );\r\n}\r\n","import { consumeHandoffToken } from './consumeHandoffToken';\r\n\r\nexport interface CreateHandoffRouteHandlerOptions {\r\n  /** Destination's own PHP backend base URL (server env var). */\r\n  backendUrl: string;\r\n  /** Destination's service API key (server env var). */\r\n  serviceApiKey: string;\r\n  /** Internal shared secret between this Next server and its PHP backend. */\r\n  internalToken: string;\r\n  /** Where to send users on missing/invalid tokens. Default: `/login`. */\r\n  loginPath?: string;\r\n  /** Landing page path that runs the client-side multi-account auto-add. Default: `/auth/handoff/landing`. */\r\n  landingPath?: string;\r\n}\r\n\r\n/**\r\n * Build a Next App Router GET handler for `app/auth/handoff/route.ts`.\r\n *\r\n * Consumer usage (copy-pasta — package can't ship route files):\r\n *\r\n * ```ts\r\n * // src/app/auth/handoff/route.ts\r\n * import { createHandoffRouteHandler } from '@benbraide/auth-service-nextjs/sharing';\r\n *\r\n * export const GET = createHandoffRouteHandler({\r\n *   backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL!,\r\n *   serviceApiKey: process.env.NEXT_SERVICE_API_KEY!,\r\n *   internalToken: process.env.NEXT_INTERNAL_TOKEN!,\r\n * });\r\n * ```\r\n */\r\nexport function createHandoffRouteHandler(\r\n  options: CreateHandoffRouteHandlerOptions,\r\n) {\r\n  const loginPath = options.loginPath ?? '/login';\r\n  const landingPath = options.landingPath ?? '/auth/handoff/landing';\r\n\r\n  return async function GET(request: Request): Promise<Response> {\r\n    const url = new URL(request.url);\r\n    const token = url.searchParams.get('token');\r\n    const nextParam = url.searchParams.get('next');\r\n\r\n    if (!token) {\r\n      return Response.redirect(new URL(loginPath, url), 302);\r\n    }\r\n\r\n    const result = await consumeHandoffToken({\r\n      token,\r\n      backendUrl: options.backendUrl,\r\n      serviceApiKey: options.serviceApiKey,\r\n      internalToken: options.internalToken,\r\n    });\r\n\r\n    if (!result.ok) {\r\n      const errUrl = new URL(loginPath, url);\r\n      errUrl.searchParams.set('error', `handoff_${result.reason}`);\r\n      return Response.redirect(errUrl, 302);\r\n    }\r\n\r\n    const bridge = new URL(landingPath, url);\r\n    bridge.searchParams.set('account_uuid', result.user.uuid);\r\n    bridge.searchParams.set('session_token', result.sessionToken);\r\n    bridge.searchParams.set('next', nextParam ?? result.nextPath);\r\n    bridge.searchParams.set('toast', `Now viewing as ${result.user.name}`);\r\n    return Response.redirect(bridge, 302);\r\n  };\r\n}\r\n","/**\r\n * Message types for PostMessage communication between iframe and parent window\r\n */\r\nexport const MESSAGE_TYPES = {\r\n  // Handshake messages\r\n  READY: 'READY',                          // iframe → host: Iframe loaded and ready\r\n  HANDSHAKE: 'HANDSHAKE',                  // host → iframe: Host origin identification\r\n  HANDSHAKE_ACK: 'HANDSHAKE_ACK',          // iframe → host: Origin validated, connection established\r\n\r\n  // Unified Action Protocol (new)\r\n  ACTION_REQUEST: 'ACTION_REQUEST',        // host → iframe: Unified action request\r\n  URL_REDIRECT: 'URL_REDIRECT',            // iframe → host: Unified navigation request\r\n\r\n  // Account query messages (legacy - kept for backward compatibility)\r\n  GET_ACCOUNTS: 'GET_ACCOUNTS',            // host → iframe: Request account list\r\n  ACCOUNTS_DATA: 'ACCOUNTS_DATA',          // iframe → host: Send account data\r\n\r\n  // Account action messages (legacy - kept for backward compatibility)\r\n  SWITCH_ACCOUNT: 'SWITCH_ACCOUNT',        // host → iframe: Request account switch\r\n  ACCOUNT_SWITCHED: 'ACCOUNT_SWITCHED',    // iframe → host: Account switch completed\r\n  ADD_ACCOUNT: 'ADD_ACCOUNT',              // host → iframe: Request add account flow\r\n  ACCOUNT_ADDED: 'ACCOUNT_ADDED',          // iframe → host: Account added successfully\r\n\r\n  // Cross-product handoff (Sharing protocol — F3/F4/A6)\r\n  // Values MUST be SCREAMING_SNAKE to match the auth-service iframe's own\r\n  // MESSAGE_TYPES registry (resources/js/widgets/message-protocol.js). The\r\n  // iframe's isValidMessage() whitelists by value, so a kebab-case slug is\r\n  // silently dropped and the handoff never completes.\r\n  ADD_ACCOUNT_WITH_TOKEN: 'ADD_ACCOUNT_WITH_TOKEN',                  // host → iframe: push a pre-authenticated account\r\n  ADD_ACCOUNT_WITH_TOKEN_OK: 'ADD_ACCOUNT_WITH_TOKEN_OK',            // iframe → host: cookie written, account appended\r\n  ADD_ACCOUNT_WITH_TOKEN_FAILED: 'ADD_ACCOUNT_WITH_TOKEN_FAILED',    // iframe → host: backend rejected the session token\r\n\r\n  // Prep–Sign–Promote pattern (v1.4 sharing/prep/*)\r\n  PREP_SIGNED:      'prep-signed',         // peer iframe → host: signature captured, prep flipped to 'signed'\r\n  PREP_SIGN_FAILED: 'prep-sign-failed',    // peer iframe → host: signature rejected by intent handler\r\n\r\n  MANAGE_ACCOUNT: 'MANAGE_ACCOUNT',        // iframe → host: Request account management flow\r\n  UPDATE_AVATAR: 'UPDATE_AVATAR',          // iframe → host: Request avatar update flow\r\n  LOGOUT_ACCOUNT: 'LOGOUT_ACCOUNT',        // host → iframe: Request logout specific account\r\n  ACCOUNT_LOGGED_OUT: 'ACCOUNT_LOGGED_OUT', // iframe → host: Account logged out\r\n  LOGOUT_ALL: 'LOGOUT_ALL',                // host → iframe: Request logout all accounts\r\n  ALL_LOGGED_OUT: 'ALL_LOGGED_OUT',        // iframe → host: All accounts logged out\r\n\r\n  // Interactive request messages (iframe → host)\r\n  CONFIRM_REQUEST: 'CONFIRM_REQUEST',      // iframe → host: Request user confirmation\r\n  PROMPT_REQUEST: 'PROMPT_REQUEST',        // iframe → host: Request user text input\r\n  ALERT_REQUEST: 'ALERT_REQUEST',          // iframe → host: Request user acknowledgment\r\n  REQUEST_RESPONSE: 'REQUEST_RESPONSE',    // host → iframe: Response to interactive request\r\n\r\n  // Event notifications\r\n  SESSION_CHANGED: 'SESSION_CHANGED',      // iframe → host: Session state changed\r\n  TOKEN_REFRESHED: 'TOKEN_REFRESHED',      // iframe → host: Auth token refreshed\r\n  SIZE_CHANGED: 'SIZE_CHANGED',            // iframe → host: Content height changed\r\n\r\n  // External session management\r\n  SET_SESSION: 'SET_SESSION',              // host → iframe: Set session from external login\r\n  SESSION_SET: 'SESSION_SET',              // iframe → host: Confirm session was set\r\n\r\n  // Page context messages\r\n  SET_PAGE_URL: 'SET_PAGE_URL',            // host → iframe: Update current page URL\r\n\r\n  // Error messages\r\n  ERROR: 'ERROR',                          // both ways: Error notification\r\n  UNAUTHORIZED: 'UNAUTHORIZED',            // iframe → host: Origin not authorized\r\n} as const;\r\n\r\n/**\r\n * PostMessage message structure\r\n */\r\nexport interface PostMessageData {\r\n  type: string;\r\n  payload?: any;\r\n  requestId?: string;\r\n  timestamp?: number;\r\n  version?: string;\r\n}\r\n\r\n/**\r\n * Session change event payload\r\n */\r\nexport interface SessionChangedPayload {\r\n  isAuthenticated: boolean;\r\n  currentUser: any;\r\n  accounts: any[];\r\n}\r\n\r\n/**\r\n * Account switched event payload\r\n */\r\nexport interface AccountSwitchedPayload {\r\n  uuid: string;\r\n  name: string;\r\n  email: string;\r\n  avatar_url?: string;\r\n}\r\n\r\n/**\r\n * Error event payload\r\n */\r\nexport interface ErrorPayload {\r\n  message: string;\r\n  code?: string;\r\n  details?: any;\r\n}\r\n\r\n/**\r\n * Resize event payload\r\n */\r\nexport interface ResizePayload {\r\n  height: number;\r\n}\r\n\r\n/**\r\n * Action request payload (Unified Action Protocol)\r\n */\r\nexport interface ActionRequestPayload {\r\n  action: string;\r\n  params: Record<string, any>;\r\n}\r\n\r\n/**\r\n * URL redirect payload (Unified Action Protocol)\r\n */\r\nexport interface UrlRedirectPayload {\r\n  url: string;\r\n  reason?: string;\r\n  openInNewTab?: boolean;\r\n}\r\n\r\n/**\r\n * Sharing protocol — request payload sent by `multiAccountAutoAdd` (F4) to the\r\n * auth-service iframe (A6). The iframe redeems the session token at its own\r\n * backend, writes the destination-scoped session cookie, and acks with\r\n * ADD_ACCOUNT_WITH_TOKEN_OK (or ..._FAILED).\r\n */\r\nexport interface AddAccountWithTokenPayload {\r\n  /** UUID of the user being added — must match the user the session token was issued for. */\r\n  accountUuid: string;\r\n  /** Short-lived session token returned by the handoff exchange (F1). */\r\n  sessionToken: string;\r\n}\r\n\r\n/**\r\n * Sharing protocol — iframe → host: cookie written and account appended.\r\n */\r\nexport interface AddAccountWithTokenOkPayload {\r\n  accountUuid: string;\r\n}\r\n\r\n/**\r\n * Sharing protocol — iframe → host: redemption failed.\r\n *\r\n * `reason` is one of:\r\n * - `http_401` — session token invalid / expired (auth-service rejected)\r\n * - `http_4xx` / `http_5xx` — generic upstream error\r\n * - `network` — fetch to iframe's own backend failed\r\n * - `mismatch` — session token's user didn't match `accountUuid`\r\n */\r\nexport interface AddAccountWithTokenFailedPayload {\r\n  reason: string;\r\n  message?: string;\r\n}\r\n\r\n/**\r\n * Prep–Sign–Promote (v1.4) — iframe → host: signature captured.\r\n * Fires from the peer's /sharing/embed/{slug}/{prep_id} surface after a\r\n * successful POST to .../submit. The orchestrator's <PrepEmbed> listens\r\n * for this to chain into Sharing::shareUser + Sharing::promote.\r\n */\r\nexport interface PrepSignedPayload {\r\n  prep_id: string;\r\n}\r\n\r\n/**\r\n * Prep–Sign–Promote (v1.4) — iframe → host: signature rejected.\r\n * `error` mirrors the JSON body's `error` field from /submit (e.g.\r\n * `handler_rejected`, `invalid_state`, `not_found`).\r\n */\r\nexport interface PrepSignFailedPayload {\r\n  prep_id: string;\r\n  error: string;\r\n  message?: string;\r\n}\r\n","import { MESSAGE_TYPES } from '../lib/message-protocol';\r\nimport type {\r\n  AddAccountWithTokenFailedPayload,\r\n  AddAccountWithTokenOkPayload,\r\n  AddAccountWithTokenPayload,\r\n} from '../lib/message-protocol';\r\nimport type { PostMessageClient } from '../lib/client';\r\n\r\nexport class MultiAccountAutoAddError extends Error {\r\n  constructor(public readonly reason: string, message?: string) {\r\n    super(message ?? `multi-account auto-add failed: ${reason}`);\r\n    this.name = 'MultiAccountAutoAddError';\r\n  }\r\n}\r\n\r\nexport interface SwitcherApi {\r\n  addAccount: () => Promise<void>;\r\n  switchAccount: (uuid: string) => Promise<void>;\r\n}\r\n\r\nexport interface MultiAccountAutoAddOptions {\r\n  accountUuid: string;\r\n  sessionToken: string;\r\n  switcher: SwitcherApi;\r\n  /** The live PostMessageClient (typically from AccountSwitcherProvider context). */\r\n  client: Pick<PostMessageClient, 'on' | 'off'>;\r\n  /**\r\n   * Function that posts a typed message to the iframe. Defaults to the\r\n   * private `sendMessage` of PostMessageClient — injected here for unit\r\n   * testability since the real method is private.\r\n   */\r\n  sendMessage: (type: string, payload: unknown) => void;\r\n  /** Override the default 5000ms ack timeout. */\r\n  timeoutMs?: number;\r\n}\r\n\r\nconst DEFAULT_TIMEOUT_MS = 5_000;\r\n\r\n/**\r\n * Push a pre-authenticated account into the destination's account switcher.\r\n *\r\n * 1. Sends ADD_ACCOUNT_WITH_TOKEN over postMessage\r\n * 2. Awaits ADD_ACCOUNT_WITH_TOKEN_OK / _FAILED (timeout-guarded)\r\n * 3. On OK, calls switcher.switchAccount(accountUuid)\r\n * 4. On FAILED or timeout, throws MultiAccountAutoAddError\r\n *\r\n * Used by HandoffLanding (F5). The iframe-side handler is the A6 work.\r\n */\r\nexport async function multiAccountAutoAdd(\r\n  options: MultiAccountAutoAddOptions,\r\n): Promise<void> {\r\n  const { accountUuid, sessionToken, switcher, client, sendMessage } = options;\r\n  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\r\n\r\n  const payload: AddAccountWithTokenPayload = { accountUuid, sessionToken };\r\n\r\n  await new Promise<void>((resolve, reject) => {\r\n    let settled = false;\r\n    let timer: ReturnType<typeof setTimeout> | null = null;\r\n\r\n    const cleanup = () => {\r\n      client.off(MESSAGE_TYPES.ADD_ACCOUNT_WITH_TOKEN_OK, onOk);\r\n      client.off(MESSAGE_TYPES.ADD_ACCOUNT_WITH_TOKEN_FAILED, onFailed);\r\n      if (timer) clearTimeout(timer);\r\n    };\r\n\r\n    const settle = (fn: () => void) => {\r\n      if (settled) return;\r\n      settled = true;\r\n      cleanup();\r\n      fn();\r\n    };\r\n\r\n    const onOk = (msg: { payload?: AddAccountWithTokenOkPayload }) => {\r\n      const ack = msg?.payload;\r\n      if (!ack || ack.accountUuid !== accountUuid) {\r\n        settle(() => reject(new MultiAccountAutoAddError(\r\n          'mismatch',\r\n          `ack uuid ${ack?.accountUuid ?? 'undefined'} != requested ${accountUuid}`,\r\n        )));\r\n        return;\r\n      }\r\n      settle(() => resolve());\r\n    };\r\n\r\n    const onFailed = (msg: { payload?: AddAccountWithTokenFailedPayload }) => {\r\n      const fail = msg?.payload;\r\n      settle(() => reject(new MultiAccountAutoAddError(\r\n        fail?.reason ?? 'unknown',\r\n        fail?.message,\r\n      )));\r\n    };\r\n\r\n    client.on(MESSAGE_TYPES.ADD_ACCOUNT_WITH_TOKEN_OK, onOk);\r\n    client.on(MESSAGE_TYPES.ADD_ACCOUNT_WITH_TOKEN_FAILED, onFailed);\r\n\r\n    timer = setTimeout(() => {\r\n      settle(() => reject(new MultiAccountAutoAddError(\r\n        'timeout',\r\n        `no ack within ${timeoutMs}ms`,\r\n      )));\r\n    }, timeoutMs);\r\n\r\n    sendMessage(MESSAGE_TYPES.ADD_ACCOUNT_WITH_TOKEN, payload);\r\n  });\r\n\r\n  await switcher.switchAccount(accountUuid);\r\n}\r\n","'use client';\nimport { useEffect, useRef, useState } from 'react';\nimport { useRouter, useSearchParams } from 'next/navigation';\nimport { useAccountSwitcher } from '../hooks/useAccountSwitcher';\nimport { multiAccountAutoAdd, MultiAccountAutoAddError } from './multiAccountAutoAdd';\n\nexport interface HandoffLandingProps {\n  /** Override the splash text. */\n  splash?: string;\n  /** Where to send the user if the URL is missing required params. Default `/login`. */\n  loginPath?: string;\n  /**\n   * How long to wait for `<AccountSwitcherProvider>` to initialize its\n   * PostMessage client AND complete the iframe handshake before giving up with\n   * `handoff_provider_missing`.\n   *\n   * The provider creates the client in a mount effect (which runs *after* this\n   * child mounts) and the iframe handshake is asynchronous, so HandoffLanding\n   * must poll for readiness rather than bail on first render. Default 8000ms.\n   */\n  providerReadyTimeoutMs?: number;\n}\n\n/**\n * The page consumers drop at `app/auth/handoff/landing/page.tsx`:\n *\n * ```tsx\n * 'use client';\n * import { HandoffLanding } from '@benbraide/auth-service-nextjs/sharing';\n * export default function Page() { return <HandoffLanding />; }\n * ```\n *\n * Reads `account_uuid`, `session_token`, `next`, `toast` from the URL (set by\n * the F2 route handler), waits for the provider's PostMessage client to be\n * ready (iframe registered + handshake complete), runs `multiAccountAutoAdd`,\n * then redirects to `${next}?_toast=${toast}` so the arriving page's\n * `HandoffArrivalToast` can show the banner.\n *\n * Requirements on the consumer:\n * - This page must be rendered inside `<AccountSwitcherProvider>`.\n * - An `<AccountSwitcher>` iframe must be mounted somewhere in that tree so the\n *   provider's client has a registered iframe + completed handshake to post to.\n */\nexport function HandoffLanding({\n  splash = 'Setting up your account…',\n  loginPath = '/login',\n  providerReadyTimeoutMs = 8000,\n}: HandoffLandingProps) {\n  const router = useRouter();\n  const params = useSearchParams();\n  const switcher = useAccountSwitcher();\n  const fired = useRef(false);\n  const startedAtRef = useRef<number | null>(null);\n  const [error, setError] = useState<string | null>(null);\n\n  // Keep the latest switcher in a ref so the poll loop always reads the live\n  // client without re-arming the loop on every provider re-render.\n  const switcherRef = useRef(switcher);\n  switcherRef.current = switcher;\n\n  useEffect(() => {\n    if (fired.current) return;\n\n    const accountUuid = params?.get('account_uuid');\n    const sessionToken = params?.get('session_token');\n    const next = params?.get('next') ?? '/';\n    const toast = params?.get('toast') ?? '';\n\n    if (!accountUuid || !sessionToken) {\n      fired.current = true;\n      router.replace(`${loginPath}?error=handoff_missing_params`);\n      return;\n    }\n\n    if (startedAtRef.current === null) startedAtRef.current = Date.now();\n    let cancelled = false;\n    let timer: ReturnType<typeof setTimeout> | null = null;\n\n    const runAutoAdd = () => {\n      fired.current = true;\n      const current = switcherRef.current;\n      void (async () => {\n        try {\n          await multiAccountAutoAdd({\n            accountUuid,\n            sessionToken,\n            switcher: {\n              addAccount: current.addAccount,\n              switchAccount: current.switchAccount,\n            },\n            client: current.client!,\n            sendMessage: current.sendMessage,\n          });\n          const dest = toast\n            ? `${next}${next.includes('?') ? '&' : '?'}_toast=${encodeURIComponent(toast)}`\n            : next;\n          router.replace(dest);\n        } catch (err) {\n          const reason = err instanceof MultiAccountAutoAddError ? err.reason : 'unknown';\n          setError(reason);\n          router.replace(`${loginPath}?error=handoff_${reason}`);\n        }\n      })();\n    };\n\n    // Poll for the provider's client + completed iframe handshake. Fires the\n    // auto-add the moment it's ready, or bails after the bounded window so the\n    // user never sits on the splash forever.\n    const tick = () => {\n      if (cancelled || fired.current) return;\n\n      const current = switcherRef.current;\n      const ready = !!current.client && current.client.isReady() && !!current.sendMessage;\n      if (ready) {\n        runAutoAdd();\n        return;\n      }\n\n      const elapsed = Date.now() - (startedAtRef.current ?? Date.now());\n      if (elapsed >= providerReadyTimeoutMs) {\n        fired.current = true;\n        setError('provider_missing');\n        router.replace(`${loginPath}?error=handoff_provider_missing`);\n        return;\n      }\n\n      timer = setTimeout(tick, 150);\n    };\n\n    tick();\n\n    return () => {\n      cancelled = true;\n      if (timer) clearTimeout(timer);\n    };\n    // `switcher` intentionally omitted — the loop reads it via switcherRef so it\n    // doesn't re-arm on every provider re-render.\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [params, router, loginPath, providerReadyTimeoutMs]);\n\n  return (\n    <div\n      style={{\n        minHeight: '60vh',\n        display: 'flex',\n        alignItems: 'center',\n        justifyContent: 'center',\n        fontFamily: 'system-ui, sans-serif',\n        color: '#444',\n      }}\n      role=\"status\"\n      aria-live=\"polite\"\n    >\n      {error ? `Sign-in failed (${error})…` : splash}\n    </div>\n  );\n}\n","'use client';\r\n\r\nimport React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';\r\nimport type {\r\n  Session,\r\n  AccountSwitcherConfig,\r\n  AccountSwitcherContextValue,\r\n  ConfirmPayload,\r\n  PromptPayload,\r\n  AlertPayload,\r\n  UrlRedirectPayload,\r\n} from '../lib/types';\r\nimport { PostMessageClient } from '../lib/client';\r\nimport { MESSAGE_TYPES } from '../lib/message-protocol';\r\nimport { DialogManager } from '../lib/dialogs';\r\n\r\n// Cross-bundle singleton context.\r\n//\r\n// tsup builds this package as multiple entry points (index, components, hooks,\r\n// sharing) with `splitting: false`, which DUPLICATES this module — and its\r\n// `createContext()` call — into every entry bundle. That yields distinct\r\n// Context objects, so a Provider imported from one entry (e.g. the root) and a\r\n// consumer imported from another (e.g. `/sharing`'s HandoffLanding) never\r\n// match, and the consumer reads `null`. Anchoring the Context on `globalThis`\r\n// behind a stable `Symbol.for()` key guarantees exactly ONE shared Context\r\n// object across all bundles at runtime. See tsup.config.ts.\r\nconst CONTEXT_KEY = Symbol.for('@benbraide/auth-service-nextjs/AccountSwitcherContext');\r\ntype AccountSwitcherReactContext = React.Context<AccountSwitcherContextValue | null>;\r\nconst contextRegistry = globalThis as unknown as Record<symbol, AccountSwitcherReactContext | undefined>;\r\nconst AccountSwitcherContext: AccountSwitcherReactContext =\r\n  contextRegistry[CONTEXT_KEY] ??\r\n  (contextRegistry[CONTEXT_KEY] = createContext<AccountSwitcherContextValue | null>(null));\r\n\r\nexport interface AccountSwitcherProviderProps extends AccountSwitcherConfig {\r\n  children: React.ReactNode;\r\n}\r\n\r\n/**\r\n * Provider component for account switcher functionality\r\n * Wraps your app to provide session state and switcher methods\r\n */\r\nexport function AccountSwitcherProvider({\r\n  children,\r\n  backendUrl,\r\n  authServiceUrl,\r\n  serviceApiKey,\r\n  onSessionChange,\r\n  onAccountChange,\r\n  onError,\r\n  debug = false,\r\n  dialogs,\r\n  onConfirmRequest,\r\n  onPromptRequest,\r\n  onAlertRequest,\r\n  autoResize = true,\r\n  minHeight = 200,\r\n  maxHeight = null,\r\n  onResize,\r\n  onNavigate,\r\n  onNavigateToAddAccount,\r\n  onNavigateToManageAccount,\r\n  onNavigateToAvatarUpdate,\r\n}: AccountSwitcherProviderProps) {\r\n  const [session, setSession] = useState<Session>({\r\n    isAuthenticated: false,\r\n    currentUser: null,\r\n    accounts: [],\r\n  });\r\n  const [isLoading, setIsLoading] = useState(false);\r\n  const [error, setError] = useState<Error | null>(null);\r\n  // State mirror of clientRef so the context value updates (and consumers such\r\n  // as HandoffLanding re-render) once the PostMessage client is initialized in\r\n  // the mount effect below.\r\n  const [activeClient, setActiveClient] = useState<PostMessageClient | null>(null);\r\n  const clientRef = useRef<PostMessageClient | null>(null);\r\n  const dialogManagerRef = useRef<DialogManager | null>(null);\r\n\r\n  // Use refs for callbacks to avoid recreating PostMessageClient on every prop change\r\n  const debugRef = useRef(debug);\r\n  const onSessionChangeRef = useRef(onSessionChange);\r\n  const onAccountChangeRef = useRef(onAccountChange);\r\n  const onErrorRef = useRef(onError);\r\n  const onConfirmRequestRef = useRef(onConfirmRequest);\r\n  const onPromptRequestRef = useRef(onPromptRequest);\r\n  const onAlertRequestRef = useRef(onAlertRequest);\r\n  const onNavigateRef = useRef(onNavigate);\r\n  const onNavigateToAddAccountRef = useRef(onNavigateToAddAccount);\r\n  const onNavigateToManageAccountRef = useRef(onNavigateToManageAccount);\r\n  const onNavigateToAvatarUpdateRef = useRef(onNavigateToAvatarUpdate);\r\n\r\n  // Update refs whenever callbacks change\r\n  useEffect(() => {\r\n    debugRef.current = debug;\r\n    onSessionChangeRef.current = onSessionChange;\r\n    onAccountChangeRef.current = onAccountChange;\r\n    onErrorRef.current = onError;\r\n    onConfirmRequestRef.current = onConfirmRequest;\r\n    onPromptRequestRef.current = onPromptRequest;\r\n    onAlertRequestRef.current = onAlertRequest;\r\n    onNavigateRef.current = onNavigate;\r\n    onNavigateToAddAccountRef.current = onNavigateToAddAccount;\r\n    onNavigateToManageAccountRef.current = onNavigateToManageAccount;\r\n    onNavigateToAvatarUpdateRef.current = onNavigateToAvatarUpdate;\r\n  });\r\n\r\n  // Initialize PostMessage client\r\n  useEffect(() => {\r\n    if (typeof window === 'undefined') return;\r\n\r\n    // Initialize dialog manager if dialogs are enabled\r\n    if (dialogs?.enabled !== false) {\r\n      const dialogManager = new DialogManager(dialogs || {});\r\n      dialogManager.injectDialogStyles();\r\n      dialogManagerRef.current = dialogManager;\r\n    }\r\n\r\n    const config: AccountSwitcherConfig = {\r\n      backendUrl,\r\n      authServiceUrl,\r\n      serviceApiKey,\r\n      onSessionChange,\r\n      onAccountChange,\r\n      onError,\r\n      debug,\r\n      dialogs,\r\n      onConfirmRequest,\r\n      onPromptRequest,\r\n      onAlertRequest,\r\n      autoResize,\r\n      minHeight,\r\n      maxHeight,\r\n      onResize,\r\n    };\r\n\r\n    const client = new PostMessageClient(config);\r\n    clientRef.current = client;\r\n    // Publish into state so the context value exposes a live `client` to\r\n    // consumers (e.g. the cross-product handoff) once it's ready.\r\n    setActiveClient(client);\r\n\r\n    // Set up session change listener\r\n    client.on(MESSAGE_TYPES.SESSION_CHANGED, (data: any) => {\r\n      const sessionData = data.payload || data;\r\n      const newSession: Session = {\r\n        isAuthenticated: sessionData.isAuthenticated || false,\r\n        currentUser: sessionData.currentUser || null,\r\n        accounts: sessionData.accounts || [],\r\n      };\r\n\r\n      setSession(newSession);\r\n      onSessionChangeRef.current?.(newSession);\r\n    });\r\n\r\n    // Set up account change listener\r\n    client.on(MESSAGE_TYPES.ACCOUNT_SWITCHED, (data: any) => {\r\n      const payload = data.payload || data;\r\n\r\n      // ACCOUNT_SWITCHED only sends { user_uuid }, not the full user object\r\n      // Find the full user object from the accounts array\r\n      setSession((prevSession) => {\r\n        const switchedUser = prevSession.accounts.find(\r\n          (account) => account.uuid === payload.user_uuid\r\n        );\r\n\r\n        if (switchedUser) {\r\n          // Update current user with the full user object from accounts\r\n          const newSession = {\r\n            ...prevSession,\r\n            currentUser: switchedUser,\r\n          };\r\n\r\n          // Call the callback with the full user object\r\n          onAccountChangeRef.current?.(switchedUser);\r\n\r\n          return newSession;\r\n        }\r\n\r\n        // If user not found in accounts, keep previous session\r\n        if (debug) {\r\n          console.warn('[AccountSwitcher] Switched user not found in accounts:', payload.user_uuid);\r\n        }\r\n        return prevSession;\r\n      });\r\n    });\r\n\r\n    // Set up error listener\r\n    client.on(MESSAGE_TYPES.ERROR, (data: any) => {\r\n      const err = data.payload || data;\r\n      const error = new Error(err.message || 'Unknown error');\r\n      setError(error);\r\n      onErrorRef.current?.(error);\r\n    });\r\n\r\n    // NOTE: SIZE_CHANGED is now handled directly in AccountSwitcher component\r\n    // using direct DOM manipulation to avoid re-renders. If you need resize notifications,\r\n    // use the onHeightChange prop on the AccountSwitcher component instead.\r\n\r\n    // Set up navigation handlers\r\n    client.on(MESSAGE_TYPES.ADD_ACCOUNT, (data: any) => {\r\n      const payload = data.payload || data;\r\n      if (payload.landing_url) {\r\n        if (onNavigateToAddAccountRef.current) {\r\n          onNavigateToAddAccountRef.current(payload.landing_url);\r\n        } else {\r\n          window.location.href = payload.landing_url;\r\n        }\r\n      }\r\n    });\r\n\r\n    client.on(MESSAGE_TYPES.MANAGE_ACCOUNT, (data: any) => {\r\n      const payload = data.payload || data;\r\n      if (payload.management_url) {\r\n        if (onNavigateToManageAccountRef.current) {\r\n          onNavigateToManageAccountRef.current(payload.management_url);\r\n        } else {\r\n          window.location.href = payload.management_url;\r\n        }\r\n      }\r\n    });\r\n\r\n    client.on(MESSAGE_TYPES.UPDATE_AVATAR, (data: any) => {\r\n      const payload = data.payload || data;\r\n      if (payload.avatar_url) {\r\n        if (onNavigateToAvatarUpdateRef.current) {\r\n          onNavigateToAvatarUpdateRef.current(payload.avatar_url);\r\n        } else {\r\n          window.location.href = payload.avatar_url;\r\n        }\r\n      }\r\n    });\r\n\r\n    // Set up unified URL redirect handler (Unified Action Protocol)\r\n    client.on(MESSAGE_TYPES.URL_REDIRECT, (data: any) => {\r\n      const payload = data.payload || data;\r\n      const redirectPayload: UrlRedirectPayload = {\r\n        url: payload.url,\r\n        reason: payload.reason,\r\n        openInNewTab: payload.openInNewTab || false,\r\n      };\r\n\r\n      if (onNavigateRef.current) {\r\n        // Use new unified navigation handler\r\n        onNavigateRef.current(redirectPayload);\r\n      } else {\r\n        // Fallback to legacy handlers or default behavior\r\n        const { url, reason, openInNewTab } = redirectPayload;\r\n\r\n        // Try legacy handlers first based on reason\r\n        if (reason === 'add-account' && onNavigateToAddAccountRef.current) {\r\n          onNavigateToAddAccountRef.current(url);\r\n        } else if (reason === 'manage-account' && onNavigateToManageAccountRef.current) {\r\n          onNavigateToManageAccountRef.current(url);\r\n        } else if (reason === 'update-avatar' && onNavigateToAvatarUpdateRef.current) {\r\n          onNavigateToAvatarUpdateRef.current(url);\r\n        } else {\r\n          // Default navigation behavior\r\n          if (openInNewTab) {\r\n            window.open(url, '_blank');\r\n          } else {\r\n            window.location.href = url;\r\n          }\r\n        }\r\n      }\r\n    });\r\n\r\n    // Set up dialog handlers\r\n    if (dialogs?.enabled !== false) {\r\n      // Confirm dialog\r\n      client.on(MESSAGE_TYPES.CONFIRM_REQUEST, async (data: any) => {\r\n        if (debug) {\r\n          console.log('[AccountSwitcher] CONFIRM_REQUEST received:', data);\r\n        }\r\n        const { requestId, payload } = data;\r\n\r\n        try {\r\n          let result: boolean;\r\n\r\n          if (onConfirmRequestRef.current) {\r\n            // Use custom handler if provided\r\n            result = await onConfirmRequestRef.current(payload as ConfirmPayload);\r\n          } else if (dialogManagerRef.current) {\r\n            // Use custom dialog manager\r\n            result = await dialogManagerRef.current.showConfirm(payload as ConfirmPayload);\r\n          } else {\r\n            // Fallback to browser confirm\r\n            result = window.confirm(payload.message);\r\n          }\r\n\r\n          client.sendDialogResponse(MESSAGE_TYPES.REQUEST_RESPONSE, requestId, {\r\n            confirmed: result,\r\n          });\r\n        } catch (err) {\r\n          client.sendDialogResponse(MESSAGE_TYPES.REQUEST_RESPONSE, requestId, {\r\n            confirmed: false,\r\n          });\r\n        }\r\n      });\r\n\r\n      // Prompt dialog\r\n      client.on(MESSAGE_TYPES.PROMPT_REQUEST, async (data: any) => {\r\n        const { requestId, payload } = data;\r\n\r\n        try {\r\n          let value: string | null = null;\r\n          let cancelled = true;\r\n\r\n          if (onPromptRequestRef.current) {\r\n            // Use custom handler if provided\r\n            const customResult = await onPromptRequestRef.current(payload as PromptPayload);\r\n            value = customResult;\r\n            cancelled = customResult === null;\r\n          } else if (dialogManagerRef.current) {\r\n            // Use custom dialog manager\r\n            const result = await dialogManagerRef.current.showPrompt(payload as PromptPayload);\r\n            value = result.value;\r\n            cancelled = result.cancelled;\r\n          } else {\r\n            // Fallback to browser prompt\r\n            const browserResult = window.prompt(payload.message, payload.defaultValue || '');\r\n            value = browserResult;\r\n            cancelled = browserResult === null;\r\n          }\r\n\r\n          client.sendDialogResponse(MESSAGE_TYPES.REQUEST_RESPONSE, requestId, {\r\n            value,\r\n            cancelled,\r\n          });\r\n        } catch (err) {\r\n          client.sendDialogResponse(MESSAGE_TYPES.REQUEST_RESPONSE, requestId, {\r\n            value: null,\r\n            cancelled: true,\r\n          });\r\n        }\r\n      });\r\n\r\n      // Alert dialog\r\n      client.on(MESSAGE_TYPES.ALERT_REQUEST, async (data: any) => {\r\n        const { requestId, payload } = data;\r\n\r\n        try {\r\n          if (onAlertRequestRef.current) {\r\n            // Use custom handler if provided\r\n            await onAlertRequestRef.current(payload as AlertPayload);\r\n          } else if (dialogManagerRef.current) {\r\n            // Use custom dialog manager\r\n            await dialogManagerRef.current.showAlert(payload as AlertPayload);\r\n          } else {\r\n            // Fallback to browser alert\r\n            window.alert(payload.message);\r\n          }\r\n\r\n          client.sendDialogResponse(MESSAGE_TYPES.REQUEST_RESPONSE, requestId, {\r\n            acknowledged: true,\r\n          });\r\n        } catch (err) {\r\n          client.sendDialogResponse(MESSAGE_TYPES.REQUEST_RESPONSE, requestId, {\r\n            acknowledged: false,\r\n          });\r\n        }\r\n      });\r\n    }\r\n\r\n    return () => {\r\n      client.destroy();\r\n      clientRef.current = null;\r\n      setActiveClient(null);\r\n    };\r\n  }, []);\r\n\r\n  const switchAccount = useCallback(async (userUuid: string) => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    setIsLoading(true);\r\n    setError(null);\r\n\r\n    try {\r\n      await clientRef.current.switchAccount(userUuid);\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  }, []);\r\n\r\n  const addAccount = useCallback(async () => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    try {\r\n      await clientRef.current.addAccount();\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    }\r\n  }, []);\r\n\r\n  const manageAccount = useCallback(async () => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    try {\r\n      await clientRef.current.manageAccount();\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    }\r\n  }, []);\r\n\r\n  const updateAvatar = useCallback(async () => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    try {\r\n      await clientRef.current.updateAvatar();\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    }\r\n  }, []);\r\n\r\n  const logoutAccount = useCallback(async (userUuid: string) => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    setIsLoading(true);\r\n    setError(null);\r\n\r\n    try {\r\n      await clientRef.current.logoutAccount(userUuid);\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  }, []);\r\n\r\n  const logoutAll = useCallback(async () => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    setIsLoading(true);\r\n    setError(null);\r\n\r\n    try {\r\n      await clientRef.current.logoutAll();\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  }, []);\r\n\r\n  const refresh = useCallback(async () => {\r\n    if (!clientRef.current) throw new Error('Client not initialized');\r\n\r\n    setIsLoading(true);\r\n    setError(null);\r\n\r\n    try {\r\n      const sessionData = await clientRef.current.getSession();\r\n      setSession(sessionData);\r\n    } catch (err) {\r\n      const error = err as Error;\r\n      setError(error);\r\n      throw error;\r\n    } finally {\r\n      setIsLoading(false);\r\n    }\r\n  }, []);\r\n\r\n  const registerIframe = useCallback((iframe: HTMLIFrameElement, origin: string) => {\r\n    if (!clientRef.current) {\r\n      console.warn('[AccountSwitcherProvider] Client not initialized, cannot register iframe');\r\n      return;\r\n    }\r\n\r\n    clientRef.current.setIframe(iframe, origin);\r\n\r\n    // Fetch initial session after iframe is registered\r\n    // Wait a bit for the iframe to be ready and handshake to complete\r\n    setTimeout(async () => {\r\n      try {\r\n        setIsLoading(true);\r\n        const sessionData = await clientRef.current!.getSession();\r\n        setSession(sessionData);\r\n      } catch (err) {\r\n        if (debugRef.current) {\r\n          console.error('[AccountSwitcherProvider] Failed to fetch initial session:', err);\r\n        }\r\n      } finally {\r\n        setIsLoading(false);\r\n      }\r\n    }, 1000); // Give the iframe time to complete handshake\r\n  }, []); // No dependencies - uses only refs and setState\r\n\r\n  const sendMessage = useCallback((type: string, payload?: unknown) => {\r\n    if (!clientRef.current) {\r\n      console.warn('[AccountSwitcherProvider] Client not initialized, cannot send message:', type);\r\n      return;\r\n    }\r\n    clientRef.current.send(type, payload);\r\n  }, []);\r\n\r\n  const value: AccountSwitcherContextValue = {\r\n    session,\r\n    isLoading,\r\n    error,\r\n    switchAccount,\r\n    addAccount,\r\n    manageAccount,\r\n    updateAvatar,\r\n    logoutAccount,\r\n    logoutAll,\r\n    refresh,\r\n    registerIframe,\r\n    client: activeClient,\r\n    sendMessage,\r\n  };\r\n\r\n  return (\r\n    <AccountSwitcherContext.Provider value={value}>\r\n      {children}\r\n    </AccountSwitcherContext.Provider>\r\n  );\r\n}\r\n\r\n/**\r\n * Hook to access the account switcher context\r\n * @throws Error if used outside of AccountSwitcherProvider\r\n */\r\nexport function useAccountSwitcherContext() {\r\n  const context = useContext(AccountSwitcherContext);\r\n\r\n  if (!context) {\r\n    throw new Error(\r\n      'useAccountSwitcherContext must be used within AccountSwitcherProvider'\r\n    );\r\n  }\r\n\r\n  return context;\r\n}\r\n","import { useAccountSwitcherContext } from '../components/AccountSwitcherProvider';\r\n\r\n/**\r\n * Hook to access account switcher methods\r\n * Returns methods for switching accounts, adding accounts, managing account, updating avatar, and logging out\r\n */\r\nexport function useAccountSwitcher() {\r\n  const {\r\n    switchAccount,\r\n    addAccount,\r\n    manageAccount,\r\n    updateAvatar,\r\n    logoutAccount,\r\n    logoutAll,\r\n    refresh,\r\n    client,\r\n    sendMessage,\r\n  } = useAccountSwitcherContext();\r\n\r\n  return {\r\n    switchAccount,\r\n    addAccount,\r\n    manageAccount,\r\n    updateAvatar,\r\n    logoutAccount,\r\n    logoutAll,\r\n    refresh,\r\n    // The live PostMessage client + sender, needed by the cross-product handoff\r\n    // (HandoffLanding → multiAccountAutoAdd). `client` is null until the\r\n    // provider's mount effect initializes it.\r\n    client,\r\n    sendMessage,\r\n  };\r\n}\r\n","'use client';\nimport { useEffect, useRef } from 'react';\n\nexport interface PrepResultLike {\n  prepId: string;\n  embedUrl: string;\n  expiresAt?: string | null;\n  status: string;\n}\n\nexport interface PrepEmbedSignedEvent { prepId: string; }\nexport interface PrepEmbedFailedEvent { prepId: string; error: string; message?: string; }\n\nexport interface PrepEmbedProps {\n  prepResult: PrepResultLike;\n  onSigned?: (e: PrepEmbedSignedEvent) => void;\n  onFailed?: (e: PrepEmbedFailedEvent) => void;\n  /** Optional CSS height. Defaults to 600px. */\n  height?: string | number;\n  /** Optional CSS width. Defaults to 100%. */\n  width?: string | number;\n  /** Override the iframe `title` attr for a11y. Defaults to \"Signing surface\". */\n  title?: string;\n  /** Restrict accepted postMessage origins. Defaults to the embed URL's origin. */\n  expectedOrigin?: string;\n}\n\n/**\n * Drop-in iframe wrapper for the orchestrator side of Prep–Sign–Promote.\n * Renders the peer's signing iframe + listens for the prep-signed /\n * prep-sign-failed messages.\n *\n * ```tsx\n * const prep = await fetch('/api/prepare-portify-agreement').then(r => r.json());\n * return <PrepEmbed prepResult={prep} onSigned={({prepId}) => promote(prepId)} />;\n * ```\n *\n * Pair with `Sharing::prepare` on the PHP orchestrator. On `onSigned`, the\n * orchestrator's flow should: (1) take payment, (2) `Sharing::shareUser`\n * to create the auth-service share, (3) `Sharing::promote` to flip the\n * peer's temp record into a permanent record.\n */\nexport function PrepEmbed({\n  prepResult,\n  onSigned,\n  onFailed,\n  height = 600,\n  width = '100%',\n  title = 'Signing surface',\n  expectedOrigin,\n}: PrepEmbedProps) {\n  const iframeRef = useRef<HTMLIFrameElement | null>(null);\n  // Use a ref (not state) for the latch — refs update synchronously,\n  // state updates are async and would let duplicate postMessage events\n  // double-fire callbacks before the state batch flushes.\n  const completedRef = useRef(false);\n\n  const computedOrigin = (() => {\n    try {\n      return expectedOrigin ?? new URL(prepResult.embedUrl).origin;\n    } catch {\n      return expectedOrigin;\n    }\n  })();\n\n  useEffect(() => {\n    function onMessage(ev: MessageEvent) {\n      if (completedRef.current) return;\n      if (computedOrigin && ev.origin !== computedOrigin) return;\n      const data = ev.data;\n      if (!data || typeof data !== 'object') return;\n      if (data.prep_id !== prepResult.prepId) return;\n\n      if (data.type === 'prep-signed') {\n        completedRef.current = true;\n        onSigned?.({ prepId: prepResult.prepId });\n      } else if (data.type === 'prep-sign-failed') {\n        completedRef.current = true;\n        onFailed?.({\n          prepId: prepResult.prepId,\n          error: typeof data.error === 'string' ? data.error : 'unknown',\n          message: typeof data.message === 'string' ? data.message : undefined,\n        });\n      }\n    }\n    window.addEventListener('message', onMessage);\n    return () => window.removeEventListener('message', onMessage);\n  }, [prepResult.prepId, computedOrigin, onSigned, onFailed]);\n\n  return (\n    <iframe\n      ref={iframeRef}\n      src={prepResult.embedUrl}\n      title={title}\n      style={{\n        width: typeof width === 'number' ? `${width}px` : width,\n        height: typeof height === 'number' ? `${height}px` : height,\n        border: 0,\n      }}\n      sandbox=\"allow-scripts allow-same-origin allow-forms\"\n    />\n  );\n}\n"],"mappings":"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","names":["DEFAULT_TIMEOUT_MS","useEffect","useRef","useState","jsx","useRef","useState","useEffect","useEffect","useRef","jsx"]}