{"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser.ts"],"sourcesContent":["import type { DrainContext } from './types'\nimport type { DrainPipelineOptions, PipelineDrainFn } from './pipeline'\nimport { createDrainPipeline } from './pipeline'\n\nexport interface BrowserDrainConfig {\n  /** URL of the server ingest endpoint */\n  endpoint: string\n  /** Custom headers sent with each fetch request (e.g. Authorization, X-API-Key). Not applied to sendBeacon — see `useBeacon`. */\n  headers?: Record<string, string>\n  /** Request timeout in milliseconds. @default 5000 */\n  timeout?: number\n  /** Use sendBeacon when the page is hidden. @default true */\n  useBeacon?: boolean\n}\n\nexport interface BrowserLogDrainOptions {\n  /** Browser drain configuration (endpoint is required) */\n  drain: BrowserDrainConfig\n  /** Pipeline configuration overrides */\n  pipeline?: DrainPipelineOptions<DrainContext>\n  /** Auto-register visibilitychange flush listener. @default true */\n  autoFlush?: boolean\n}\n\n/**\n * Create a low-level browser drain transport function.\n *\n * Returns a function compatible with `createDrainPipeline` that sends batches\n * to the configured endpoint via `fetch` (with `keepalive: true`) or\n * `navigator.sendBeacon` when the page is hidden.\n *\n * @example\n * ```ts\n * import { createBrowserDrain } from '@safaricom-mxl/log/browser'\n * import { createDrainPipeline } from '@safaricom-mxl/log/pipeline'\n *\n * const pipeline = createDrainPipeline({ batch: { size: 50 } })\n * const drain = pipeline(createBrowserDrain({ endpoint: '/api/logs' }))\n * ```\n */\nexport function createBrowserDrain(config: BrowserDrainConfig): (batch: DrainContext[]) => Promise<void> {\n  const { endpoint, headers: customHeaders, timeout = 5000, useBeacon = true } = config\n\n  return async (batch: DrainContext[]): Promise<void> => {\n    if (batch.length === 0) return\n\n    const body = JSON.stringify(batch)\n\n    if (\n      useBeacon\n      && typeof document !== 'undefined'\n      && document.visibilityState === 'hidden'\n      && typeof navigator !== 'undefined'\n      && typeof navigator.sendBeacon === 'function'\n    ) {\n      const queued = navigator.sendBeacon(endpoint, new Blob([body], { type: 'application/json' }))\n      if (!queued) {\n        throw new Error('[mxllog/browser] sendBeacon failed — payload may exceed browser limit')\n      }\n      return\n    }\n\n    const controller = new AbortController()\n    const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n    try {\n      const response = await fetch(endpoint, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json', ...customHeaders },\n        body,\n        signal: controller.signal,\n        keepalive: true,\n        credentials: 'same-origin',\n      })\n\n      if (!response.ok) {\n        throw new Error(`[mxllog/browser] Server responded with ${response.status}`)\n      }\n    } finally {\n      clearTimeout(timeoutId)\n    }\n  }\n}\n\n/**\n * Create a pre-composed browser log drain with pipeline, batching, and auto-flush.\n *\n * Returns a `PipelineDrainFn<DrainContext>` directly usable with `initLogger({ drain })`.\n *\n * @example\n * ```ts\n * import { initLogger, log } from '@safaricom-mxl/log'\n * import { createBrowserLogDrain } from '@safaricom-mxl/log/browser'\n *\n * const drain = createBrowserLogDrain({\n *   drain: { endpoint: '/api/logs' },\n * })\n * initLogger({ drain })\n *\n * log.info({ action: 'page_view', path: location.pathname })\n * ```\n */\nexport function createBrowserLogDrain(options: BrowserLogDrainOptions): PipelineDrainFn<DrainContext> & { dispose: () => void } {\n  const { autoFlush = true } = options\n\n  const pipeline = createDrainPipeline<DrainContext>({\n    batch: { size: 25, intervalMs: 2000 },\n    retry: { maxAttempts: 2 },\n    ...options.pipeline,\n  })\n\n  const drain = pipeline(createBrowserDrain(options.drain)) as PipelineDrainFn<DrainContext> & { dispose: () => void }\n\n  let onVisibilityChange: (() => void) | undefined\n\n  if (autoFlush && typeof document !== 'undefined') {\n    onVisibilityChange = () => {\n      if (document.visibilityState === 'hidden') {\n        drain.flush()\n      }\n    }\n    document.addEventListener('visibilitychange', onVisibilityChange)\n  }\n\n  drain.dispose = () => {\n    if (onVisibilityChange) {\n      document.removeEventListener('visibilitychange', onVisibilityChange)\n      onVisibilityChange = undefined\n    }\n  }\n\n  return drain\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwCA,SAAgB,mBAAmB,QAAsE;CACvG,MAAM,EAAE,UAAU,SAAS,eAAe,UAAU,KAAM,YAAY,SAAS;AAE/E,QAAO,OAAO,UAAyC;AACrD,MAAI,MAAM,WAAW,EAAG;EAExB,MAAM,OAAO,KAAK,UAAU,MAAM;AAElC,MACE,aACG,OAAO,aAAa,eACpB,SAAS,oBAAoB,YAC7B,OAAO,cAAc,eACrB,OAAO,UAAU,eAAe,YACnC;AAEA,OAAI,CADW,UAAU,WAAW,UAAU,IAAI,KAAK,CAAC,KAAK,EAAE,EAAE,MAAM,oBAAoB,CAAC,CAAC,CAE3F,OAAM,IAAI,MAAM,wEAAwE;AAE1F;;EAGF,MAAM,aAAa,IAAI,iBAAiB;EACxC,MAAM,YAAY,iBAAiB,WAAW,OAAO,EAAE,QAAQ;AAE/D,MAAI;GACF,MAAM,WAAW,MAAM,MAAM,UAAU;IACrC,QAAQ;IACR,SAAS;KAAE,gBAAgB;KAAoB,GAAG;KAAe;IACjE;IACA,QAAQ,WAAW;IACnB,WAAW;IACX,aAAa;IACd,CAAC;AAEF,OAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,0CAA0C,SAAS,SAAS;YAEtE;AACR,gBAAa,UAAU;;;;;;;;;;;;;;;;;;;;;;AAuB7B,SAAgB,sBAAsB,SAA0F;CAC9H,MAAM,EAAE,YAAY,SAAS;CAQ7B,MAAM,QANW,oBAAkC;EACjD,OAAO;GAAE,MAAM;GAAI,YAAY;GAAM;EACrC,OAAO,EAAE,aAAa,GAAG;EACzB,GAAG,QAAQ;EACZ,CAAC,CAEqB,mBAAmB,QAAQ,MAAM,CAAC;CAEzD,IAAI;AAEJ,KAAI,aAAa,OAAO,aAAa,aAAa;AAChD,6BAA2B;AACzB,OAAI,SAAS,oBAAoB,SAC/B,OAAM,OAAO;;AAGjB,WAAS,iBAAiB,oBAAoB,mBAAmB;;AAGnE,OAAM,gBAAgB;AACpB,MAAI,oBAAoB;AACtB,YAAS,oBAAoB,oBAAoB,mBAAmB;AACpE,wBAAqB,KAAA;;;AAIzB,QAAO"}