{"version":3,"sources":["../src/constants.ts","../src/assignments-cache.ts","../src/api-error.ts","../src/async-submitter.ts","../src/dedup.ts","../src/http.ts","../src/user.ts","../src/error-safety.ts","../src/config.ts","../src/client.ts","../src/version.ts"],"sourcesContent":["export const DEFAULT_BASE_URL = 'https://abmeter.ai';\nexport const DEFAULT_FLUSH_INTERVAL_MS = 1000;\n\n// Deliberately smaller than Ruby/Python's 100: those serve many users per\n// process, a browser serves one, so events are sparse. Small batches stay far\n// under the ~64 KiB keepalive/beacon quota and lose less on tab death.\nexport const BATCH_SIZE = 20;\nexport const MAX_SUBMIT_ATTEMPTS = 3;\nexport const MAX_RETRY_QUEUE = 1000;\n\nexport const DEDUP_WINDOW_MS = 10 * 60 * 1000;\nexport const DEDUP_MAX_KEYS = 1000;\n\n// fetch keepalive shares a ~64 KiB in-flight quota per page with sendBeacon;\n// chunk bodies to 80% of it rather than hoping a batch fits.\nexport const KEEPALIVE_BODY_LIMIT_BYTES = Math.floor(64 * 1024 * 0.8);\n\nexport const ASSIGNMENTS_STORAGE_PREFIX = 'abmeter:assignments:';\nexport const TRACK_ID_STORAGE_KEY = 'abmeter:track_id';\nexport const TRACK_ID_COOKIE_NAME = 'abmeter_track_id';\nexport const TRACK_ID_COOKIE_MAX_AGE_SECONDS = 365 * 24 * 60 * 60;\n\nexport const USER_ASSIGNMENTS_PATH = '/api/v1/user-assignments';\nexport const EXPOSURES_PATH = '/api/v1/exposures';\nexport const EVENTS_PATH = '/api/v1/events';\n","import { ASSIGNMENTS_STORAGE_PREFIX } from './constants';\nimport type { HttpClient } from './http';\nimport type { Logger } from './config';\nimport type { User } from './user';\n\nexport interface ExposureMetadata {\n  exposable_type: 'Experiment';\n  exposable_id: number;\n  audience_id: number;\n}\n\nexport interface Assignment {\n  value: unknown;\n  parameter_id: number;\n  space_id: number;\n  /** null for feature-flag and default resolutions — never report those as exposures. */\n  exposure: ExposureMetadata | null;\n}\n\nexport interface AssignmentsPayload {\n  user: { user_id: string; email?: string | null };\n  assignments: Record<string, Assignment>;\n}\n\ninterface StoredEntry {\n  etag?: string;\n  payload: AssignmentsPayload;\n}\n\n// The value map + hydration. Cache-then-network: hydrate synchronously from\n// localStorage (unblocks first paint), then refresh in the background with\n// If-None-Match. localStorage blocked → in-memory only; still works, just no\n// cross-session cache.\nexport class AssignmentsCache {\n  private readonly http: HttpClient;\n  private readonly user: User;\n  private readonly logger: Logger;\n  private readonly storage: Storage | null;\n  private assignments: Record<string, Assignment> = {};\n  private etag?: string;\n\n  constructor({ http, user, logger }: { http: HttpClient; user: User; logger: Logger }) {\n    this.http = http;\n    this.user = user;\n    this.logger = logger;\n    this.storage = detectStorage();\n  }\n\n  hydrateFromStorage(): boolean {\n    const raw = this.storageRead(this.storageKey());\n    if (!raw) return false;\n\n    try {\n      const entry = JSON.parse(raw) as StoredEntry;\n      if (!entry?.payload?.assignments) return false;\n      this.assignments = entry.payload.assignments;\n      this.etag = entry.etag;\n      return true;\n    } catch {\n      return false;\n    }\n  }\n\n  async refresh(): Promise<void> {\n    const body: { user_id: string; email?: string } =\n      this.user.email === undefined\n        ? { user_id: this.user.userId }\n        : { user_id: this.user.userId, email: this.user.email };\n\n    const response = await this.http.fetchAssignments(body, this.etag);\n    if (response.status === 304) return; // cache is current\n\n    if (response.payload) {\n      this.assignments = response.payload.assignments;\n      this.etag = response.etag;\n      this.persist({ etag: response.etag, payload: response.payload });\n    }\n  }\n\n  resolveValue(slug: string): unknown {\n    return this.assignments[slug]?.value;\n  }\n\n  resolveAssignment(slug: string): Assignment | undefined {\n    return this.assignments[slug];\n  }\n\n  has(slug: string): boolean {\n    return slug in this.assignments;\n  }\n\n  private persist(entry: StoredEntry): void {\n    try {\n      this.storage?.setItem(this.storageKey(), JSON.stringify(entry));\n    } catch (error) {\n      this.logger('assignments cache not persisted', error);\n    }\n  }\n\n  private storageRead(key: string): string | null {\n    try {\n      return this.storage?.getItem(key) ?? null;\n    } catch {\n      return null;\n    }\n  }\n\n  private storageKey(): string {\n    return `${ASSIGNMENTS_STORAGE_PREFIX}${this.user.userId}`;\n  }\n}\n\nfunction detectStorage(): Storage | null {\n  try {\n    const storage = globalThis.localStorage;\n    const probe = 'abmeter:probe';\n    storage.setItem(probe, '1');\n    storage.removeItem(probe);\n    return storage;\n  } catch {\n    return null;\n  }\n}\n","interface ErrorBody {\n  error?: unknown;\n  details?: { failures?: unknown[]; invalid_count?: number };\n}\n\n// Same contract as the Ruby/Python SDKs: retryable (5xx/408/429) errors\n// re-queue, partial failures and other 4xx are permanent and drop.\nexport class ApiError extends Error {\n  readonly status: number;\n  readonly details: ErrorBody['details'];\n\n  constructor(status: number, body: unknown) {\n    const parsed: ErrorBody = typeof body === 'object' && body !== null ? (body as ErrorBody) : {};\n    super(typeof parsed.error === 'string' ? parsed.error : `HTTP ${status}`);\n    this.name = 'ApiError';\n    this.status = status;\n    this.details = parsed.details;\n  }\n\n  get retryable(): boolean {\n    return this.status >= 500 || this.status === 408 || this.status === 429;\n  }\n\n  get partialFailure(): boolean {\n    return this.status === 400 && Array.isArray(this.details?.failures) && this.details.failures.length > 0;\n  }\n}\n","import { ApiError } from './api-error';\nimport {\n  BATCH_SIZE,\n  KEEPALIVE_BODY_LIMIT_BYTES,\n  MAX_RETRY_QUEUE,\n  MAX_SUBMIT_ATTEMPTS,\n} from './constants';\nimport type { ErrorCallback, Logger } from './config';\nimport type { HttpClient } from './http';\n\ntype Kind = 'exposure' | 'event';\n\ninterface QueueItem {\n  kind: Kind;\n  data: Record<string, unknown>;\n  attempts: number;\n}\n\nexport interface SubmitterOptions {\n  http: HttpClient;\n  flushIntervalMs: number;\n  batchSize?: number;\n  logger: Logger;\n  errorCallback?: ErrorCallback;\n}\n\n// Main-thread queue: setInterval + batch-size trigger, drained hard on\n// visibilitychange→hidden and pagehide (never unload/beforeunload — those\n// break bfcache and don't fire on mobile). The hidden drain uses keepalive\n// fetch with a sendBeacon fallback; a refused beacon is logged and dropped —\n// ad-blockers exist, loss is expected, never fatal.\nexport class AsyncSubmitter {\n  private readonly http: HttpClient;\n  private readonly flushIntervalMs: number;\n  private readonly batchSize: number;\n  private readonly logger: Logger;\n  private readonly errorCallback?: ErrorCallback;\n\n  private exposures: QueueItem[] = [];\n  private events: QueueItem[] = [];\n  private retryQueue: QueueItem[] = [];\n\n  private timer: ReturnType<typeof setInterval> | null = null;\n  private flushChain: Promise<void> = Promise.resolve();\n  private queuedFlush: Promise<void> | null = null;\n  private readonly onVisibilityChange = (): void => {\n    if (globalThis.document?.visibilityState === 'hidden') this.drainOnHide();\n  };\n  private readonly onPageHide = (): void => {\n    this.drainOnHide();\n  };\n\n  constructor(options: SubmitterOptions) {\n    this.http = options.http;\n    this.flushIntervalMs = options.flushIntervalMs;\n    this.batchSize = options.batchSize ?? BATCH_SIZE;\n    this.logger = options.logger;\n    this.errorCallback = options.errorCallback;\n  }\n\n  start(): void {\n    if (this.timer === null) {\n      this.timer = setInterval(() => void this.flush(), this.flushIntervalMs);\n    }\n    globalThis.document?.addEventListener('visibilitychange', this.onVisibilityChange);\n    globalThis.window?.addEventListener('pagehide', this.onPageHide);\n  }\n\n  /** Detach timer + listeners without draining. reset() is the draining teardown. */\n  stop(): void {\n    if (this.timer !== null) {\n      clearInterval(this.timer);\n      this.timer = null;\n    }\n    globalThis.document?.removeEventListener('visibilitychange', this.onVisibilityChange);\n    globalThis.window?.removeEventListener('pagehide', this.onPageHide);\n  }\n\n  queueExposure(data: Record<string, unknown>): void {\n    this.exposures.push({ kind: 'exposure', data, attempts: 0 });\n    this.flushIfFull();\n  }\n\n  queueEvent(data: Record<string, unknown>): void {\n    this.events.push({ kind: 'event', data, attempts: 0 });\n    this.flushIfFull();\n  }\n\n  pending(): number {\n    return this.exposures.length + this.events.length + this.retryQueue.length;\n  }\n\n  /**\n   * Drain the queues over the network. One retry batch is attempted per flush;\n   * items failing during this flush land in the retry queue and wait for the\n   * next one, so a failing server can't spin this loop forever.\n   *\n   * Passes are serialized, never concurrent. flush() during a running pass\n   * returns a promise for the NEXT pass — resolving early while a drain is\n   * still in flight would break callers (SPA route changes, the canonical\n   * app's pre-close barrier) that await flush() as proof nothing is queued.\n   * Callers arriving in the same window share one queued pass.\n   */\n  flush(): Promise<void> {\n    if (this.queuedFlush) return this.queuedFlush;\n\n    const pass = this.flushChain.then(() => {\n      this.queuedFlush = null;\n      return this.drain();\n    });\n    this.queuedFlush = pass;\n    this.flushChain = pass.catch(() => undefined);\n    return pass;\n  }\n\n  private async drain(): Promise<void> {\n    await this.retryOneBatch();\n    while (this.exposures.length > 0 || this.events.length > 0) {\n      if (this.exposures.length > 0) {\n        await this.submitBatch('exposure', this.exposures.splice(0, this.batchSize));\n      }\n      if (this.events.length > 0) {\n        await this.submitBatch('event', this.events.splice(0, this.batchSize));\n      }\n    }\n  }\n\n  /**\n   * Drain fully and tear down. Terminates even against a failing server: every\n   * failed batch either drops (validation, events on network error) or re-queues\n   * with attempts++ and drops at MAX_SUBMIT_ATTEMPTS.\n   */\n  async reset(options: { force?: boolean } = {}): Promise<void> {\n    this.stop();\n    if (options.force) {\n      const dropped = this.pending();\n      this.exposures = [];\n      this.events = [];\n      this.retryQueue = [];\n      if (dropped > 0) this.logger(`reset(force): dropped ${dropped} queued items`);\n      return;\n    }\n    while (this.pending() > 0) {\n      await this.flush();\n    }\n  }\n\n  /**\n   * Tab-death drain: chunked keepalive fetch (text/plain, CORS-simple) with a\n   * sendBeacon fallback when the fetch fails. Fire-and-forget by necessity —\n   * the page may be gone before any response arrives.\n   */\n  drainOnHide(): void {\n    const items = [...this.retryQueue, ...this.exposures, ...this.events];\n    this.retryQueue = [];\n    this.exposures = [];\n    this.events = [];\n\n    for (const kind of ['exposure', 'event'] as const) {\n      const rows = items.filter((item) => item.kind === kind).map((item) => item.data);\n      for (const chunk of chunkForKeepalive(rows, this.batchSize)) {\n        this.sendChunkOnHide(kind, chunk);\n      }\n    }\n  }\n\n  private sendChunkOnHide(kind: Kind, rows: unknown[]): void {\n    const beaconFallback = (): void => {\n      const accepted =\n        kind === 'exposure' ? this.http.beaconExposures(rows) : this.http.beaconEvents(rows);\n      if (!accepted) this.logger(`beacon refused; dropping ${rows.length} ${kind}s`);\n    };\n\n    try {\n      const post =\n        kind === 'exposure'\n          ? this.http.postExposures(rows, { keepalive: true })\n          : this.http.postEvents(rows, { keepalive: true });\n      post.catch(beaconFallback);\n    } catch {\n      beaconFallback();\n    }\n  }\n\n  private flushIfFull(): void {\n    if (this.exposures.length + this.events.length >= this.batchSize) void this.flush();\n  }\n\n  private async retryOneBatch(): Promise<void> {\n    if (this.retryQueue.length === 0) return;\n    const batch = this.retryQueue.splice(0, this.batchSize);\n    const byKind = new Map<Kind, QueueItem[]>();\n    for (const item of batch) {\n      const bucket = byKind.get(item.kind) ?? [];\n      bucket.push(item);\n      byKind.set(item.kind, bucket);\n    }\n    for (const [kind, items] of byKind) {\n      await this.submitBatch(kind, items);\n    }\n  }\n\n  private async submitBatch(kind: Kind, items: QueueItem[]): Promise<void> {\n    const rows = items.map((item) => item.data);\n    try {\n      if (kind === 'exposure') {\n        await this.http.postExposures(rows);\n      } else {\n        await this.http.postEvents(rows);\n      }\n    } catch (error) {\n      this.handleSubmitError(kind, items, error);\n    }\n  }\n\n  // The asymmetric retry contract shared with Ruby/Python: retryable API\n  // error → re-queue (max attempts); validation/permanent → drop; unknown\n  // network error → exposures re-queued (they feed the completeness gate),\n  // events dropped.\n  private handleSubmitError(kind: Kind, items: QueueItem[], error: unknown): void {\n    if (error instanceof ApiError) {\n      if (error.retryable) {\n        this.logger(`retryable API error for ${items.length} ${kind}s`, error.message);\n        this.requeue(items);\n      } else if (error.partialFailure) {\n        this.logger(`partial failure, dropping batch of ${items.length} ${kind}s`, error.message);\n      } else {\n        this.logger(`permanent API error, dropping ${items.length} ${kind}s`, error.message);\n      }\n    } else if (kind === 'exposure') {\n      this.logger(`network error, re-queueing ${items.length} exposures`, error);\n      this.requeue(items);\n    } else {\n      this.logger(`network error, dropping ${items.length} events`, error);\n    }\n    this.errorCallback?.(error);\n  }\n\n  private requeue(items: QueueItem[]): void {\n    for (const item of items) {\n      item.attempts += 1;\n      if (item.attempts >= MAX_SUBMIT_ATTEMPTS) {\n        this.logger(`max retries exceeded, dropping ${item.kind}`);\n      } else if (this.retryQueue.length >= MAX_RETRY_QUEUE) {\n        this.logger(`retry queue full, dropping ${item.kind}`);\n      } else {\n        this.retryQueue.push(item);\n      }\n    }\n  }\n}\n\n// Split rows so each JSON body stays under the shared keepalive/beacon quota.\n// String length approximates bytes (payloads are ASCII-dominant); the 20%\n// headroom in the limit absorbs the difference.\nexport function chunkForKeepalive(rows: unknown[], batchSize: number): unknown[][] {\n  const chunks: unknown[][] = [];\n  let current: unknown[] = [];\n  let currentBytes = 2; // []\n\n  for (const row of rows) {\n    const rowBytes = JSON.stringify(row).length + 1;\n    const overflow = currentBytes + rowBytes > KEEPALIVE_BODY_LIMIT_BYTES || current.length >= batchSize;\n    if (overflow && current.length > 0) {\n      chunks.push(current);\n      current = [];\n      currentBytes = 2;\n    }\n    current.push(row);\n    currentBytes += rowBytes;\n  }\n  if (current.length > 0) chunks.push(current);\n  return chunks;\n}\n","import { DEDUP_MAX_KEYS, DEDUP_WINDOW_MS } from './constants';\n\n// Sliding-window exposure dedup: a Map used as an insertion-ordered LRU.\n// Re-sighting a key refreshes its slot; eviction drops the oldest entry once\n// the cap is hit, so a burst of distinct keys can't grow memory unbounded.\nexport class ExposureDedup {\n  private readonly windowMs: number;\n  private readonly maxKeys: number;\n  private readonly now: () => number;\n  private readonly entries = new Map<string, number>();\n\n  constructor({\n    windowMs = DEDUP_WINDOW_MS,\n    maxKeys = DEDUP_MAX_KEYS,\n    now = Date.now,\n  }: { windowMs?: number; maxKeys?: number; now?: () => number } = {}) {\n    this.windowMs = windowMs;\n    this.maxKeys = maxKeys;\n    this.now = now;\n  }\n\n  /** True when the key was already seen inside the window; records the sighting either way. */\n  seenRecently(key: string): boolean {\n    const timestamp = this.now();\n    const seenAt = this.entries.get(key);\n    const duplicate = seenAt !== undefined && timestamp - seenAt < this.windowMs;\n\n    this.entries.delete(key);\n    this.entries.set(key, duplicate ? (seenAt as number) : timestamp);\n    this.evict();\n    return duplicate;\n  }\n\n  private evict(): void {\n    while (this.entries.size > this.maxKeys) {\n      const oldest = this.entries.keys().next().value;\n      if (oldest === undefined) return;\n      this.entries.delete(oldest);\n    }\n  }\n}\n\nexport function exposureDedupKey(exposure: {\n  user_id: string;\n  exposable_id: number;\n  audience_id: number;\n  resolved_value: unknown;\n}): string {\n  return `${exposure.user_id}|${exposure.exposable_id}|${exposure.audience_id}|${JSON.stringify(exposure.resolved_value)}`;\n}\n","import { ApiError } from './api-error';\nimport { EVENTS_PATH, EXPOSURES_PATH, USER_ASSIGNMENTS_PATH } from './constants';\nimport type { AssignmentsPayload } from './assignments-cache';\n\nexport interface AssignmentsResponse {\n  status: 200 | 304;\n  payload?: AssignmentsPayload;\n  etag?: string;\n}\n\nexport interface PostOptions {\n  /** Unload path: keepalive fetch + text/plain body (CORS-simple, no preflight). */\n  keepalive?: boolean;\n}\n\n// fetch-based client for the three browser endpoints. Non-2xx responses throw\n// ApiError; network failures propagate the underlying TypeError so the\n// submitter can apply its asymmetric unknown-error contract.\nexport class HttpClient {\n  private readonly baseUrl: string;\n  private readonly apiKey: string;\n\n  constructor({ baseUrl, apiKey }: { baseUrl: string; apiKey: string }) {\n    this.baseUrl = baseUrl;\n    this.apiKey = apiKey;\n  }\n\n  async fetchAssignments(\n    body: { user_id: string; email?: string },\n    etag?: string\n  ): Promise<AssignmentsResponse> {\n    const headers: Record<string, string> = {\n      Authorization: `Bearer ${this.apiKey}`,\n      'Content-Type': 'application/json',\n    };\n    if (etag) headers['If-None-Match'] = etag;\n\n    const response = await fetch(`${this.baseUrl}${USER_ASSIGNMENTS_PATH}`, {\n      method: 'POST',\n      headers,\n      body: JSON.stringify(body),\n    });\n\n    if (response.status === 304) return { status: 304 };\n    if (!response.ok) throw new ApiError(response.status, await parseBody(response));\n\n    return {\n      status: 200,\n      payload: (await response.json()) as AssignmentsPayload,\n      etag: response.headers.get('ETag') ?? undefined,\n    };\n  }\n\n  async postExposures(rows: unknown[], options?: PostOptions): Promise<void> {\n    await this.post(EXPOSURES_PATH, rows, options);\n  }\n\n  async postEvents(rows: unknown[], options?: PostOptions): Promise<void> {\n    await this.post(EVENTS_PATH, rows, options);\n  }\n\n  /**\n   * Last-resort transport for tab death: sendBeacon cannot set headers, so the\n   * API key travels as a query token (accepted server-side only on the two\n   * write endpoints). Returns false when the browser refuses the beacon.\n   */\n  beaconExposures(rows: unknown[]): boolean {\n    return this.beacon(EXPOSURES_PATH, rows);\n  }\n\n  beaconEvents(rows: unknown[]): boolean {\n    return this.beacon(EVENTS_PATH, rows);\n  }\n\n  private async post(path: string, rows: unknown[], options?: PostOptions): Promise<void> {\n    const keepalive = options?.keepalive ?? false;\n    const response = await fetch(`${this.baseUrl}${path}`, {\n      method: 'POST',\n      // text/plain on the keepalive path keeps the request CORS-simple (no\n      // preflight — a preflight can't complete once the page is gone).\n      headers: {\n        Authorization: `Bearer ${this.apiKey}`,\n        'Content-Type': keepalive ? 'text/plain' : 'application/json',\n      },\n      body: JSON.stringify(rows),\n      keepalive,\n    });\n    if (!response.ok) throw new ApiError(response.status, await parseBody(response));\n  }\n\n  private beacon(path: string, rows: unknown[]): boolean {\n    const sendBeacon = globalThis.navigator?.sendBeacon?.bind(globalThis.navigator);\n    if (!sendBeacon) return false;\n\n    const url = `${this.baseUrl}${path}?api_key=${encodeURIComponent(this.apiKey)}`;\n    const body = new Blob([JSON.stringify(rows)], { type: 'text/plain' });\n    try {\n      return sendBeacon(url, body);\n    } catch {\n      return false;\n    }\n  }\n}\n\nasync function parseBody(response: Response): Promise<unknown> {\n  try {\n    return await response.json();\n  } catch {\n    return undefined;\n  }\n}\n","import {\n  TRACK_ID_COOKIE_MAX_AGE_SECONDS,\n  TRACK_ID_COOKIE_NAME,\n  TRACK_ID_STORAGE_KEY,\n} from './constants';\n\nexport interface UserInput {\n  userId?: string;\n  email?: string;\n}\n\nexport interface User {\n  userId: string;\n  email?: string;\n}\n\n// When the caller supplies no id, identity is a generated track id: a random\n// UUID persisted in cookie + localStorage and sent as the user_id wire field.\n// ~122 bits of randomness make targeted impersonation of anonymous users\n// infeasible and keep real user ids out of browser traffic. Note: Safari ITP\n// caps JS-set cookies at ~7 days; long experiments should set the cookie\n// server-side.\nexport function ensureUser(input?: UserInput): User {\n  const userId = input?.userId ?? loadOrCreateTrackId();\n  return input?.email === undefined ? { userId } : { userId, email: input.email };\n}\n\nfunction loadOrCreateTrackId(): string {\n  const existing = readCookie(TRACK_ID_COOKIE_NAME) ?? readStorage(TRACK_ID_STORAGE_KEY);\n  const trackId = existing ?? generateUuid();\n  persistTrackId(trackId);\n  return trackId;\n}\n\nfunction persistTrackId(trackId: string): void {\n  writeCookie(TRACK_ID_COOKIE_NAME, trackId);\n  writeStorage(TRACK_ID_STORAGE_KEY, trackId);\n}\n\nfunction generateUuid(): string {\n  const cryptoApi = globalThis.crypto;\n  if (cryptoApi?.randomUUID) return cryptoApi.randomUUID();\n\n  const bytes = new Uint8Array(16);\n  if (cryptoApi?.getRandomValues) {\n    cryptoApi.getRandomValues(bytes);\n  } else {\n    for (let i = 0; i < bytes.length; i += 1) bytes[i] = Math.floor(Math.random() * 256);\n  }\n  bytes[6] = ((bytes[6] as number) & 0x0f) | 0x40;\n  bytes[8] = ((bytes[8] as number) & 0x3f) | 0x80;\n  const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n\nfunction readCookie(name: string): string | null {\n  if (typeof document === 'undefined') return null;\n  const match = document.cookie\n    .split(';')\n    .map((part) => part.trim())\n    .find((part) => part.startsWith(`${name}=`));\n  return match ? decodeURIComponent(match.slice(name.length + 1)) || null : null;\n}\n\nfunction writeCookie(name: string, value: string): void {\n  if (typeof document === 'undefined') return;\n  try {\n    document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${TRACK_ID_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;\n  } catch {\n    // Cookies blocked — localStorage (or memory for the session) still holds the id.\n  }\n}\n\nfunction readStorage(key: string): string | null {\n  try {\n    return globalThis.localStorage?.getItem(key) ?? null;\n  } catch {\n    return null;\n  }\n}\n\nfunction writeStorage(key: string, value: string): void {\n  try {\n    globalThis.localStorage?.setItem(key, value);\n  } catch {\n    // localStorage blocked (Safari private mode, extensions) — cookie may still work.\n  }\n}\n","import type { ErrorCallback, Logger } from './config';\n\nexport interface SafetyHandlers {\n  logger?: Logger;\n  errorCallback?: ErrorCallback;\n}\n\n// The error boundary around the public API: never let SDK internals crash the\n// host page, but never swallow silently either — every failure is logged and\n// forwarded to errorCallback so a salt-bug-class regression stays visible.\nexport function guard<R>(operation: string, fallback: R, handlers: SafetyHandlers, fn: () => R): R {\n  try {\n    return fn();\n  } catch (error) {\n    report(operation, error, handlers);\n    return fallback;\n  }\n}\n\nexport async function guardAsync<R>(\n  operation: string,\n  fallback: R,\n  handlers: SafetyHandlers,\n  fn: () => Promise<R>\n): Promise<R> {\n  try {\n    return await fn();\n  } catch (error) {\n    report(operation, error, handlers);\n    return fallback;\n  }\n}\n\nfunction report(operation: string, error: unknown, handlers: SafetyHandlers): void {\n  if (handlers.logger) {\n    handlers.logger(`${operation} failed`, error);\n  } else {\n    console.error(`[abmeter] ${operation} failed`, error);\n  }\n  try {\n    handlers.errorCallback?.(error);\n  } catch {\n    // A throwing errorCallback must not take the page down with it.\n  }\n}\n","import { DEFAULT_BASE_URL, DEFAULT_FLUSH_INTERVAL_MS } from './constants';\nimport type { UserInput } from './user';\n\nexport type Logger = (message: string, payload?: unknown) => void;\nexport type ErrorCallback = (error: unknown) => void;\n\nexport interface ConfigInput {\n  apiKey: string;\n  baseUrl?: string;\n  user?: UserInput;\n  /** Milliseconds between background flushes. */\n  flushInterval?: number;\n  logger?: Logger;\n  errorCallback?: ErrorCallback;\n}\n\nexport interface Config {\n  apiKey: string;\n  baseUrl: string;\n  flushIntervalMs: number;\n  logger: Logger;\n  errorCallback?: ErrorCallback;\n}\n\nconst PUBLISHABLE_KEY_PREFIX = 'pk_';\n\nexport function resolveConfig(input: ConfigInput): Config {\n  if (!input || typeof input.apiKey !== 'string' || input.apiKey.length === 0) {\n    throw new Error('abmeter.configure: apiKey is required');\n  }\n\n  // Refuse, don't warn: anything embedded in a browser bundle is readable by\n  // anyone, so this SDK only accepts keys that are safe to expose. A secret\n  // key here would grant full account access to every visitor.\n  if (!input.apiKey.startsWith(PUBLISHABLE_KEY_PREFIX)) {\n    throw new Error(\n      'abmeter.configure: apiKey must be a publishable key (pk_...) — mint one on the ' +\n        'Lab API Keys page. Secret keys (api-...) must never be shipped in browser code: ' +\n        'anyone can read them from your page source and gain full access to your ABMeter account.'\n    );\n  }\n\n  return {\n    apiKey: input.apiKey,\n    baseUrl: (input.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, ''),\n    flushIntervalMs: input.flushInterval ?? DEFAULT_FLUSH_INTERVAL_MS,\n    logger: input.logger ?? defaultLogger,\n    errorCallback: input.errorCallback,\n  };\n}\n\nfunction defaultLogger(message: string, payload?: unknown): void {\n  if (payload === undefined) {\n    console.warn(`[abmeter] ${message}`);\n  } else {\n    console.warn(`[abmeter] ${message}`, payload);\n  }\n}\n","import { AssignmentsCache } from './assignments-cache';\nimport { AsyncSubmitter } from './async-submitter';\nimport { ExposureDedup, exposureDedupKey } from './dedup';\nimport { HttpClient } from './http';\nimport { ensureUser } from './user';\nimport { guard, guardAsync } from './error-safety';\nimport { resolveConfig } from './config';\nimport type { Config, ConfigInput } from './config';\nimport type { User } from './user';\n\nexport interface ExposureRecord {\n  parameter_id: number;\n  space_id: number;\n  resolved_value: unknown;\n  user_id: string;\n  exposable_type: 'Experiment';\n  exposable_id: number;\n  audience_id: number;\n  resolved_at: string;\n}\n\ninterface ClientState {\n  config: Config;\n  user: User;\n  cache: AssignmentsCache;\n  dedup: ExposureDedup;\n  submitter: AsyncSubmitter;\n  refreshPromise: Promise<void>;\n}\n\nlet state: ClientState | null = null;\n\n/**\n * Initialize the SDK singleton. Cache-then-network: hydrates synchronously\n * from localStorage when a cached map exists, then refreshes in the background\n * (await ready() to know the refresh settled). Throws on misconfiguration —\n * a bad apiKey must be loud, not error-safe.\n */\nexport function configure(input: ConfigInput): void {\n  const config = resolveConfig(input);\n  const user = ensureUser(input.user);\n  const http = new HttpClient({ baseUrl: config.baseUrl, apiKey: config.apiKey });\n  const cache = new AssignmentsCache({ http, user, logger: config.logger });\n  const submitter = new AsyncSubmitter({\n    http,\n    flushIntervalMs: config.flushIntervalMs,\n    logger: config.logger,\n    errorCallback: config.errorCallback,\n  });\n\n  // Reconfiguring detaches the previous submitter and lets it drain itself.\n  // configure() is synchronous, so the drain cannot be awaited or reported —\n  // but dropping the queue would lose exposures and events already collected,\n  // a silent hole in the customer's results, while a stray background drain\n  // only costs bandwidth. reset({ force: true }) is how a caller opts into\n  // discarding, and await reset() is how one gets a guaranteed drain; neither\n  // should be the accidental default here. Best-effort: reset() detaches the\n  // unload listeners first, so a page dying mid-drain still loses the tail.\n  if (state) {\n    const previous = state.submitter;\n    const pending = previous.pending();\n    if (pending > 0) state.config.logger(`configure: draining ${pending} queued items from the previous configuration`);\n    void previous.reset().catch(() => {\n      // Already logged by the submitter; a failed drain must not break configure().\n    });\n  }\n\n  cache.hydrateFromStorage();\n  submitter.start();\n  const refreshPromise = cache.refresh().catch((error) => {\n    config.logger('assignments refresh failed', error);\n    try {\n      config.errorCallback?.(error);\n    } catch {\n      // errorCallback failures must not surface here\n    }\n  });\n\n  state = { config, user, cache, dedup: new ExposureDedup(), submitter, refreshPromise };\n}\n\n/** Resolves when the background assignments refresh has settled (fetched or failed). */\nexport function ready(): Promise<void> {\n  return state?.refreshPromise ?? Promise.resolve();\n}\n\n/**\n * Resolved value for this user, or undefined when unknown/unconfigured. Queues\n * an exposure lazily — only experiment resolutions carry exposure metadata,\n * and repeats inside the dedup window are not re-queued.\n */\nexport function resolveParameter(slug: string): unknown {\n  return guard('resolveParameter', undefined, handlers(), () => {\n    const current = requireState();\n    const assignment = current.cache.resolveAssignment(slug);\n    if (!assignment) {\n      current.config.logger(`resolveParameter: unknown parameter '${slug}'`);\n      return undefined;\n    }\n\n    const exposure = buildExposure(current, slug);\n    if (exposure && !current.dedup.seenRecently(exposureDedupKey(exposure))) {\n      current.submitter.queueExposure({ ...exposure });\n    }\n    return assignment.value;\n  });\n}\n\n/** The exposure record resolveParameter would submit, or null — without queueing anything. */\nexport function getExposure(slug: string): ExposureRecord | null {\n  return guard('getExposure', null, handlers(), () => buildExposure(requireState(), slug));\n}\n\n/**\n * Queue an event for the configured user.\n *\n * Deliberately takes no user id, unlike the server-side SDKs: there one\n * process serves every user, so each call must say who it is for, while a\n * page has exactly the one user configure() established. An override would\n * only ever detach the event — results attribute events to a visitor by\n * matching the id their exposure was recorded under, so an event under any\n * other id is stored, counted, and never joined.\n */\nexport function trackEvent(eventSlug: string, customFields?: Record<string, unknown>): void {\n  guard('trackEvent', undefined, handlers(), () => {\n    const current = requireState();\n    current.submitter.queueEvent({\n      event_slug: eventSlug,\n      user_id: current.user.userId,\n      occurred_at: new Date().toISOString(),\n      custom_fields: customFields ?? {},\n    });\n  });\n}\n\n/** Drain the queue now (e.g. on SPA route changes). */\nexport function flush(): Promise<void> {\n  return guardAsync('flush', undefined, handlers(), async () => {\n    await state?.submitter.flush();\n  });\n}\n\n/**\n * Drain fully and tear down timers/listeners; configure() again to restart.\n * force drops the queue instead of draining it.\n */\nexport function reset(options: { force?: boolean } = {}): Promise<void> {\n  return guardAsync('reset', undefined, handlers(), async () => {\n    const current = state;\n    state = null;\n    await current?.submitter.reset(options);\n  });\n}\n\nfunction buildExposure(current: ClientState, slug: string): ExposureRecord | null {\n  const assignment = current.cache.resolveAssignment(slug);\n  if (!assignment?.exposure) return null;\n\n  return {\n    parameter_id: assignment.parameter_id,\n    space_id: assignment.space_id,\n    resolved_value: assignment.value,\n    user_id: current.user.userId,\n    exposable_type: assignment.exposure.exposable_type,\n    exposable_id: assignment.exposure.exposable_id,\n    audience_id: assignment.exposure.audience_id,\n    resolved_at: new Date().toISOString(),\n  };\n}\n\nfunction requireState(): ClientState {\n  if (!state) throw new Error('abmeter is not configured — call abmeter.configure(...) first');\n  return state;\n}\n\nfunction handlers(): { logger?: (message: string, payload?: unknown) => void; errorCallback?: (error: unknown) => void } {\n  return state ? { logger: state.config.logger, errorCallback: state.config.errorCallback } : {};\n}\n","export const VERSION = '0.2.2';\n"],"mappings":"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","names":["handlers"]}