{"version":3,"file":"offscreen.cjs","sources":["../src/shared/transport-server.ts","../src/offscreen/server.ts","../src/offscreen/index.ts"],"sourcesContent":["// Server-side transport. A single TransportServer listens to all channels that\n// connect. In the extension this is offscreen, to which the SW proxies\n// content-scripts (one offscreen ↔ N channels from the SW, one per content).\n//\n// Contract:\n//  - on<K>(kind, handler) — registers a handler for a request type. One\n//    handler per kind (override dispatch): if redefined — the last one\n//    wins. Throws are caught and serialized into a ResponseErr.\n//  - broadcast<K>(kind, payload) — fan-out to all live channels. Used by\n//    BillingClient / AuthClient when state changes.\n//  - accept(channel) — add a channel to the active pool. We don't listen on\n//    chrome.runtime.onConnect inside shared code, so it can be tested without\n//    chrome.* — that's done by the extension-side adapter (offscreen/sw).\n\nimport type {\n  CancelEnvelope,\n  EventEnvelope,\n  EventKind,\n  EventPayload,\n  RequestEnvelope,\n  RequestKind,\n  RequestParams,\n  RequestResult\n} from './protocol';\nimport { PROTOCOL_VERSION } from './protocol';\nimport { serializeError } from './errors';\nimport type { MessageChannel } from './channel';\n\nexport interface RequestContext {\n  /** AbortSignal that triggers when a cancel-envelope is received from the client.\n   *  A handler can pass it into the underlying fetch to cancel the network\n   *  operation. Ignoring it is also OK — older handlers keep working. */\n  signal: AbortSignal;\n}\n\nexport type RequestHandler<K extends RequestKind> = (\n  params: RequestParams<K>,\n  ctx: RequestContext\n) => Promise<RequestResult<K>> | RequestResult<K>;\n\nexport class TransportServer {\n  private handlers = new Map<RequestKind, RequestHandler<RequestKind>>();\n  private channels = new Set<MessageChannel>();\n  /** Active requests per channel: channel → id → AbortController. On a cancel\n   *  envelope we find the controller and abort it. On disconnect — abort all. */\n  private active = new WeakMap<MessageChannel, Map<string, AbortController>>();\n\n  constructor() {\n    // Built-in handshake handler — responds with the current protocol version.\n    // The client logs the mismatch on the TransportClient.ensureChannel side,\n    // we don't block further requests (best-effort versioning).\n    this.on('handshake', () => ({\n      protocolVersion: PROTOCOL_VERSION,\n      offscreenReady: true\n    }));\n  }\n\n  on<K extends RequestKind>(kind: K, handler: RequestHandler<K>): void {\n    this.handlers.set(kind, handler as RequestHandler<RequestKind>);\n  }\n\n  off<K extends RequestKind>(kind: K): void {\n    this.handlers.delete(kind);\n  }\n\n  /** Attach a channel. The server starts handling requests from it and\n   *  includes it in broadcasts. On disconnect it automatically removes it and\n   *  aborts all in-flight handlers for that channel. */\n  accept(channel: MessageChannel): void {\n    this.channels.add(channel);\n    this.active.set(channel, new Map());\n    channel.onMessage((env) => this.dispatch(channel, env));\n    channel.onDisconnect(() => {\n      this.channels.delete(channel);\n      const inFlight = this.active.get(channel);\n      if (inFlight) {\n        for (const ctrl of inFlight.values()) ctrl.abort();\n      }\n      this.active.delete(channel);\n    });\n  }\n\n  /** Fan-out an event to all connected channels. */\n  broadcast<K extends EventKind>(kind: K, payload: EventPayload<K>): void {\n    const envelope: EventEnvelope<EventPayload<K>> = { type: 'event', kind, payload };\n    for (const channel of this.channels) {\n      try {\n        channel.send(envelope);\n      } catch (e) {\n        console.error('[sdk-extension] broadcast send failed', e);\n      }\n    }\n  }\n\n  /** Size of the active pool — for health-check / offscreen cleanup\n   *  (if 0, the host can close the offscreen document). */\n  get connectionCount(): number {\n    return this.channels.size;\n  }\n\n  private async dispatch(channel: MessageChannel, raw: unknown): Promise<void> {\n    if (isCancel(raw)) {\n      const inFlight = this.active.get(channel);\n      const ctrl = inFlight?.get(raw.id);\n      if (ctrl) {\n        ctrl.abort();\n        inFlight!.delete(raw.id);\n      }\n      return;\n    }\n    if (!isRequest(raw)) return;\n    const handler = this.handlers.get(raw.kind);\n    if (!handler) {\n      this.respondErr(channel, raw.id, new Error(`Unknown request kind: ${raw.kind}`));\n      return;\n    }\n    const ctrl = new AbortController();\n    const inFlight = this.active.get(channel);\n    inFlight?.set(raw.id, ctrl);\n    try {\n      const result = await handler(raw.params as RequestParams<RequestKind>, {\n        signal: ctrl.signal\n      });\n      this.respondOk(channel, raw.id, result);\n    } catch (e) {\n      // If the handler finished via abort — the client already knows (it\n      // did the cancelling itself). We send the error response anyway; the\n      // client-side pending is already cleared, nothing happens. Safer than skipping the response.\n      this.respondErr(channel, raw.id, e);\n    } finally {\n      inFlight?.delete(raw.id);\n    }\n  }\n\n  private respondOk(channel: MessageChannel, id: string, result: unknown): void {\n    try {\n      channel.send({ type: 'response', id, ok: true, result });\n    } catch (e) {\n      console.error('[sdk-extension] respond send failed', e);\n    }\n  }\n\n  private respondErr(channel: MessageChannel, id: string, error: unknown): void {\n    try {\n      channel.send({ type: 'response', id, ok: false, error: serializeError(error) });\n    } catch (e) {\n      console.error('[sdk-extension] respond err send failed', e);\n    }\n  }\n}\n\nfunction isRequest(value: unknown): value is RequestEnvelope {\n  if (typeof value !== 'object' || value === null) return false;\n  return (value as { type?: unknown }).type === 'request';\n}\n\nfunction isCancel(value: unknown): value is CancelEnvelope {\n  if (typeof value !== 'object' || value === null) return false;\n  return (value as { type?: unknown }).type === 'cancel';\n}\n","// Offscreen-side server. Owns the real BillingClient + AuthClient (if enabled)\n// — the single source of truth for the whole extension. Registers handlers on\n// the TransportServer, accepts ports from the SW via chrome.runtime.onConnect,\n// broadcasts userChange/authChange/balancesChange on state changes.\n//\n// Lifecycle: the server is created once via startOffscreenServer(). If the SW\n// restarts, it re-creates offscreen (if the document died) or opens a new port\n// (if the document is alive). In both cases the server accepts the new channel,\n// and the state survives.\n//\n// OAuth flows. The PKCE verifier is held in offscreen between the oauthStart and\n// oauthExchange requests. Content only opens the popup and waits for the code\n// (natively, in its own frame) — the verifier never crosses the runtime boundary.\n\nimport { BillingClient } from '@sdk/core/BillingClient';\nimport { PaywallError } from '@sdk/core/types';\nimport {\n  AuthClient,\n  type AuthSession,\n  type OAuthResumeCheckout\n} from '@sdk/core/auth';\nimport { EventTracker } from '@sdk/core/EventTracker';\nimport { createTrialStore } from '@sdk/core/trial';\nimport { STORAGE_KEYS } from '@sdk/core/storage';\nimport type { TrialConfig } from '@sdk/core/types';\nimport type { OffscreenServerOptions } from './index';\nimport { TransportServer } from '../shared/transport-server';\nimport { portToChannel } from '../shared/chrome-port';\nimport { RELAY_PORT_NAME } from '../shared/port-name';\nimport { base64ToBytes } from '../shared/base64';\nimport { MAX_SUPPORT_FILES, MAX_SUPPORT_FILE_SIZE } from '../shared/support-limits';\n\n// Staged support attachments awaiting their createSupportTicket. Sweep window:\n// generous enough for a slow sequential upload of 5 files, short enough that\n// an abandoned form (staged files, tab closed before submit) doesn't pin\n// megabytes in the offscreen heap for long.\nconst STAGED_FILE_TTL_MS = 10 * 60 * 1000;\n// Heap cap across ALL tabs (one offscreen per extension): 2 full tickets' worth.\nconst STAGED_FILES_MAX_COUNT = MAX_SUPPORT_FILES * 2;\n\ntype StagedFile = { name: string; type: string; bytes: Uint8Array; stagedAt: number };\n\n// How long a state handed out by oauthStart stays adoptable. Matches\n// OAUTH_FLOW_TTL_MS in @sdk/core/auth — past it the verifier is gone anyway.\nconst OAUTH_STATE_TTL_MS = 10 * 60 * 1000;\n// How long a settled exchange is remembered after success. Covers the race where\n// the SW adopts the code first and the still-alive popup asks for the same state\n// a moment later: it gets the session instead of `oauth_invalid_state`. The code\n// is single-use at GoTrue, so replaying it is not an option.\nconst OAUTH_EXCHANGE_MEMO_MS = 60 * 1000;\n\nexport class OffscreenServer {\n  readonly billing: BillingClient;\n  readonly auth: AuthClient | undefined;\n  readonly tracker: EventTracker | undefined;\n  private readonly transport = new TransportServer();\n  private readonly stagedFiles = new Map<string, StagedFile>();\n  private connectListener: ((port: chrome.runtime.Port) => void) | null = null;\n  private userUnsub: (() => void) | null = null;\n  private balanceUnsub: (() => void) | null = null;\n  private authUnsub: (() => void) | null = null;\n  /** States handed out by auth.oauthStart, oldest first. The adopt path (service\n   *  worker) learns only the code — the state rides in `window.name`, which a\n   *  worker cannot read — so we resolve the flow from here. */\n  private pendingOAuthStates: Array<{\n    state: string;\n    at: number;\n    resumeCheckout?: OAuthResumeCheckout;\n  }> = [];\n  /** Exchanges keyed by state, in-flight and briefly after settling. Both entry\n   *  points funnel through it so one auth code is never sent to GoTrue twice. */\n  private oauthExchanges = new Map<string, Promise<AuthSession>>();\n\n  constructor(opts: OffscreenServerOptions) {\n    if (opts.auth) {\n      this.auth = new AuthClient({\n        paywallId: opts.paywallId,\n        apiOrigin: opts.apiOrigin\n      });\n    }\n\n    this.billing = new BillingClient({\n      paywallId: opts.paywallId,\n      apiOrigin: opts.apiOrigin,\n      auth: this.auth\n    });\n\n    this.tracker = createTrackerIfEnabled(opts, this.billing);\n\n    this.registerBillingHandlers();\n    if (this.auth) this.registerAuthHandlers(this.auth);\n    if (this.tracker) this.registerTrackerHandlers(this.tracker);\n    this.bridgeBroadcasts();\n  }\n\n  private registerTrackerHandlers(tracker: EventTracker): void {\n    this.transport.on('tracker.track', (params) => {\n      tracker.track(params.name, params.props);\n    });\n  }\n\n  /** Drop staged support attachments older than the TTL. Called on every\n   *  stage request — no timer needed, the map is only ever populated by the\n   *  same request path. */\n  private sweepStagedFiles(): void {\n    const cutoff = Date.now() - STAGED_FILE_TTL_MS;\n    for (const [id, f] of this.stagedFiles) {\n      if (f.stagedAt < cutoff) this.stagedFiles.delete(id);\n    }\n  }\n\n  private registerBillingHandlers(): void {\n    // ctx.signal is forwarded into the underlying fetch — cancellation from the\n    // content side (the user closed the modal) actually cancels the network\n    // request in offscreen, instead of leaving a \"zombie fetch\" hanging until\n    // timeout.\n    this.transport.on('billing.bootstrap', async (params, ctx) =>\n      this.billing.bootstrap({ force: params.force, signal: ctx.signal })\n    );\n    this.transport.on('billing.getCachedBootstrap', () =>\n      this.billing.getCachedBootstrap()\n    );\n\n    this.transport.on('billing.getVisitorId', async () => this.billing.getVisitorId());\n\n    this.transport.on('billing.getUser', async (params, ctx) =>\n      this.billing.getUser({ force: params.force, signal: ctx.signal })\n    );\n    this.transport.on('billing.getCachedUser', () => this.billing.getCachedUser());\n    this.transport.on('billing.getSettledUser', async (_params, ctx) =>\n      this.billing.getSettledUser({ signal: ctx.signal })\n    );\n\n    this.transport.on('billing.getBalances', async (params, ctx) =>\n      this.billing.getBalances({ force: params.force, signal: ctx.signal })\n    );\n    this.transport.on('billing.getCachedBalances', () => this.billing.getCachedBalances());\n\n    this.transport.on('billing.createCheckout', async (params, ctx) =>\n      this.billing.createCheckout({ ...params, signal: ctx.signal })\n    );\n\n    this.transport.on('billing.listPurchases', async (_params, ctx) =>\n      this.billing.listPurchases({ signal: ctx.signal })\n    );\n    this.transport.on('billing.cancelSubscription', async (params, ctx) =>\n      this.billing.cancelSubscription({ ...params, signal: ctx.signal })\n    );\n    this.transport.on('billing.getCustomerPortalUrl', async (params, ctx) =>\n      this.billing.getCustomerPortalUrl({ returnUrl: params.returnUrl, signal: ctx.signal })\n    );\n\n    // Attachments arrive as base64 (Files don't survive the JSON-serialized\n    // runtime ports — see shared/messages.ts). Stage each file, then the\n    // ticket request references the staged ids and we rebuild real Files here.\n    this.transport.on('billing.stageSupportFile', (params) => {\n      this.sweepStagedFiles();\n      const bytes = base64ToBytes(params.dataBase64);\n      if (bytes.length > MAX_SUPPORT_FILE_SIZE) {\n        throw new PaywallError('invalid_file', 'Attachment too large', { status: 400 });\n      }\n      if (this.stagedFiles.size >= STAGED_FILES_MAX_COUNT) {\n        throw new PaywallError('too_many_files', 'Too many staged attachments', {\n          status: 429\n        });\n      }\n      const fileId = crypto.randomUUID();\n      this.stagedFiles.set(fileId, {\n        name: params.name,\n        type: params.type,\n        bytes,\n        stagedAt: Date.now()\n      });\n      return { fileId };\n    });\n\n    this.transport.on('billing.createSupportTicket', async (params) => {\n      const { fileIds, ...ticket } = params;\n      // Consume the staged entries up front: whether the backend call succeeds\n      // or fails, a retry re-stages from the content side — nothing may linger.\n      const files: File[] = [];\n      for (const id of fileIds ?? []) {\n        const staged = this.stagedFiles.get(id);\n        this.stagedFiles.delete(id);\n        // A missing id means the stage was swept (TTL) or never happened —\n        // failing loudly beats silently recreating the \"ticket without the\n        // screenshot\" bug this flow exists to fix.\n        if (!staged) {\n          throw new PaywallError('invalid_file', 'Attachment expired, re-send the ticket', {\n            status: 400\n          });\n        }\n        files.push(new File([staged.bytes as BlobPart], staged.name, { type: staged.type }));\n      }\n      return this.billing.createSupportTicket({\n        ...ticket,\n        files: files.length > 0 ? files : undefined\n      });\n    });\n\n    this.transport.on('billing.getIdentity', () => this.billing.getIdentity() ?? null);\n    this.transport.on('billing.setIdentity', (params) => {\n      this.billing.setIdentity(params.identity ?? undefined);\n    });\n\n    // Storage proxy. Any consumer going through `billing.getStorage()` ends up\n    // here; the state lives in the offscreen localStorage = single source of truth.\n    const storage = this.billing.getStorage();\n    this.transport.on('storage.get', async (params) => storage.getItem(params.key));\n    this.transport.on('storage.set', async (params) => {\n      await storage.setItem(params.key, params.value);\n    });\n    this.transport.on('storage.remove', async (params) => {\n      await storage.removeItem(params.key);\n    });\n\n    // Trial-store with an atomic recordBlock via navigator.locks. Each\n    // recordBlock call is serialized by the key `trial:<paywallId>` — two tabs\n    // can't grab the same snapshot at once and both write a decrement, so\n    // there's no drift.\n    this.transport.on('trial.check', async (params) =>\n      withTrialLock(params.paywallId, () =>\n        this.makeTrialStore(params.paywallId, params.config).check()\n      )\n    );\n    this.transport.on('trial.recordBlock', async (params) =>\n      withTrialLock(params.paywallId, () =>\n        this.makeTrialStore(params.paywallId, params.config).recordBlock()\n      )\n    );\n    this.transport.on('trial.reset', async (params) =>\n      withTrialLock(params.paywallId, () =>\n        this.makeTrialStore(params.paywallId, params.config).reset()\n      )\n    );\n  }\n\n  /** Each trial handler creates a fresh store — it's stateless and reads state\n   *  from storage. There's no point caching instances (storage = SoT). */\n  private makeTrialStore(paywallId: string, config: TrialConfig) {\n    return createTrialStore(this.billing.getStorage(), paywallId, config);\n  }\n\n  private registerAuthHandlers(auth: AuthClient): void {\n    this.transport.on('auth.signInWithEmail', async (params) =>\n      auth.signInWithEmail(params)\n    );\n    this.transport.on('auth.signUp', async (params) => auth.signUp(params));\n    this.transport.on('auth.signOut', async () => auth.signOut());\n    this.transport.on('auth.refresh', async () => auth.refresh());\n    this.transport.on('auth.getCachedSession', () => auth.getCachedSession());\n    this.transport.on('auth.requestPasswordReset', async (params) =>\n      auth.requestPasswordReset(params)\n    );\n    this.transport.on('auth.updatePassword', async (params) =>\n      auth.updatePassword(params)\n    );\n    this.transport.on('auth.sendOtp', async (params) => auth.sendOtp(params));\n    this.transport.on('auth.verifyOtp', async (params) => auth.verifyOtp(params));\n    this.transport.on('auth.resendConfirmation', async (params) =>\n      auth.resendConfirmation(params)\n    );\n    this.transport.on('auth.revokeAllSessions', async () => auth.revokeAllSessions());\n    this.transport.on('auth.getLastLogin', async () => auth.getLastLogin());\n\n    // OAuth split-API (Phase 4.5). The verifier lives inside AuthClient between\n    // these two requests; content only opens the popup and waits for the code.\n    // No state in the SDK-extension offscreen-server — it's all in AuthClient\n    // itself.\n    this.transport.on('auth.oauthStart', async (params) => {\n      const { authorize_url, state } = await auth.startOAuthFlow({\n        provider: params.provider,\n        scopes: params.scopes,\n        userMeta: params.userMeta,\n        switchAccount: params.switchAccount\n      });\n      this.rememberOAuthState(state, params.resumeCheckout);\n      return { authorizeUrl: authorize_url, state };\n    });\n    this.transport.on('auth.oauthExchange', async (params) => {\n      const session = await this.exchangeOAuthOnce(auth, params.state, params.code);\n      // Only a live surface calls this — it got the code by postMessage and is\n      // about to continue the flow itself. Drop the flow so the worker's adopt\n      // path finds nothing: otherwise both would create a checkout for one\n      // sign-in, and the user would be charged for whichever they finish.\n      this.forgetOAuthState(params.state);\n      return session;\n    });\n    // Adopt: the surface that started the flow is gone (a toolbar action popup\n    // is destroyed the moment the provider window takes focus, which on some\n    // window managers happens every time), so nobody is left to hand us the\n    // code. The SW read it off the callback URL instead. We own the verifier, so\n    // we can finish alone — and the resulting authChange broadcast reaches every\n    // surface that is still alive, plus the next one to open.\n    this.transport.on('auth.oauthAdopt', async (params) => {\n      const pending = this.newestPendingOAuthFlow();\n      if (!pending) return { adopted: false, reason: 'no_pending_flow' };\n      try {\n        await this.exchangeOAuthOnce(auth, pending.state, params.code);\n      } catch (e) {\n        const code = e instanceof PaywallError ? e.code : 'exchange_failed';\n        return { adopted: false, reason: code };\n      }\n      this.forgetOAuthState(pending.state);\n      // Signed in. If a purchase was waiting behind this gate, create it now so\n      // the worker can send the provider tab straight to payment — otherwise the\n      // user has to reopen the extension and click buy a second time, which is\n      // the whole reason this path exists.\n      const checkoutUrl = pending.resumeCheckout\n        ? await this.createResumeCheckout(pending.resumeCheckout)\n        : undefined;\n      return checkoutUrl ? { adopted: true, checkoutUrl } : { adopted: true };\n    });\n    this.transport.on('auth.getAccessToken', async () => auth.getAccessToken());\n\n    this.transport.on('auth.signInAnonymously', async (params) =>\n      auth.signInAnonymously({\n        captchaToken: params.captchaToken,\n        userMeta: params.userMeta,\n        forceNewAnon: params.forceNewAnon\n      })\n    );\n  }\n\n  private rememberOAuthState(state: string, resumeCheckout?: OAuthResumeCheckout): void {\n    const cutoff = Date.now() - OAUTH_STATE_TTL_MS;\n    this.pendingOAuthStates = this.pendingOAuthStates.filter((f) => f.at >= cutoff);\n    this.pendingOAuthStates.push({ state, at: Date.now(), resumeCheckout });\n  }\n\n  /**\n   * Creates the checkout a finished sign-in was gating. Returns the URL, or\n   * undefined when there is nothing to open — including the 409 for a user who\n   * already owns the subscription, where sending them to pay again would be\n   * worse than sending them nowhere (they reopen the extension and the settled\n   * -user gate shows the restored state).\n   *\n   * Mirrors what PaywallUI does around a checkout, because the surface that\n   * normally would is gone: the pending marker keeps the next open from flashing\n   * the paywall at a user who is mid-payment, and the analytics event keeps the\n   * funnel honest.\n   */\n  private async createResumeCheckout(\n    intent: OAuthResumeCheckout\n  ): Promise<string | undefined> {\n    try {\n      const result = await this.billing.createCheckout({\n        priceId: intent.priceId,\n        offerId: intent.offerId,\n        ignoreActivePurchase: intent.renew === true\n      });\n      if (!result.url) return undefined;\n\n      this.tracker?.track('checkout_started', {\n        price_id: intent.priceId,\n        acquiring: result.acquiring\n      });\n      try {\n        await Promise.resolve(\n          this.billing\n            .getStorage()\n            .setItem(\n              STORAGE_KEYS.checkoutPending(this.billing.paywallId),\n              JSON.stringify({ at: Date.now() })\n            )\n        );\n      } catch {\n        /* quota / disabled storage — costs a one-time paywall flash, not the sale */\n      }\n      return result.url;\n    } catch {\n      // already_purchased, a dead price, the network — the sign-in itself stands,\n      // so we still report adopted and simply close the tab.\n      return undefined;\n    }\n  }\n\n  /** The most recently started flow, which is the one a returning code belongs\n   *  to. Concurrent OAuth flows in one extension are not a real scenario — each\n   *  needs its own provider window — so newest-wins is enough, and a wrong guess\n   *  costs only a failed exchange, never a wrong session (the code is bound to\n   *  the challenge sent with that state). */\n  private newestPendingOAuthFlow(): {\n    state: string;\n    resumeCheckout?: OAuthResumeCheckout;\n  } | null {\n    const cutoff = Date.now() - OAUTH_STATE_TTL_MS;\n    for (let i = this.pendingOAuthStates.length - 1; i >= 0; i--) {\n      if (this.pendingOAuthStates[i].at >= cutoff) return this.pendingOAuthStates[i];\n    }\n    return null;\n  }\n\n  private forgetOAuthState(state: string): void {\n    this.pendingOAuthStates = this.pendingOAuthStates.filter((f) => f.state !== state);\n  }\n\n  private exchangeOAuthOnce(\n    auth: AuthClient,\n    state: string,\n    code: string\n  ): Promise<AuthSession> {\n    const inflight = this.oauthExchanges.get(state);\n    if (inflight) return inflight;\n\n    const promise = auth.completeOAuthFlow({ state, code });\n    this.oauthExchanges.set(state, promise);\n    promise.then(\n      () => {\n        setTimeout(() => this.oauthExchanges.delete(state), OAUTH_EXCHANGE_MEMO_MS);\n      },\n      () => {\n        // Failures are forgotten immediately — a retry with a fresh code must\n        // not be answered from the memo.\n        this.oauthExchanges.delete(state);\n      }\n    );\n    return promise;\n  }\n\n  private bridgeBroadcasts(): void {\n    this.userUnsub = this.billing.onUserChange(\n      (user) => this.transport.broadcast('userChange', user),\n      { immediate: 'none' }\n    );\n    this.balanceUnsub = this.billing.onBalanceChange(\n      (balances) => this.transport.broadcast('balancesChange', balances),\n      { immediate: 'none' }\n    );\n    if (this.auth) {\n      // We do NOT broadcast INITIAL_SESSION: it's a per-subscriber synthetic\n      // event; the content-side RemoteAuthClient emits it itself right after\n      // resolving its hydrate promise (via a getCachedSession request).\n      // Otherwise one content re-connect would spawn a duplicate INITIAL_SESSION\n      // for every listener in it.\n      this.authUnsub = this.auth.onAuthChange((event, session) => {\n        if (event === 'INITIAL_SESSION') return;\n        this.transport.broadcast('authChange', { event, session });\n      });\n    }\n  }\n\n  /** Accept a raw MessageChannel, bypassing chrome.runtime. Unit tests wire an\n   *  in-memory channel here to exercise the real handler graph; prod traffic\n   *  always arrives through start() → onConnect. */\n  acceptChannel(channel: Parameters<TransportServer['accept']>[0]): void {\n    this.transport.accept(channel);\n  }\n\n  /** Start the listener on chrome.runtime.onConnect. */\n  start(): void {\n    if (this.connectListener) return;\n    // We accept only the SW relay-port (RELAY_PORT_NAME). chrome.runtime.connect\n    // from popup/content/side-panel is delivered to ALL extension contexts with\n    // an onConnect listener — including offscreen directly, bypassing the SW. If\n    // we accepted PORT_NAME, a single popup.connect() would deliver TWO ports to\n    // offscreen (SW relay + direct popup), and one send from the popup would be\n    // duplicated: the SW relay posts the msg → handler #1, the direct popup port\n    // receives the same msg → handler #2. The SW therefore uses a separate name,\n    // RELAY_PORT_NAME, for its own connect to offscreen.\n    this.connectListener = (port) => {\n      if (port.name !== RELAY_PORT_NAME) return;\n      this.transport.accept(portToChannel(port));\n    };\n    chrome.runtime.onConnect.addListener(this.connectListener);\n  }\n\n  stop(): void {\n    if (this.connectListener) {\n      chrome.runtime.onConnect.removeListener(this.connectListener);\n      this.connectListener = null;\n    }\n    this.userUnsub?.();\n    this.balanceUnsub?.();\n    this.authUnsub?.();\n    this.userUnsub = null;\n    this.balanceUnsub = null;\n    this.authUnsub = null;\n    this.tracker?.destroy();\n  }\n}\n\n/** Serializes trial operations by key — atomic read-modify-write inside\n *  offscreen. navigator.locks is available in the offscreen context (Chrome\n *  69+); for browsers without it — fallback to a direct call (a race is\n *  possible, but that's a deep legacy case). */\nasync function withTrialLock<T>(paywallId: string, fn: () => Promise<T>): Promise<T> {\n  if (typeof navigator !== 'undefined' && navigator.locks?.request) {\n    return navigator.locks.request(`@monetize.software/sdk-extension:trial:${paywallId}`, fn);\n  }\n  return fn();\n}\n\nfunction createTrackerIfEnabled(\n  opts: OffscreenServerOptions,\n  billing: BillingClient\n): EventTracker | undefined {\n  if (opts.analytics === false) return undefined;\n  const cfg = typeof opts.analytics === 'object' && opts.analytics !== null ? opts.analytics : {};\n  // A thunk, not a string: after an edge failover (sdk core/edge.ts) events\n  // must follow the origin that is actually reachable, resolved at flush time.\n  const endpoint =\n    cfg.endpoint ??\n    (() => `${billing.activeApiOrigin()}/api/v1/paywall/${billing.paywallId}/events`);\n  return new EventTracker({\n    endpoint,\n    paywallId: billing.paywallId,\n    capabilities: billing.capabilities,\n    getVisitorId: () => billing.getVisitorId(),\n    getCachedVisitorId: () => billing.getCachedVisitorId(),\n    getUserId: () => billing.getIdentity()?.userId ?? null,\n    flushIntervalMs: cfg.flushIntervalMs,\n    maxBufferSize: cfg.maxBufferSize\n  });\n}\n","// Offscreen page entry. Owns the real BillingClient (and in Phase 4+ —\n// AuthClient, EventTracker) — the single source of truth for the whole\n// extension.\n//\n// Imported in `offscreen.html`:\n//   <script type=\"module\">\n//     import { startOffscreenServer } from '@monetize.software/sdk-extension/offscreen';\n//     startOffscreenServer({ paywallId: '123', apiOrigin: 'https://...' });\n//   </script>\n\nimport { OffscreenServer } from './server';\n\nexport interface OffscreenServerOptions {\n  paywallId: string;\n  apiOrigin?: string;\n  /** If true — the offscreen-server creates its own AuthClient and connects it\n   *  to BillingClient for Bearer authorization. The session is stored in the\n   *  offscreen localStorage and shared across all surfaces of the extension via\n   *  the authChange broadcast. */\n  auth?: boolean;\n  /** Analytics. Enabled by default; pass false to disable entirely. An object —\n   *  custom parameters (endpoint, batch). There's one EventTracker per\n   *  extension, all content track() calls are forwarded to it. */\n  analytics?:\n    | boolean\n    | {\n        endpoint?: string;\n        flushIntervalMs?: number;\n        maxBufferSize?: number;\n      };\n}\n\nlet active: OffscreenServer | null = null;\n\nexport function startOffscreenServer(opts: OffscreenServerOptions): OffscreenServer {\n  if (typeof chrome === 'undefined' || !chrome.runtime) {\n    throw new Error('@monetize.software/sdk-extension/offscreen requires chrome.runtime');\n  }\n  if (active) {\n    // Double start — can happen if the host loads the offscreen-bootstrap twice\n    // (HMR in dev, or a bug with a duplicate <script>). We return the existing\n    // instance — re-creating would register a second listener on runtime.onConnect.\n    return active;\n  }\n  active = new OffscreenServer(opts);\n  active.start();\n  return active;\n}\n\nexport type { OffscreenServer };\n"],"names":["TransportServer","PROTOCOL_VERSION","kind","handler","channel","env","inFlight","ctrl","payload","envelope","e","raw","isCancel","isRequest","result","id","error","serializeError","value","STAGED_FILE_TTL_MS","STAGED_FILES_MAX_COUNT","MAX_SUPPORT_FILES","OAUTH_STATE_TTL_MS","OAUTH_EXCHANGE_MEMO_MS","OffscreenServer","opts","AuthClient","BillingClient","createTrackerIfEnabled","tracker","params","cutoff","f","ctx","_params","bytes","base64ToBytes","MAX_SUPPORT_FILE_SIZE","PaywallError","fileId","fileIds","ticket","files","staged","storage","withTrialLock","paywallId","config","createTrialStore","auth","authorize_url","state","session","pending","checkoutUrl","resumeCheckout","intent","STORAGE_KEYS","i","code","inflight","promise","user","balances","event","port","RELAY_PORT_NAME","portToChannel","fn","billing","cfg","endpoint","EventTracker","active","startOffscreenServer"],"mappings":"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"}