{"version":3,"sources":["../src/heuristics/sink.ts"],"names":["ev","onActivity"],"mappings":"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","file":"heuristics.cjs","sourcesContent":["/**\n * In-browser heuristics analytics sink for agent traffic.\n *\n * Agent bridge mutations emit `AutoActivityEvent`s into the in-page\n * `@particle-academy/fancy-auto-common` activity bus (presence / cursors).\n * This sink — living in THIS package so it shares the SAME bundled\n * `fancy-auto-common` module instance the bridges emit into — subscribes to\n * that bus and POSTs each event to the `fancy-heuristics` `/collect` endpoint\n * as `actor:\"agent\"`, so agent traffic shows up in the heuristics dashboard.\n *\n * It does NOT depend on `fancy-heuristics` / `fancy-heuristics-js`: it only\n * emits the frozen wire shape over HTTP. The mapping mirrors\n * `fancy-heuristics-js/src/agent.ts` `mapActivityToEvent` — keep parity.\n */\nimport { onActivity, type AgentActivityEvent } from \"../presence\";\n\n/** What kind of interaction an event captures (mirrors fancy-heuristics-js). */\ntype HeuristicsEventKind = \"pageview\" | \"click\" | \"scroll\" | \"pointer\" | \"dwell\";\n\n/** Who produced the interaction. */\ntype HeuristicsActor = \"human\" | \"agent\";\n\n/**\n * A single interaction event — mirrors `fancy-heuristics-js` `HeuristicsEvent`.\n * Optional fields are omitted (never `null`) when not relevant to the `kind`.\n */\nexport interface HeuristicsEvent {\n  kind: HeuristicsEventKind;\n  actor: HeuristicsActor;\n  /** location.pathname at capture time. */\n  path: string;\n  /** ms epoch. */\n  ts: number;\n  x?: number;\n  y?: number;\n  vw?: number;\n  vh?: number;\n  scrollPct?: number;\n  dwellMs?: number;\n  targetId?: string;\n  label?: string;\n  meta?: Record<string, unknown>;\n}\n\n/** The batched POST body sent to `${endpoint}/collect`. */\nexport interface CollectBatch {\n  siteKey: string;\n  sessionId: string;\n  events: HeuristicsEvent[];\n}\n\nexport interface AttachHeuristicsSinkOptions {\n  /** Base URL, e.g. \"/heuristics\". POSTs to `${endpoint}/collect`. */\n  endpoint: string;\n  /** Identifies the site to the ingestion endpoint. */\n  siteKey: string;\n  /** Stable session id. Default: a generated \"agent-<rand>\" per attach. */\n  sessionId?: string;\n  /** Resolves the current path. Default: `() => location.pathname`. */\n  path?: () => string;\n  /** Which activity sources to record. Default \"all\". */\n  source?: \"agent\" | \"flow\" | \"all\";\n  /** Flush interval in ms. Default 2000. */\n  batchMs?: number;\n}\n\nfunction numericMeta(\n  meta: Record<string, unknown> | undefined,\n  key: string,\n): number | undefined {\n  const v = meta?.[key];\n  return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n}\n\n/**\n * Map one activity event to an `actor:\"agent\"` HeuristicsEvent. Activities\n * carrying a finite `meta.dwellMs` become `kind:\"dwell\"`; everything else is a\n * discrete `kind:\"click\"` with x/y/vw/vh pulled from numeric meta (default 0).\n * Mirrors `fancy-heuristics-js/src/agent.ts` `mapActivityToEvent`.\n */\nexport function mapActivityToEvent(\n  e: AgentActivityEvent,\n  path: string,\n): HeuristicsEvent {\n  const source = e.source ?? \"agent\";\n  const targetId = e.target?.elementId;\n  const label = e.target?.label;\n  const meta: Record<string, unknown> = {\n    action: e.action,\n    agentId: e.agentId,\n    source,\n    kind: e.target?.kind,\n  };\n  const dwellMs = numericMeta(e.meta, \"dwellMs\");\n\n  if (dwellMs !== undefined) {\n    const ev: HeuristicsEvent = {\n      kind: \"dwell\",\n      actor: \"agent\",\n      path,\n      ts: e.timestamp,\n      dwellMs,\n      meta,\n    };\n    if (targetId !== undefined) ev.targetId = targetId;\n    if (label !== undefined) ev.label = label;\n    return ev;\n  }\n\n  const ev: HeuristicsEvent = {\n    kind: \"click\",\n    actor: \"agent\",\n    path,\n    ts: e.timestamp,\n    x: numericMeta(e.meta, \"x\") ?? 0,\n    y: numericMeta(e.meta, \"y\") ?? 0,\n    vw: numericMeta(e.meta, \"vw\") ?? 0,\n    vh: numericMeta(e.meta, \"vh\") ?? 0,\n    meta,\n  };\n  if (targetId !== undefined) ev.targetId = targetId;\n  if (label !== undefined) ev.label = label;\n  return ev;\n}\n\nfunction randomId(): string {\n  return Math.random().toString(36).slice(2, 10);\n}\n\n/**\n * Subscribe to the in-page agent activity bus and forward each matching event\n * to the heuristics `/collect` endpoint as `actor:\"agent\"`. SSR-safe (returns a\n * no-op when there is no `window`). Returns an unsubscribe that flushes the\n * buffer and detaches all listeners/timers.\n */\nexport function attachHeuristicsSink(\n  opts: AttachHeuristicsSinkOptions,\n): () => void {\n  // Browser guard — SSR-safe no-op.\n  if (typeof window === \"undefined\") return () => {};\n\n  const endpoint = opts.endpoint.replace(/\\/$/, \"\");\n  const url = `${endpoint}/collect`;\n  const siteKey = opts.siteKey;\n  const sessionId = opts.sessionId ?? `agent-${randomId()}`;\n  const getPath = opts.path ?? (() => location.pathname);\n  const source = opts.source ?? \"all\";\n  const batchMs = opts.batchMs ?? 2000;\n\n  // onActivity's `source` filter is strict-equality; only set it when we want a\n  // single source. \"all\" subscribes unfiltered.\n  const filter = source === \"all\" ? undefined : { source };\n\n  let buffer: HeuristicsEvent[] = [];\n\n  function flush(): void {\n    if (buffer.length === 0) return;\n    const events = buffer;\n    buffer = [];\n    const batch: CollectBatch = { siteKey, sessionId, events };\n    const body = JSON.stringify(batch);\n    try {\n      if (\n        typeof navigator !== \"undefined\" &&\n        typeof navigator.sendBeacon === \"function\"\n      ) {\n        const blob = new Blob([body], { type: \"application/json\" });\n        navigator.sendBeacon(url, blob);\n        return;\n      }\n    } catch {\n      // fall through to fetch\n    }\n    try {\n      void fetch(url, {\n        method: \"POST\",\n        headers: { \"content-type\": \"application/json\" },\n        body,\n        keepalive: true,\n      });\n    } catch {\n      // never throw from a flush\n    }\n  }\n\n  const unsubscribeActivity = onActivity((e) => {\n    try {\n      buffer.push(mapActivityToEvent(e, getPath()));\n    } catch {\n      // never let a malformed event break the bus\n    }\n  }, filter);\n\n  const timer: ReturnType<typeof setInterval> = setInterval(flush, batchMs);\n\n  const onPageHide = () => flush();\n  const onVisibility = () => {\n    if (document.visibilityState === \"hidden\") flush();\n  };\n  window.addEventListener(\"pagehide\", onPageHide);\n  document.addEventListener(\"visibilitychange\", onVisibility);\n\n  return () => {\n    unsubscribeActivity();\n    window.removeEventListener(\"pagehide\", onPageHide);\n    document.removeEventListener(\"visibilitychange\", onVisibility);\n    clearInterval(timer);\n    flush();\n  };\n}\n"]}