{"version":3,"sources":["../src/index.ts","../src/types/index.ts"],"sourcesContent":["import type { WalkerOS } from '@walkeros/core';\nimport { getMappingValue, isNumber, isObject, isString } from '@walkeros/core';\nimport type { DestinationWeb } from '@walkeros/web-core';\nimport { getEnv } from '@walkeros/web-core';\nimport type {\n  Destination,\n  Env,\n  Lintrk,\n  LintrkTrackData,\n  Mapping,\n} from './types';\n\n// Types export\nexport * as DestinationLinkedIn from './types';\n\n/**\n * LinkedIn Insight Tag web destination.\n *\n * Loads the LinkedIn Insight Tag script from LinkedIn's CDN and forwards\n * explicit conversions via `window.lintrk('track', ...)`. Opt-in: events\n * without `mapping.settings.conversion` produce zero calls.\n *\n * See `/workspaces/developer/docs/research/linkedin-destination-guide.md`.\n */\n\nconst LINKEDIN_SCRIPT_SRC =\n  'https://snap.licdn.com/li.lms-analytics/insight.min.js';\n\n/**\n * Install the LinkedIn `window.lintrk` queue shim. Mirrors the official\n * snippet: any call made before the CDN script loads is pushed to a queue\n * the script processes once it initializes.\n *\n * Idempotent - if `lintrk` is already installed (e.g. another tag installed\n * it), leave the existing function alone.\n */\nfunction installLintrkQueue(env: Env | undefined): void {\n  const { window } = getEnv(env);\n  const w = window as Env['window'];\n  if (w.lintrk) return;\n  const lintrk = function (this: unknown, ...args: unknown[]) {\n    (lintrk.q = lintrk.q || []).push(args);\n  } as unknown as Lintrk;\n  lintrk.q = [];\n  w.lintrk = lintrk;\n}\n\nfunction setPartnerGlobals(env: Env | undefined, partnerId: string): void {\n  const { window } = getEnv(env);\n  const w = window as Env['window'];\n  w._linkedin_partner_id = partnerId;\n  w._linkedin_data_partner_ids = w._linkedin_data_partner_ids || [];\n  if (!w._linkedin_data_partner_ids.includes(partnerId)) {\n    w._linkedin_data_partner_ids.push(partnerId);\n  }\n}\n\nfunction addScript(env: DestinationWeb.Env | undefined): void {\n  const { document } = getEnv(env);\n  const doc = document as Document;\n  const script = doc.createElement('script');\n  script.src = LINKEDIN_SCRIPT_SRC;\n  script.async = true;\n  doc.head.appendChild(script);\n}\n\n/**\n * Resolve the short-key walkerOS mapping shape into a LintrkTrackData object.\n *\n * Input (already resolved via getMappingValue):\n *   { id, value?, currency?, eventId? }\n *\n * Output (vendor API shape):\n *   { conversion_id, conversion_value?, currency?, event_id? }\n *\n * Null/falsy guards:\n *  - id must resolve to a truthy number → otherwise return undefined (skip call)\n *  - other fields are omitted when falsy\n */\nfunction buildLintrkData(resolved: unknown): LintrkTrackData | undefined {\n  if (!isObject(resolved)) return undefined;\n\n  const { id, value, currency, eventId } = resolved as {\n    id?: unknown;\n    value?: unknown;\n    currency?: unknown;\n    eventId?: unknown;\n  };\n\n  // id must be a truthy number (Campaign Manager conversion_id).\n  // Accept numeric strings by coercion - users may supply { value: \"12345\" }.\n  const numericId = isNumber(id)\n    ? id\n    : isString(id) && id !== '' && !Number.isNaN(Number(id))\n      ? Number(id)\n      : undefined;\n  if (numericId === undefined || numericId === 0) return undefined;\n\n  const data: LintrkTrackData = { conversion_id: numericId };\n\n  // conversion_value - accept number or numeric string; omit if falsy/NaN.\n  if (isNumber(value)) {\n    data.conversion_value = value;\n  } else if (isString(value) && value !== '') {\n    const n = Number(value);\n    if (!Number.isNaN(n)) data.conversion_value = n;\n  }\n\n  // currency - string only; omit if falsy.\n  if (isString(currency) && currency !== '') {\n    data.currency = currency;\n  }\n\n  // event_id - string only; omit if falsy.\n  if (isString(eventId) && eventId !== '') {\n    data.event_id = eventId;\n  }\n\n  return data;\n}\n\nexport const destinationLinkedIn: Destination = {\n  type: 'linkedin',\n\n  config: {},\n\n  init({ config, env }) {\n    const settings = config.settings;\n    if (!settings?.apiKey) return false;\n\n    setPartnerGlobals(env as Env | undefined, settings.apiKey);\n    installLintrkQueue(env as Env | undefined);\n\n    // Script injection is opt-in via config.loadScript (same default as\n    // plausible / meta). Users who already embed the Insight Tag snippet in\n    // their HTML should leave loadScript false - the destination only\n    // installs the queue shim and mutates _linkedin_partner_id.\n    if (config.loadScript) addScript(env);\n\n    return config;\n  },\n\n  async push(event, { rule, env, collector }) {\n    const { window } = getEnv(env);\n    const w = window as Env['window'];\n    const lintrk = w.lintrk;\n    if (!lintrk) return; // init must have run\n\n    // Honor rule-level silent (core flag at packages/core/src/types/mapping.ts).\n    if (rule?.silent === true) return;\n\n    const mappingSettings = (rule?.settings || {}) as Mapping;\n    if (mappingSettings.conversion === undefined) return; // opt-in: no config = no call\n\n    const resolved = await getMappingValue(\n      event as WalkerOS.Event,\n      mappingSettings.conversion,\n      { collector },\n    );\n\n    const data = buildLintrkData(resolved);\n    if (!data) return;\n\n    lintrk('track', data);\n  },\n\n  on(type, context) {\n    if (type !== 'consent' || !context.data) return;\n\n    // LinkedIn has no vendor consent mode. The collector's config.consent gate\n    // is the sole mechanism for blocking events. This handler exists for a\n    // narrow case: config.loadScript was true BUT marketing consent was\n    // initially denied, so init() skipped addScript(). When consent later\n    // grants marketing, we inject the script.\n    const consent = context.data as WalkerOS.Consent;\n    const config = context.config;\n    if (!config?.loadScript) return;\n    if (consent.marketing !== true) return;\n\n    // Check whether the script tag is already in the DOM - addScript is not\n    // idempotent, and re-injecting would load the tag twice.\n    const { document } = getEnv(context.env);\n    const doc = document as Document;\n    const existing = doc.querySelector\n      ? doc.querySelector('script[src*=\"snap.licdn.com\"]')\n      : null;\n    if (existing) return;\n\n    addScript(context.env);\n  },\n};\n\nexport default destinationLinkedIn;\n","import type {\n  Mapping as WalkerOSMapping,\n  Destination as CoreDestination,\n} from '@walkeros/core';\nimport type { DestinationWeb } from '@walkeros/web-core';\n\n/**\n * LinkedIn Insight Tag runtime surface.\n *\n * The Insight Tag script installs `window.lintrk` — a single function that\n * accepts exactly one tracked action: `lintrk('track', data)`.\n *\n * Before the script loads, the destination installs a queue-backed shim so\n * calls made during init are buffered and flushed once the script loads.\n * This mirrors the pattern LinkedIn's own snippet uses.\n */\nexport type LintrkAction = 'track';\n\nexport interface LintrkTrackData {\n  conversion_id: number;\n  conversion_value?: number;\n  currency?: string;\n  event_id?: string;\n}\n\nexport type Lintrk = ((action: LintrkAction, data: LintrkTrackData) => void) & {\n  /** Internal queue populated before the CDN script loads. */\n  q?: unknown[];\n};\n\ndeclare global {\n  interface Window {\n    _linkedin_partner_id?: string;\n    _linkedin_data_partner_ids?: string[];\n    lintrk?: Lintrk;\n  }\n}\n\n/**\n * Destination-level settings.\n *\n * apiKey — the LinkedIn Partner ID (numeric string, e.g. \"123456\"), assigned\n *          to `window._linkedin_partner_id` before the script loads.\n * include — event sections made available for mapping resolution. Present for\n *          consistency with other destinations; has no effect at call time\n *          because `lintrk()` only accepts four fixed fields. Kept so users\n *          can reference section-prefixed keys in future custom mapping\n *          strategies without a breaking change.\n */\nexport interface Settings {\n  apiKey: string;\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Per-rule mapping settings.\n *\n * conversion — mapping value resolving to an object with short keys:\n *   { id (required), value?, currency?, eventId? }\n *\n * `id` becomes `conversion_id` (number, Campaign Manager conversion rule ID).\n * Other keys translate 1:1 to the vendor parameter names.\n *\n * Events without a resolved `conversion.id` are silently ignored — LinkedIn\n * is an opt-in conversion model.\n */\nexport interface Mapping {\n  conversion?: WalkerOSMapping.Value;\n}\n\n/**\n * Env — mock surface for tests and dev. The destination mutates\n * `window._linkedin_partner_id` / `window._linkedin_data_partner_ids` and\n * installs `window.lintrk` in init; tests can pre-seed `window.lintrk` with\n * a spy to skip the script injection path.\n *\n * `document` is also mocked so `addScript()` can run headlessly.\n */\nexport interface Env extends DestinationWeb.Env {\n  window: {\n    _linkedin_partner_id?: string;\n    _linkedin_data_partner_ids?: string[];\n    lintrk?: Lintrk;\n  };\n}\n\nexport type Types = CoreDestination.Types<Settings, Mapping, Env, InitSettings>;\n\nexport type Destination = DestinationWeb.Destination<Types>;\nexport type Config = DestinationWeb.Config<Types>;\n\nexport interface LinkedInDestination extends Destination {\n  env?: Env;\n}\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n"],"mappings":"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","names":[]}