{"version":3,"sources":["../../src/direct/connect-flow.ts"],"sourcesContent":["/**\n * Framework-agnostic connect-flow state machine for the browser two-tab helper.\n *\n * @remarks\n * This is the testable core behind {@link useDirectVanaConnect}. It is pure\n * TypeScript (no React, no DOM-only APIs beyond an injectable window opener and\n * timers) so the full flow — create request, open Vana, poll status, read data —\n * can be exercised in a Node test environment.\n *\n * The React hook is a thin `useSyncExternalStore` binding over this store.\n *\n * @category Direct\n * @module direct/connect-flow\n */\n\nimport type {\n  AccessRequest,\n  AccessRequestStatus,\n  AccessRequestStatusValue,\n  ApprovedDataResult,\n} from \"./types\";\nimport { normalizeMobileContinuationUrl } from \"./types\";\n\n/**\n * Caller-supplied transports. These typically `fetch` the app's own backend\n * routes, which in turn delegate to a {@link DirectDataController}.\n */\nexport interface DirectConnectTransports<T = unknown> {\n  /** Ask the backend to create an access request. */\n  createRequest: () => Promise<AccessRequest>;\n  /** Ask the backend for the current status of a request. */\n  getStatus: (requestId: string) => Promise<AccessRequestStatus>;\n  /** Ask the backend to read the approved data. */\n  readResult: (requestId: string) => Promise<ApprovedDataResult<T>>;\n}\n\n/**\n * A handle to a tab opened synchronously under the user's click gesture.\n *\n * @remarks\n * The flow opens this tab *before* it knows the approval URL (popup blockers\n * only allow `window.open()` during the click's transient activation), then\n * navigates it once `createRequest` resolves.\n */\nexport interface ConnectWindow {\n  /** Point the already-open tab at the approval URL. */\n  navigate(url: string): void;\n  /** Close the tab (used to clean up an un-navigated tab on failure/reset). */\n  close(): void;\n}\n\n/** Browser class used only to choose the destination returned by Vana. */\nexport type DirectBrowserPlatform = \"desktop\" | \"mobile\";\n\n/** Injectable browser-platform policy; it never asserts whether an app exists. */\nexport interface DirectBrowserPlatformPolicy {\n  current(): DirectBrowserPlatform;\n}\n\n/** Tunables for the connect flow. */\nexport interface DirectConnectOptions {\n  /** Status poll interval in ms. Defaults to 1500. */\n  pollIntervalMs?: number;\n  /**\n   * Overall timeout in ms before giving up. Defaults to 300000 (5 min).\n   * Used only when the access request does not carry an authoritative\n   * `expiresAt` value.\n   */\n  timeoutMs?: number;\n  /**\n   * Synchronously open a blank tab under the click's transient activation and\n   * return a handle to navigate later, or `null` if the browser blocked it.\n   * Defaults to `window.open(\"\", \"_blank\")` (with `opener` severed). Injectable\n   * for tests.\n   *\n   * @remarks\n   * Renamed from the pre-3.8 `openWindow?: (url) => void`. The old contract was\n   * the BUI-622 bug itself (it was called with the URL *after* an `await`, so\n   * the popup blocker suppressed it); it cannot be preserved while fixing the\n   * bug. Custom openers must now open synchronously and return a navigable\n   * handle.\n   */\n  openApprovalWindow?: () => ConnectWindow | null;\n  /** SDK-owned mobile/desktop policy. Injectable for deterministic tests. */\n  browserPlatformPolicy?: DirectBrowserPlatformPolicy;\n  /** `setTimeout`. Injectable for tests. Defaults to `globalThis.setTimeout`. */\n  setTimeoutFn?: (cb: () => void, ms: number) => unknown;\n  /** `clearTimeout`. Injectable for tests. Defaults to `globalThis.clearTimeout`. */\n  clearTimeoutFn?: (handle: unknown) => void;\n  /** Clock source in ms. Injectable for tests. Defaults to `Date.now`. */\n  now?: () => number;\n}\n\n/**\n * Discriminated connect-flow state.\n *\n * @remarks\n * `type` matches the builder guide: it starts at `\"idle\"` and is non-idle while\n * connecting. The intermediate phases give richer UIs something to render.\n *\n * Desktop and light-data requests move through `\"awaiting_approval\"` (Vana Web\n * opens in a popup). A deep Direct request on a mobile browser moves through\n * `\"ready_to_open\"` instead: the SDK exposes a plain HTTPS\n * `mobileContinuationUrl` for the UI to render as a primary \"Open Vana\" link,\n * never launching it automatically, and keeps polling in memory.\n */\nexport type DirectConnectState<T = unknown> =\n  | { type: \"idle\" }\n  | { type: \"creating\" }\n  | {\n      type: \"awaiting_approval\";\n      request: AccessRequest;\n      /**\n       * `true` when the popup was blocked. The UI should render the universal\n       * HTTPS `request.approvalUrl` as a manual \"Open approval\" link.\n       */\n      popupBlocked: boolean;\n    }\n  | {\n      type: \"ready_to_open\";\n      request: AccessRequest;\n      /**\n       * Validated HTTPS continuation URL the mobile UI renders as the primary\n       * \"Open Vana\" tap. Polling continues while it is shown; its embedded\n       * ticket may rotate to a fresh URL between polls.\n       */\n      mobileContinuationUrl: string;\n    }\n  | { type: \"reading\"; request: AccessRequest }\n  | { type: \"done\"; result: ApprovedDataResult<T> }\n  | { type: \"error\"; error: Error };\n\n/** Whether an explicit read retry reused consent or started fresh approval. */\nexport type DirectConnectRetryOutcome =\n  | \"retried_existing_grant\"\n  | \"fresh_approval_required\";\n\n/** The store returned by {@link createDirectConnectFlow}. */\nexport interface DirectConnectFlow<T = unknown> {\n  /** Current state. */\n  getState(): DirectConnectState<T>;\n  /** Subscribe to state changes; returns an unsubscribe function. */\n  subscribe(listener: () => void): () => void;\n  /** Begin the flow. No-op if already running. */\n  start(): Promise<void>;\n  /**\n   * Retry a failed read, reusing a still-live approved request when possible.\n   *\n   * @remarks\n   * This explicit path avoids the observed double-approval symptom where\n   * \"Try that again\" minted a new request after a transient read failure.\n   * The return value tells callers whether existing consent was reused or a\n   * fresh approval was required.\n   */\n  retryRead(): Promise<DirectConnectRetryOutcome>;\n  /** Reset to `idle` and stop any in-flight polling. */\n  reset(): void;\n}\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\nfunction toError(value: unknown): Error {\n  return value instanceof Error ? value : new Error(String(value));\n}\n\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n  return status === \"approved\" || status === \"ready_for_read\";\n}\n\nconst MOBILE_USER_AGENT =\n  /Android|iPhone|iPad|iPod|Mobile|Silk|Kindle|Opera Mini|IEMobile/i;\n\nfunction defaultBrowserPlatformPolicy(): DirectBrowserPlatformPolicy {\n  return {\n    current() {\n      if (typeof navigator === \"undefined\") return \"desktop\";\n      const isTouchCapableIpad =\n        navigator.platform === \"MacIntel\" && navigator.maxTouchPoints > 1;\n      return MOBILE_USER_AGENT.test(navigator.userAgent) || isTouchCapableIpad\n        ? \"mobile\"\n        : \"desktop\";\n    },\n  };\n}\n\n/**\n * Default {@link DirectConnectOptions.openApprovalWindow}: open a blank tab\n * synchronously (inside the click gesture) and return a handle to navigate\n * once the approval URL is known. Returns `null` when blocked or non-DOM.\n */\nfunction defaultOpenApprovalWindow(): ConnectWindow | null {\n  if (typeof window === \"undefined\" || !window.open) return null;\n  // We can't pass the \"noopener\"/\"noreferrer\" feature string here: it makes\n  // window.open() return null, which would throw away the handle we need to\n  // navigate later. So we open plain and re-create both protections by hand.\n  const opened = window.open(\"\", \"_blank\");\n  if (!opened) return null;\n  // Sever the opener link while the tab is still about:blank, so the approval\n  // page can't reach back into the app (reverse tab-nabbing).\n  try {\n    opened.opener = null;\n  } catch {\n    // Some environments make `opener` read-only; best-effort only.\n  }\n  return {\n    navigate(url: string) {\n      // Restore the no-referrer protection the old \"noreferrer\" feature gave:\n      // tag the blank document so the upcoming navigation sends no Referer to\n      // the approval page (best-effort; the blank doc is same-origin here).\n      try {\n        const meta = opened.document.createElement(\"meta\");\n        meta.name = \"referrer\";\n        meta.content = \"no-referrer\";\n        (opened.document.head ?? opened.document.documentElement)?.appendChild(\n          meta,\n        );\n      } catch {\n        // Cross-origin/unavailable document: skip, navigation still proceeds.\n      }\n      opened.location.href = url;\n    },\n    close() {\n      opened.close();\n    },\n  };\n}\n\n/**\n * Create a connect-flow store.\n *\n * @param transports - Backend transports (`createRequest`, `getStatus`, `readResult`).\n * @param options - Polling/timeout tunables and injectable side effects.\n * @returns A {@link DirectConnectFlow} store.\n */\nexport function createDirectConnectFlow<T = unknown>(\n  transports: DirectConnectTransports<T>,\n  options: DirectConnectOptions = {},\n): DirectConnectFlow<T> {\n  const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n  // `openApprovalWindow` and `browserPlatformPolicy` are resolved lazily at\n  // start() (see below) so options swapped in after construction are still\n  // honoured — matching the latest-callback pattern the React hook uses for its\n  // transports.\n  const setTimeoutFn =\n    options.setTimeoutFn ??\n    ((cb: () => void, ms: number) => globalThis.setTimeout(cb, ms));\n  const clearTimeoutFn =\n    options.clearTimeoutFn ??\n    ((handle: unknown) => {\n      globalThis.clearTimeout(handle as never);\n    });\n  const now = options.now ?? (() => Date.now());\n  let state: DirectConnectState<T> = { type: \"idle\" };\n  const listeners = new Set<() => void>();\n  let pollHandle: unknown = null;\n  let running = false;\n  // Retained only after status proved this request had a read-ready grant.\n  // A retry rechecks that status before trusting the prior consent.\n  let approvedRequest: AccessRequest | null = null;\n  // Monotonic id for the current start() invocation. reset() (and an\n  // immediately following start()) bumps it, so a previous run whose async\n  // createRequest is still in flight can detect it has been superseded and\n  // avoid touching shared state / the newer run's tab.\n  let activeRunId = 0;\n  // Holds the tab we opened only while it is still blank (un-navigated). Once\n  // navigated to the approval URL we drop the reference so reset/cleanup never\n  // closes the live approval tab the user is interacting with.\n  let openedWindow: ConnectWindow | null = null;\n\n  function emit(): void {\n    for (const listener of listeners) listener();\n  }\n\n  function setState(next: DirectConnectState<T>): void {\n    state = next;\n    emit();\n  }\n\n  function clearPoll(): void {\n    if (pollHandle !== null) {\n      clearTimeoutFn(pollHandle);\n      pollHandle = null;\n    }\n  }\n\n  /** Close the opened tab if it is still blank (never navigated). */\n  function closeUnnavigatedWindow(): void {\n    if (openedWindow) {\n      openedWindow.close();\n      openedWindow = null;\n    }\n  }\n\n  function isRunningPhase(): boolean {\n    return (\n      state.type === \"creating\" ||\n      state.type === \"awaiting_approval\" ||\n      state.type === \"ready_to_open\" ||\n      state.type === \"reading\"\n    );\n  }\n\n  async function readAndFinish(request: AccessRequest): Promise<void> {\n    setState({ type: \"reading\", request });\n    try {\n      const result = await transports.readResult(request.requestId);\n      if (!running) return;\n      setState({ type: \"done\", result });\n    } catch (err) {\n      if (!running) return;\n      setState({ type: \"error\", error: toError(err) });\n    } finally {\n      running = false;\n    }\n  }\n\n  function scheduleNextPoll(request: AccessRequest, deadline: number): void {\n    pollHandle = setTimeoutFn(() => {\n      void poll(request, deadline);\n    }, pollIntervalMs);\n  }\n\n  function requestDeadline(request: AccessRequest): number {\n    if (request.expiresAt !== undefined) {\n      const expiresAt = Date.parse(request.expiresAt);\n      if (Number.isFinite(expiresAt)) return expiresAt;\n    }\n    return now() + timeoutMs;\n  }\n\n  /**\n   * Enter the polling loop from the given initial state (either\n   * `awaiting_approval` for desktop/light or `ready_to_open` for mobile-deep).\n   * Errors out immediately if the request has already expired.\n   */\n  function startPolling(\n    request: AccessRequest,\n    initialState: DirectConnectState<T>,\n  ): void {\n    setState(initialState);\n    const deadline = requestDeadline(request);\n    if (now() >= deadline) {\n      running = false;\n      setState({\n        type: \"error\",\n        error: new Error(\"Access request expired\"),\n      });\n      return;\n    }\n    scheduleNextPoll(request, deadline);\n  }\n\n  async function poll(request: AccessRequest, deadline: number): Promise<void> {\n    if (!running) return;\n    if (now() >= deadline) {\n      running = false;\n      setState({\n        type: \"error\",\n        error: new Error(\"Timed out waiting for approval\"),\n      });\n      return;\n    }\n    let status: AccessRequestStatus;\n    try {\n      status = await transports.getStatus(request.requestId);\n    } catch (err) {\n      if (!running) return;\n      running = false;\n      setState({ type: \"error\", error: toError(err) });\n      return;\n    }\n    if (!running) return;\n\n    // A pending deep-mobile status may rotate the continuation ticket. Adopt a\n    // fresh, still-valid URL so the rendered \"Open Vana\" link always points at a\n    // live ticket; ignore it on the desktop/light path.\n    if (status.status === \"pending\" && state.type === \"ready_to_open\") {\n      const refreshed = normalizeMobileContinuationUrl(\n        status.mobileContinuationUrl,\n      );\n      if (refreshed && refreshed !== state.mobileContinuationUrl) {\n        request = { ...request, mobileContinuationUrl: refreshed };\n        setState({\n          type: \"ready_to_open\",\n          request,\n          mobileContinuationUrl: refreshed,\n        });\n      }\n    }\n\n    if (isReadReadyStatus(status.status)) {\n      clearPoll();\n      approvedRequest = request;\n      await readAndFinish(request);\n      return;\n    }\n    if (\n      status.status === \"completed\" ||\n      status.status === \"denied\" ||\n      status.status === \"expired\"\n    ) {\n      running = false;\n      setState({\n        type: \"error\",\n        error: new Error(`Access request ${status.status}`),\n      });\n      return;\n    }\n    scheduleNextPoll(request, deadline);\n  }\n\n  const flow: DirectConnectFlow<T> = {\n    getState() {\n      return state;\n    },\n\n    subscribe(listener: () => void) {\n      listeners.add(listener);\n      return () => listeners.delete(listener);\n    },\n\n    async start(): Promise<void> {\n      if (running || isRunningPhase()) return;\n      running = true;\n      // start() deliberately keeps its established first-run semantics: every\n      // explicit start creates a request. Only retryRead() may reuse consent.\n      approvedRequest = null;\n      const runId = ++activeRunId;\n      // Read the platform policy at start time, like openApprovalWindow below,\n      // so a policy swapped in after construction (a React rerender forwards\n      // options through a ref) still decides this run's destination.\n      const browserPlatform = (\n        options.browserPlatformPolicy ?? defaultBrowserPlatformPolicy()\n      ).current();\n\n      // Desktop preserves the pre-mobile synchronous popup contract: open a\n      // blank tab while the click's transient activation is live, then navigate\n      // it once createRequest returns the approval URL (BUI-622). Mobile never\n      // creates that transient tab; deep requests expose one explicit HTTPS\n      // link, while light requests retain the manual approvalUrl fallback.\n      // Read the opener option at start time so a swapped-in custom opener is\n      // still honored for desktop flows.\n      const approvalWindow =\n        browserPlatform === \"desktop\"\n          ? (options.openApprovalWindow ?? defaultOpenApprovalWindow)()\n          : null;\n      openedWindow = approvalWindow;\n\n      setState({ type: \"creating\" });\n\n      let request: AccessRequest;\n      try {\n        request = await transports.createRequest();\n      } catch (err) {\n        // If we were superseded (reset, possibly + a newer start()) while this\n        // request was in flight, only clean up our own tab — never the shared\n        // state or the newer run's window.\n        if (runId !== activeRunId) {\n          approvalWindow?.close();\n          return;\n        }\n        running = false;\n        closeUnnavigatedWindow();\n        setState({ type: \"error\", error: toError(err) });\n        return;\n      }\n      if (runId !== activeRunId) {\n        approvalWindow?.close();\n        return;\n      }\n      // Re-validate the continuation URL at the SDK boundary (defense in depth\n      // for custom transports that bypass the default client).\n      request = {\n        ...request,\n        mobileContinuationUrl: normalizeMobileContinuationUrl(\n          request.mobileContinuationUrl,\n        ),\n      };\n\n      // The SDK owns only the small mobile-versus-desktop destination choice.\n      // A deep Direct request on mobile carries a validated continuation URL;\n      // desktop keeps its popup contract, while mobile light exposes the HTTPS\n      // approval URL as the existing manual fallback without opening a tab.\n      const mobileContinuationUrl =\n        browserPlatform === \"mobile\"\n          ? request.mobileContinuationUrl\n          : undefined;\n\n      if (mobileContinuationUrl) {\n        // Do not auto-launch: DCR creation is async, so the original Connect\n        // gesture can no longer be trusted to retain iOS user activation. Let\n        // the UI render an explicit primary \"Open Vana\" link; polling continues\n        // in this tab.\n        startPolling(request, {\n          type: \"ready_to_open\",\n          request,\n          mobileContinuationUrl,\n        });\n        return;\n      }\n\n      // Desktop/light: navigate the synchronously-opened tab to the HTTPS\n      // approval URL. `approvalWindow === null` means the popup was blocked;\n      // surface it so the UI renders request.approvalUrl as a visible manual\n      // \"Open approval\" link instead of hanging. We poll either way, so a manual\n      // open still resolves the flow, and the timeout still bounds the wait.\n      if (approvalWindow) {\n        approvalWindow.navigate(request.approvalUrl);\n        // Hand the tab off to the user; we no longer own/close it.\n        openedWindow = null;\n      }\n      startPolling(request, {\n        type: \"awaiting_approval\",\n        request,\n        popupBlocked: approvalWindow === null,\n      });\n    },\n\n    async retryRead(): Promise<DirectConnectRetryOutcome> {\n      if (running || isRunningPhase()) {\n        throw new Error(\n          \"Cannot retry a read while the connect flow is running\",\n        );\n      }\n\n      const request = approvedRequest;\n      const parsedExpiry = request?.expiresAt\n        ? Date.parse(request.expiresAt)\n        : Number.NaN;\n      const requestExpired =\n        Number.isFinite(parsedExpiry) && now() >= parsedExpiry;\n\n      if (request && !requestExpired) {\n        running = true;\n        const runId = ++activeRunId;\n        let status: AccessRequestStatus;\n        try {\n          status = await transports.getStatus(request.requestId);\n        } catch (err) {\n          if (runId !== activeRunId) {\n            throw new Error(\"Read retry was superseded\");\n          }\n          running = false;\n          const error = toError(err);\n          setState({ type: \"error\", error });\n          throw error;\n        }\n        if (runId !== activeRunId) {\n          throw new Error(\"Read retry was superseded\");\n        }\n        if (isReadReadyStatus(status.status)) {\n          await readAndFinish(request);\n          return \"retried_existing_grant\";\n        }\n        running = false;\n      }\n\n      approvedRequest = null;\n      await flow.start();\n      return \"fresh_approval_required\";\n    },\n\n    reset(): void {\n      running = false;\n      approvedRequest = null;\n      // Invalidate any in-flight start() so a late createRequest can't clobber\n      // a subsequent run.\n      activeRunId++;\n      clearPoll();\n      closeUnnavigatedWindow();\n      setState({ type: \"idle\" });\n    },\n  };\n\n  return flow;\n}\n"],"mappings":"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","names":[]}