{"version":3,"sources":["../src/interceptor.ts","../src/types/index.ts","../src/index.ts"],"sourcesContent":["import type { WalkerOS, Source, Collector } from '@walkeros/core';\nimport { isArray, isObject, isString, tryCatch } from '@walkeros/core';\n\n// Global flag to prevent infinite loops\nlet isProcessing = false;\n\n/**\n * DataLayer interceptor - handles dataLayer.push interception and event transformation\n */\nexport function interceptDataLayer(\n  push: Collector.PushFn,\n  config: Source.Config,\n  win: Record<string, unknown> = globalThis.window as unknown as Record<\n    string,\n    unknown\n  >,\n): void {\n  const settings = config.settings as {\n    name?: string;\n    prefix?: string;\n    filter?: (event: unknown) => boolean;\n  };\n  const dataLayerName = settings?.name || 'dataLayer';\n\n  // Ensure dataLayer exists\n  if (!win[dataLayerName]) {\n    win[dataLayerName] = [];\n  }\n\n  const dataLayer = win[dataLayerName] as unknown[];\n  if (!Array.isArray(dataLayer)) return;\n\n  // Store original push\n  const originalPush = dataLayer.push.bind(dataLayer);\n\n  // Override push with event processing\n  dataLayer.push = function (...args: unknown[]): number {\n    // Prevent infinite loops\n    if (isProcessing) {\n      return originalPush(...args);\n    }\n\n    isProcessing = true;\n    try {\n      // Process each argument\n      for (const arg of args) {\n        processEvent(push, settings, arg);\n      }\n    } finally {\n      isProcessing = false;\n    }\n\n    // Call original push\n    return originalPush(...args);\n  };\n}\n\n/**\n * Process existing events on initialization\n */\nexport function processExistingEvents(\n  push: Collector.PushFn,\n  config: Source.Config,\n  limit?: number,\n  win: Record<string, unknown> = globalThis.window as unknown as Record<\n    string,\n    unknown\n  >,\n): void {\n  const settings = config.settings as {\n    name?: string;\n    prefix?: string;\n    filter?: (event: unknown) => boolean;\n  };\n  const dataLayerName = settings?.name || 'dataLayer';\n  const dataLayer = win[dataLayerName] as unknown[];\n\n  if (!Array.isArray(dataLayer)) return;\n\n  // Prevent loops during initialization\n  if (isProcessing) return;\n\n  isProcessing = true;\n  try {\n    const count = limit ?? dataLayer.length;\n    for (let i = 0; i < count; i++) {\n      processEvent(push, settings, dataLayer[i]);\n    }\n  } finally {\n    isProcessing = false;\n  }\n}\n\n/**\n * Process a single event - handles filtering, transformation, and WalkerOS event creation\n */\nfunction processEvent(\n  push: Collector.PushFn,\n  settings: { prefix?: string; filter?: (event: unknown) => boolean } = {},\n  rawEvent: unknown,\n): void {\n  // Apply filter if provided\n  if (settings.filter) {\n    const filterFn = tryCatch(\n      () => settings.filter!(rawEvent),\n      () => false, // If filter throws, don't skip the event\n    );\n    const filterResult = filterFn();\n    if (filterResult === true) {\n      return; // Skip filtered events\n    }\n  }\n\n  // Transform the event (handles gtag format and direct objects)\n  const transformedEvent = transformDataLayerEvent(rawEvent);\n  if (!transformedEvent) {\n    return; // Skip invalid events\n  }\n\n  const prefix = settings.prefix || 'dataLayer';\n  const eventName = `${prefix} ${transformedEvent.name}`;\n\n  // Create partial WalkerOS event structure (collector will enrich it)\n  const { name: _name, ...data } = transformedEvent;\n  const partialEvent: WalkerOS.DeepPartialEvent = {\n    name: eventName,\n    data: data as WalkerOS.Properties,\n  };\n\n  // Push to collector\n  tryCatch(\n    () => push(partialEvent),\n    () => {}, // Silently handle push errors\n  )();\n}\n\n/**\n * Transform dataLayer events to standardized format\n * Handles: gtag arguments, direct objects, existing events\n */\nfunction transformDataLayerEvent(\n  rawEvent: unknown,\n): { name: string; [key: string]: unknown } | null {\n  // Handle direct object format: { event: 'test', data: 'value' }\n  if (isObject(rawEvent) && isString(rawEvent.event)) {\n    const { event, ...rest } = rawEvent;\n    return { name: event, ...rest };\n  }\n\n  // Handle gtag argument format: ['consent', 'update', { ad_storage: 'granted' }]\n  if (isArray(rawEvent) && rawEvent.length >= 2) {\n    return transformGtagArgs(rawEvent);\n  }\n\n  // Handle arguments object (from gtag function calls)\n  if (isGtagArguments(rawEvent)) {\n    const argsArray = Array.from(rawEvent as ArrayLike<unknown>);\n    return transformGtagArgs(argsArray);\n  }\n\n  return null;\n}\n\n/**\n * Transform gtag-style arguments to event object\n * ['consent', 'update', { ad_storage: 'granted' }] → { event: 'consent update', ad_storage: 'granted' }\n */\nfunction transformGtagArgs(\n  args: unknown[],\n): { name: string; [key: string]: unknown } | null {\n  const [command, action, params] = args;\n\n  if (!isString(command)) return null;\n\n  let eventName: string;\n  let eventData: Record<string, unknown> = {};\n\n  switch (command) {\n    case 'consent':\n      // Consent requires action and params\n      if (!isString(action) || args.length < 3) return null;\n      // Only allow 'default' and 'update' actions for consent\n      if (action !== 'default' && action !== 'update') return null;\n      // Params must be a valid object (not null)\n      if (!isObject(params) || params === null) return null;\n\n      eventName = `${command} ${action}`;\n      eventData = { ...params };\n      break;\n\n    case 'event':\n      // Event requires at least action parameter\n      if (!isString(action)) return null;\n      eventName = action;\n      if (isObject(params)) {\n        eventData = { ...params };\n      }\n      break;\n\n    case 'config':\n      // Config requires at least action parameter\n      if (!isString(action)) return null;\n      eventName = `${command} ${action}`;\n      if (isObject(params)) {\n        eventData = { ...params };\n      }\n      break;\n\n    case 'set':\n      if (isString(action)) {\n        eventName = `${command} ${action}`;\n        if (isObject(params)) {\n          eventData = { ...params };\n        }\n      } else if (isObject(action)) {\n        eventName = `${command} custom`;\n        eventData = { ...action };\n      } else {\n        return null;\n      }\n      break;\n\n    default:\n      // Unknown command, ignore\n      return null;\n  }\n\n  return {\n    name: eventName,\n    ...eventData,\n  };\n}\n\n/**\n * Check if object is gtag arguments object\n */\nfunction isGtagArguments(obj: unknown): boolean {\n  return (\n    obj != null &&\n    typeof obj === 'object' &&\n    'length' in obj &&\n    typeof (obj as ArrayLike<unknown>).length === 'number' &&\n    (obj as ArrayLike<unknown>).length > 0\n  );\n}\n","import type { WalkerOS, Source, Elb } from '@walkeros/core';\nimport type { SettingsSchema, Mapping } from '../schemas';\nimport type { z } from '@walkeros/core/dev';\n\ndeclare module '@walkeros/core' {\n  interface SourceMap {\n    dataLayer: { type: 'dataLayer'; platform?: 'web' };\n  }\n}\n\ndeclare global {\n  interface Window {\n    dataLayer?: DataLayer;\n    [key: string]: DataLayer | unknown;\n  }\n}\n\nexport type DataLayer = Array<unknown>;\n\n// Base settings from Zod schema\ntype BaseSettings = z.infer<typeof SettingsSchema>;\n\n// Override filter to be actual function type (not serializable in schema)\nexport interface Settings extends Omit<BaseSettings, 'filter'> {\n  name?: string;\n  prefix?: string;\n  filter?: (event: unknown) => WalkerOS.PromiseOrValue<boolean>;\n}\n\n// InitSettings: user input (all optional)\nexport type InitSettings = Partial<Settings>;\n\nexport type { Mapping };\n\nexport type Push = Elb.Fn;\n\nexport interface Env extends Source.BaseEnv {\n  window?: Window & typeof globalThis;\n}\n\nexport type Types = Source.Types<Settings, Mapping, Push, Env, InitSettings>;\n\nexport type Config = Source.Config<Types>;\n\nexport type DataLayerEvent = {\n  event: string;\n  [key: string]: unknown;\n};\n\nexport type MappedEvent = {\n  event?: WalkerOS.DeepPartialEvent & { id: string };\n  command?: {\n    name: string;\n    data: unknown;\n  };\n};\n","import type { Source, On } from '@walkeros/core';\nimport type { Types } from './types';\nimport { interceptDataLayer, processExistingEvents } from './interceptor';\n\n// Export types for external usage\nexport * as SourceDataLayer from './types';\n\n/**\n * DataLayer source implementation using environment injection.\n *\n * This source intercepts dataLayer.push calls and transforms them to WalkerOS events.\n * It works by replacing the dataLayer.push method with a custom handler.\n */\nexport const sourceDataLayer: Source.Init<Types> = async (context) => {\n  const { config, env } = context;\n  const { elb, window: envWindow } = env;\n\n  const settings: Source.Settings<Types> = {\n    name: 'dataLayer',\n    prefix: 'dataLayer',\n    ...config?.settings,\n  };\n\n  const fullConfig: Source.Config<Types> = {\n    settings,\n  };\n\n  // Captured at init time — see init below.\n  let pendingReplayCount = 0;\n\n  // Lifecycle setup. Called by the collector eagerly after registration,\n  // regardless of `require`. The factory body above is side-effect-free.\n  const init = () => {\n    if (!envWindow) return;\n    const dataLayerName = settings.name || 'dataLayer';\n    const dl = (envWindow as Record<string, unknown>)[dataLayerName];\n    pendingReplayCount = Array.isArray(dl) ? dl.length : 0;\n\n    const win = envWindow as unknown as Record<string, unknown>;\n    interceptDataLayer(elb, fullConfig, win);\n  };\n\n  // Replay pre-existing entries when the run signal lands. The collector\n  // strictly gates on() delivery: this fires only when the source is\n  // started (`config.init === true` AND `config.require` empty). If a\n  // require gate is unmet, the run event is queued in `queueOn` by\n  // onApply and replayed when the source becomes started.\n  const handleEvent = async (event: On.Types) => {\n    if (event === 'run' && envWindow && pendingReplayCount > 0) {\n      const win = envWindow as unknown as Record<string, unknown>;\n      processExistingEvents(elb, fullConfig, pendingReplayCount, win);\n      pendingReplayCount = 0;\n    }\n  };\n\n  return {\n    type: 'dataLayer',\n    config: fullConfig,\n    push: elb,\n    on: handleEvent,\n    init,\n    destroy: async () => {\n      // Cleanup: restore original dataLayer.push if possible\n      const dataLayerName = settings.name || 'dataLayer';\n      if (\n        envWindow &&\n        envWindow[dataLayerName] &&\n        Array.isArray(envWindow[dataLayerName])\n      ) {\n        // Note: Complete restoration would require storing original push method\n        // For now, we'll just document this limitation\n      }\n    },\n  };\n};\n\nexport default sourceDataLayer;\n"],"mappings":";AACA,SAAS,SAAS,UAAU,UAAU,gBAAgB;AAGtD,IAAI,eAAe;AAKZ,SAAS,mBACd,MACA,QACA,MAA+B,WAAW,QAIpC;AACN,QAAM,WAAW,OAAO;AAKxB,QAAM,iBAAgB,qCAAU,SAAQ;AAGxC,MAAI,CAAC,IAAI,aAAa,GAAG;AACvB,QAAI,aAAa,IAAI,CAAC;AAAA,EACxB;AAEA,QAAM,YAAY,IAAI,aAAa;AACnC,MAAI,CAAC,MAAM,QAAQ,SAAS,EAAG;AAG/B,QAAM,eAAe,UAAU,KAAK,KAAK,SAAS;AAGlD,YAAU,OAAO,YAAa,MAAyB;AAErD,QAAI,cAAc;AAChB,aAAO,aAAa,GAAG,IAAI;AAAA,IAC7B;AAEA,mBAAe;AACf,QAAI;AAEF,iBAAW,OAAO,MAAM;AACtB,qBAAa,MAAM,UAAU,GAAG;AAAA,MAClC;AAAA,IACF,UAAE;AACA,qBAAe;AAAA,IACjB;AAGA,WAAO,aAAa,GAAG,IAAI;AAAA,EAC7B;AACF;AAKO,SAAS,sBACd,MACA,QACA,OACA,MAA+B,WAAW,QAIpC;AACN,QAAM,WAAW,OAAO;AAKxB,QAAM,iBAAgB,qCAAU,SAAQ;AACxC,QAAM,YAAY,IAAI,aAAa;AAEnC,MAAI,CAAC,MAAM,QAAQ,SAAS,EAAG;AAG/B,MAAI,aAAc;AAElB,iBAAe;AACf,MAAI;AACF,UAAM,QAAQ,wBAAS,UAAU;AACjC,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,mBAAa,MAAM,UAAU,UAAU,CAAC,CAAC;AAAA,IAC3C;AAAA,EACF,UAAE;AACA,mBAAe;AAAA,EACjB;AACF;AAKA,SAAS,aACP,MACA,WAAsE,CAAC,GACvE,UACM;AAEN,MAAI,SAAS,QAAQ;AACnB,UAAM,WAAW;AAAA,MACf,MAAM,SAAS,OAAQ,QAAQ;AAAA,MAC/B,MAAM;AAAA;AAAA,IACR;AACA,UAAM,eAAe,SAAS;AAC9B,QAAI,iBAAiB,MAAM;AACzB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,mBAAmB,wBAAwB,QAAQ;AACzD,MAAI,CAAC,kBAAkB;AACrB;AAAA,EACF;AAEA,QAAM,SAAS,SAAS,UAAU;AAClC,QAAM,YAAY,GAAG,MAAM,IAAI,iBAAiB,IAAI;AAGpD,QAAM,EAAE,MAAM,OAAO,GAAG,KAAK,IAAI;AACjC,QAAM,eAA0C;AAAA,IAC9C,MAAM;AAAA,IACN;AAAA,EACF;AAGA;AAAA,IACE,MAAM,KAAK,YAAY;AAAA,IACvB,MAAM;AAAA,IAAC;AAAA;AAAA,EACT,EAAE;AACJ;AAMA,SAAS,wBACP,UACiD;AAEjD,MAAI,SAAS,QAAQ,KAAK,SAAS,SAAS,KAAK,GAAG;AAClD,UAAM,EAAE,OAAO,GAAG,KAAK,IAAI;AAC3B,WAAO,EAAE,MAAM,OAAO,GAAG,KAAK;AAAA,EAChC;AAGA,MAAI,QAAQ,QAAQ,KAAK,SAAS,UAAU,GAAG;AAC7C,WAAO,kBAAkB,QAAQ;AAAA,EACnC;AAGA,MAAI,gBAAgB,QAAQ,GAAG;AAC7B,UAAM,YAAY,MAAM,KAAK,QAA8B;AAC3D,WAAO,kBAAkB,SAAS;AAAA,EACpC;AAEA,SAAO;AACT;AAMA,SAAS,kBACP,MACiD;AACjD,QAAM,CAAC,SAAS,QAAQ,MAAM,IAAI;AAElC,MAAI,CAAC,SAAS,OAAO,EAAG,QAAO;AAE/B,MAAI;AACJ,MAAI,YAAqC,CAAC;AAE1C,UAAQ,SAAS;AAAA,IACf,KAAK;AAEH,UAAI,CAAC,SAAS,MAAM,KAAK,KAAK,SAAS,EAAG,QAAO;AAEjD,UAAI,WAAW,aAAa,WAAW,SAAU,QAAO;AAExD,UAAI,CAAC,SAAS,MAAM,KAAK,WAAW,KAAM,QAAO;AAEjD,kBAAY,GAAG,OAAO,IAAI,MAAM;AAChC,kBAAY,EAAE,GAAG,OAAO;AACxB;AAAA,IAEF,KAAK;AAEH,UAAI,CAAC,SAAS,MAAM,EAAG,QAAO;AAC9B,kBAAY;AACZ,UAAI,SAAS,MAAM,GAAG;AACpB,oBAAY,EAAE,GAAG,OAAO;AAAA,MAC1B;AACA;AAAA,IAEF,KAAK;AAEH,UAAI,CAAC,SAAS,MAAM,EAAG,QAAO;AAC9B,kBAAY,GAAG,OAAO,IAAI,MAAM;AAChC,UAAI,SAAS,MAAM,GAAG;AACpB,oBAAY,EAAE,GAAG,OAAO;AAAA,MAC1B;AACA;AAAA,IAEF,KAAK;AACH,UAAI,SAAS,MAAM,GAAG;AACpB,oBAAY,GAAG,OAAO,IAAI,MAAM;AAChC,YAAI,SAAS,MAAM,GAAG;AACpB,sBAAY,EAAE,GAAG,OAAO;AAAA,QAC1B;AAAA,MACF,WAAW,SAAS,MAAM,GAAG;AAC3B,oBAAY,GAAG,OAAO;AACtB,oBAAY,EAAE,GAAG,OAAO;AAAA,MAC1B,OAAO;AACL,eAAO;AAAA,MACT;AACA;AAAA,IAEF;AAEE,aAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,GAAG;AAAA,EACL;AACF;AAKA,SAAS,gBAAgB,KAAuB;AAC9C,SACE,OAAO,QACP,OAAO,QAAQ,YACf,YAAY,OACZ,OAAQ,IAA2B,WAAW,YAC7C,IAA2B,SAAS;AAEzC;;;ACpPA;;;ACaO,IAAM,kBAAsC,OAAO,YAAY;AACpE,QAAM,EAAE,QAAQ,IAAI,IAAI;AACxB,QAAM,EAAE,KAAK,QAAQ,UAAU,IAAI;AAEnC,QAAM,WAAmC;AAAA,IACvC,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,GAAG,iCAAQ;AAAA,EACb;AAEA,QAAM,aAAmC;AAAA,IACvC;AAAA,EACF;AAGA,MAAI,qBAAqB;AAIzB,QAAM,OAAO,MAAM;AACjB,QAAI,CAAC,UAAW;AAChB,UAAM,gBAAgB,SAAS,QAAQ;AACvC,UAAM,KAAM,UAAsC,aAAa;AAC/D,yBAAqB,MAAM,QAAQ,EAAE,IAAI,GAAG,SAAS;AAErD,UAAM,MAAM;AACZ,uBAAmB,KAAK,YAAY,GAAG;AAAA,EACzC;AAOA,QAAM,cAAc,OAAO,UAAoB;AAC7C,QAAI,UAAU,SAAS,aAAa,qBAAqB,GAAG;AAC1D,YAAM,MAAM;AACZ,4BAAsB,KAAK,YAAY,oBAAoB,GAAG;AAC9D,2BAAqB;AAAA,IACvB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,IACN,IAAI;AAAA,IACJ;AAAA,IACA,SAAS,YAAY;AAEnB,YAAM,gBAAgB,SAAS,QAAQ;AACvC,UACE,aACA,UAAU,aAAa,KACvB,MAAM,QAAQ,UAAU,aAAa,CAAC,GACtC;AAAA,MAGF;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}