@walkeros/web-destination-tiktok 4.8.1 → 4.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +1 -1
- package/dist/dev.d.mts +26 -8
- package/dist/dev.d.ts +26 -8
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/examples/index.d.mts +25 -7
- package/dist/examples/index.d.ts +25 -7
- package/dist/examples/index.js +60 -36
- package/dist/examples/index.mjs +61 -37
- package/dist/index.browser.js +1 -1
- package/dist/index.d.mts +16 -7
- package/dist/index.d.ts +16 -7
- package/dist/index.es5.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +107 -23
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,
|
|
1
|
+
"use strict";var e,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,i=Object.prototype.hasOwnProperty,a={};((e,n)=>{for(var o in n)t(e,o,{get:n[o],enumerable:!0})})(a,{DestinationTikTok:()=>d,default:()=>y,destinationTikTok:()=>p}),module.exports=(e=a,((e,a,r,s)=>{if(a&&"object"==typeof a||"function"==typeof a)for(let c of o(a))i.call(e,c)||c===r||t(e,c,{get:()=>a[c],enumerable:!(s=n(a,c))||s.enumerable});return e})(t({},"__esModule",{value:!0}),e));var r=require("@walkeros/core"),s=require("@walkeros/web-core"),c=require("@walkeros/web-core"),l=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie"];var d={},f=["load","track","identify","enableCookie","disableCookie"];function u(e){const{ttq:t}=(0,s.getEnv)(e).window;if("function"!=typeof(n=t)||!f.every(e=>"function"==typeof Reflect.get(n,e)))throw new r.StepError("TikTok Pixel not ready: window.ttq is missing",{code:"vendor-not-ready"});var n;return t}function k(e,t){return(0,s.loadVendor)(!1!==e.loadScript?"https://analytics.tiktok.com/i18n/pixel/events.js":void 0,()=>(0,s.getEnv)(t).window.ttq,{timeout:e.timeout,document:(0,s.getEnv)(t).document})}var p={type:"tiktok",config:{},async init({config:e,env:t}){const n={...e.settings};if(!n.apiKey)throw new r.StepError("TikTok needs settings.apiKey",{code:"config"});!function(e){const{window:t}=(0,c.getEnv)(e);if(t.ttq)return;const n=Object.assign((...e)=>{var t;((t=n._i).all||(t.all=[])).push(e)},{methods:[...l],_i:{},loaded:!1});t.ttq=Object.assign(n,Object.fromEntries(l.map(e=>[e,(...t)=>n(e,...t)])))}(t),k(e,t);const o=u(t),{apiKey:i,identify:a,_state:s,...d}=n,f={...d};await(0,r.stepCall)(()=>o.load(i,f),"vendor-not-ready","TikTok load call failed");return{...e,settings:{...n,_state:{lastIdentity:{}}}}},async push(e,{config:t,rule:n,data:o,env:i,collector:a,logger:s}){var c;k(t,i).check();const l=u(i),d=t.settings||{},f=(null==n?void 0:n.settings)||{},p=d._state||{},y=null!=(c=f.identify)?c:d.identify;if(void 0!==y){const t=await(0,r.getMappingValue)(e,y,{collector:a}),n=(0,r.isObject)(t)?await async function(e,t,n={}){const o={...n},i={};let a=!1;for(const e of["email","phone_number","external_id"]){const n=t[e];(0,r.isString)(n)&&""!==n&&(i[e]=n,o[e]!==n&&(o[e]=n,a=!0))}if(0!==Object.keys(i).length)return a?(await(0,r.stepCall)(()=>e.identify(i),"transport","TikTok identify failed"),o):o}(l,t,p.lastIdentity):void 0;n?p.lastIdentity=n:function(e,t,n,o){e.debug(`TikTok skipped ${n}: ${o} resolved to nothing`,{event:t.name})}(s,e,"identify","identify")}if(!0!==(null==n?void 0:n.silent)){const t=(0,r.isString)(null==n?void 0:n.name)?n.name:e.name,i=(0,r.isObject)(o)?o:{};await(0,r.stepCall)(()=>l.track(t,i,{event_id:e.id}),"transport","TikTok track failed")}d._state=p},async on(e,{config:t,data:n,env:o,collector:i}){const a=t.consent;if("consent"!==e||!a||!(0,r.isObject)(n))return;if(!Object.keys(a).some(e=>e in n))return;const s=u(o),c=!1!==(0,r.getGrantedConsent)(a,i.consent);await(0,r.stepCall)(()=>c?s.enableCookie():s.disableCookie(),"consent-not-applied","TikTok did not take the consent update")}},y=p;//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/types/index.ts"],"sourcesContent":["import type { WalkerOS } from '@walkeros/core';\nimport { getMappingValue, isObject, isString } from '@walkeros/core';\nimport { getEnv } from '@walkeros/web-core';\nimport { addScript, setup } from './setup';\nimport type {\n Destination,\n IdentifyParams,\n Mapping,\n RuntimeState,\n Settings,\n TTQ,\n} from './types';\n\n// Types export\nexport * as DestinationTikTok from './types';\n\n/**\n * Read the TikTok `ttq` function off env.window. getEnv() transparently\n * falls through to the global window when env is undefined (production),\n * or uses env.window when the caller provides a mock (tests).\n */\nfunction getTTQ(env: unknown): TTQ {\n const { window } = getEnv(env as Parameters<typeof getEnv>[0]);\n return (window as unknown as { ttq: TTQ }).ttq;\n}\n\n/**\n * Resolve an identify mapping value and, if any Advanced Matching\n * parameter changed since last push, call ttq.identify() with the full\n * resolved object. Returns the updated lastIdentity snapshot.\n *\n * The destination never partial-updates identity - TikTok treats each\n * ttq.identify() call as a full replacement of Advanced Matching state.\n */\nfunction applyIdentify(\n ttq: TTQ,\n resolved: Record<string, unknown>,\n lastIdentity: NonNullable<RuntimeState['lastIdentity']> = {},\n): NonNullable<RuntimeState['lastIdentity']> {\n const next: NonNullable<RuntimeState['lastIdentity']> = { ...lastIdentity };\n const params: IdentifyParams = {};\n let changed = false;\n\n for (const key of ['email', 'phone_number', 'external_id'] as const) {\n const raw = resolved[key];\n if (!isString(raw) || raw === '') continue;\n params[key] = raw;\n if (next[key] !== raw) {\n next[key] = raw;\n changed = true;\n }\n }\n\n // Skip ttq.identify() entirely if the resolved object has no non-empty\n // values (anonymous visitor) or if nothing changed.\n if (!changed || Object.keys(params).length === 0) return next;\n\n ttq.identify(params);\n return next;\n}\n\nexport const destinationTikTok: Destination = {\n type: 'tiktok',\n\n config: {},\n\n init({ config, env }) {\n const settings = config.settings;\n if (!settings?.apiKey) return false;\n\n // Ensure window.ttq exists (queue-based stub) before any call.\n setup(env);\n\n // Load the real TikTok Pixel SDK from the CDN. The queue replays\n // once the script executes.\n if (config.loadScript !== false) addScript(env);\n\n const ttq = getTTQ(env);\n\n // Build ttq.load() second-arg config by stripping walkerOS-specific\n // keys and forwarding the rest as TikTok passthrough options.\n const {\n apiKey,\n identify: _identify,\n _state: _existingState,\n ...passthrough\n } = settings;\n\n // TikTok's SDK auto-fires ttq.page() on load; we let it fire (no knob).\n const loadOptions: Record<string, unknown> = { ...passthrough };\n\n ttq.load(apiKey, loadOptions);\n\n // Initialize runtime state. Identity is resolved on first push.\n const _state: RuntimeState = { lastIdentity: {} };\n return { ...config, settings: { ...settings, _state } };\n },\n\n async push(event, { config, rule, data, env, collector }) {\n const ttq = getTTQ(env);\n const settings = (config.settings || {}) as Settings;\n const mappingSettings = (rule?.settings || {}) as Mapping;\n\n const state: RuntimeState = settings._state || {};\n\n // Identity - rule-level override wins over destination-level.\n // Resolved and diffed against runtime state; only changes fire\n // ttq.identify(). Order: identify BEFORE track so Advanced Matching\n // is set for the conversion event.\n const identifyMapping = mappingSettings.identify ?? settings.identify;\n if (identifyMapping !== undefined) {\n const resolved = await getMappingValue(event, identifyMapping, {\n collector,\n });\n if (isObject(resolved)) {\n state.lastIdentity = applyIdentify(\n ttq,\n resolved as Record<string, unknown>,\n state.lastIdentity,\n );\n }\n }\n\n // Default track. rule.silent suppresses the call entirely (e.g., for\n // rules that are identify-only).\n if (rule?.silent !== true) {\n const eventName = isString(rule?.name) ? rule.name : event.name;\n const params: Record<string, unknown> = isObject(data)\n ? (data as Record<string, unknown>)\n : {};\n\n ttq.track(eventName, params, { event_id: event.id });\n }\n\n // Persist runtime state mutations. walkerOS destinations are\n // long-lived singletons so mutating settings in place is safe and\n // matches the Snowplow precedent.\n settings._state = state;\n },\n\n on(type, context) {\n if (type !== 'consent' || !context.data) return;\n const ttq = getTTQ(context.env);\n\n const consent = context.data as WalkerOS.Consent;\n // Derive the consent key(s) to check from config.consent. TikTok is\n // an advertising tool, so the typical key is \"marketing\". If ALL\n // required keys are granted, enable cookies; otherwise disable. A\n // missing config.consent means nothing to check and we take no\n // action.\n const required = context.config?.consent;\n if (!required || Object.keys(required).length === 0) return;\n\n const allGranted = Object.keys(required).every(\n (key) => consent[key] === true,\n );\n\n if (allGranted) {\n ttq.enableCookie();\n } else {\n ttq.disableCookie();\n }\n },\n};\n\nexport default destinationTikTok;\n","import type { DestinationWeb } from '@walkeros/web-core';\nimport { getEnv } from '@walkeros/web-core';\n\nconst DEFAULT_TIKTOK_SRC = 'https://analytics.tiktok.com/i18n/pixel/events.js';\n\n/**\n * Inject the TikTok Pixel CDN script. The real SDK replaces the queueing\n * stub created by setup() with a full implementation.\n */\nexport function addScript(\n env?: DestinationWeb.Env,\n src = DEFAULT_TIKTOK_SRC,\n): void {\n const { document } = getEnv(env);\n const script = (document as Document).createElement('script');\n script.src = src;\n script.async = true;\n (document as Document).head.appendChild(script);\n}\n\ninterface TTQFunction {\n (...args: unknown[]): void;\n methods?: string[];\n _i?: Record<string, unknown>;\n loaded?: boolean;\n page?: (...args: unknown[]) => void;\n track?: (...args: unknown[]) => void;\n identify?: (...args: unknown[]) => void;\n enableCookie?: (...args: unknown[]) => void;\n disableCookie?: (...args: unknown[]) => void;\n load?: (...args: unknown[]) => void;\n}\n\n/**\n * Create the `window.ttq` queue object (TikTok's snippet pattern).\n * Any method call before the real SDK loads is buffered; once the CDN\n * script runs, the queue is replayed in order. Idempotent — does\n * nothing if ttq already exists on the window.\n */\nexport function setup(env?: DestinationWeb.Env): void {\n const { window } = getEnv(env);\n const w = window as unknown as { ttq?: TTQFunction };\n if (w.ttq && (w.ttq as TTQFunction).loaded) return;\n\n // Preserve any existing mock ttq (from env.window.ttq in tests). Only\n // construct a new queue if nothing is there.\n if (!w.ttq) {\n const n = function (...args: unknown[]): void {\n (n._i as Record<string, unknown[]>) ||= {};\n ((n._i as { all?: unknown[] }).all ||= []).push(args);\n } as TTQFunction;\n\n n.methods = [\n 'page',\n 'track',\n 'identify',\n 'instances',\n 'debug',\n 'on',\n 'off',\n 'once',\n 'ready',\n 'alias',\n 'group',\n 'enableCookie',\n 'disableCookie',\n ];\n\n // Stub out each method to enqueue through the root `n` function.\n for (const method of n.methods) {\n (n as unknown as Record<string, unknown>)[method] = (\n ...args: unknown[]\n ) => n(method, ...args);\n }\n\n n._i = {};\n n.loaded = false;\n w.ttq = n;\n }\n}\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 * TikTok's 14 recognized standard event names — rigid taxonomy for ad\n * optimization. `mapping.name` values from this union light up IDE\n * autocomplete; `| string` still allows arbitrary custom event names\n * (which TikTok treats as custom events with no optimization value).\n */\nexport type StandardEventNames =\n | 'ViewContent'\n | 'ClickButton'\n | 'Search'\n | 'AddToWishlist'\n | 'AddToCart'\n | 'InitiateCheckout'\n | 'AddPaymentInfo'\n | 'CompletePayment'\n | 'PlaceAnOrder'\n | 'Contact'\n | 'Download'\n | 'SubmitForm'\n | 'CompleteRegistration'\n | 'Subscribe'\n | (string & {});\n\n/**\n * Settings (destination-level).\n *\n * apiKey is the TikTok Pixel ID. `identify` resolves to Advanced Matching\n * parameters (email, phone_number, external_id) — TikTok auto-hashes these\n * before sending, so raw values are safe. `include` names walkerOS event\n * sections to flatten into prefixed TikTok event parameters. All other\n * TikTok-native snake_case options (e.g., `auto_config`, `limited_data_use`)\n * are passed through to `ttq.load(apiKey, options)`.\n */\nexport interface Settings {\n /** TikTok Pixel ID — first argument to ttq.load(...). Required. */\n apiKey: string;\n /**\n * walkerOS mapping value resolving to an Advanced Matching object with any\n * of: `email`, `phone_number`, `external_id`. Applied on first push and\n * re-fired only on change (runtime state in _state.lastIdentity).\n */\n identify?: WalkerOSMapping.Value;\n // `pageview` removed 2026-04-09 — TikTok's auto page view fires from the\n // SDK and cannot be reliably suppressed via init config. Letting it fire\n // is the expected behavior.\n /** TikTok default: true. Enable auto-detection of form fields for Advanced Matching. */\n auto_config?: boolean;\n /** TikTok default: false. Restrict data use under U.S. state privacy laws. */\n limited_data_use?: boolean;\n /**\n * Runtime state — populated by init() and mutated by push(). Not\n * user-facing. Stores last-resolved identity so subsequent pushes with\n * unchanged values skip redundant ttq.identify() calls.\n */\n _state?: RuntimeState;\n /**\n * Pass-through bucket: any additional TikTok Pixel init config (snake_case\n * keys per TikTok's docs). Merged into ttq.load()'s second argument.\n */\n [key: string]: unknown;\n}\n\nexport interface RuntimeState {\n lastIdentity?: {\n email?: string;\n phone_number?: string;\n external_id?: string;\n };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Mapping (rule.settings) — per-event overrides.\n *\n * identify — mapping value resolving to an Advanced Matching object (same\n * shape as settings.identify). Merged onto/replaces the\n * destination-level identity for this single push.\n * include — replaces destination-level include for this rule only.\n */\nexport interface Mapping {\n identify?: WalkerOSMapping.Value;\n}\n\n/**\n * Advanced Matching parameter shape — what settings.identify must resolve to.\n * All fields are optional; if the resolved object has no non-empty values,\n * the destination skips the ttq.identify() call entirely.\n */\nexport interface IdentifyParams {\n email?: string;\n phone_number?: string;\n external_id?: string;\n}\n\n/**\n * Minimal surface of TikTok's window.ttq this destination uses. Tests mock\n * this by attaching a plain object to env.window.ttq; production uses the\n * real SDK created by the snippet setup() call.\n */\nexport interface TTQ {\n (...args: unknown[]): void;\n load: (pixelId: string, config?: Record<string, unknown>) => void;\n page: () => void;\n track: (\n event: string,\n params?: Record<string, unknown>,\n options?: { event_id?: string },\n ) => void;\n identify: (params: IdentifyParams) => void;\n enableCookie: () => void;\n disableCookie: () => void;\n /** Queue-based pattern — snippet sets this; destination does not read it. */\n methods?: string[];\n /** Internal queue populated before the CDN SDK loads. */\n _i?: Record<string, unknown>;\n /** Flag the snippet sets after the real SDK loads. */\n loaded?: boolean;\n}\n\n/**\n * Env — the destination reads `window.ttq` via getEnv(env). Tests attach\n * a mock ttq to env.window; production reads window.ttq created by the\n * SDK snippet.\n */\nexport interface Env extends DestinationWeb.Env {\n window: {\n ttq: TTQ;\n };\n document: {\n createElement: (tagName: string) => Element;\n head: { appendChild: (node: unknown) => void };\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 TikTokDestination 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":["import_web_core"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/types/index.ts"],"sourcesContent":["import type { Logger, WalkerOS } from '@walkeros/core';\nimport {\n getGrantedConsent,\n getMappingValue,\n isObject,\n isString,\n StepError,\n stepCall,\n} from '@walkeros/core';\nimport type { DestinationWeb } from '@walkeros/web-core';\nimport { getEnv, loadVendor } from '@walkeros/web-core';\nimport { SCRIPT_SRC, setup } from './setup';\nimport type {\n Destination,\n IdentifyParams,\n Mapping,\n RuntimeState,\n Settings,\n TTQ,\n} from './types';\n\nconst TTQ_METHODS = [\n 'load',\n 'track',\n 'identify',\n 'enableCookie',\n 'disableCookie',\n];\n\n/** A loaded Pixel (or a test mock), not the queue `setup()` installs. */\nfunction isTTQ(value: unknown): value is TTQ {\n return (\n typeof value === 'function' &&\n TTQ_METHODS.every((key) => typeof Reflect.get(value, key) === 'function')\n );\n}\n\n// Types export\nexport * as DestinationTikTok from './types';\n\n/**\n * Read the TikTok `ttq` function off env.window. getEnv() transparently\n * falls through to the global window when env is undefined (production),\n * or uses env.window when the caller provides a mock (tests).\n */\nfunction getTTQ(env?: DestinationWeb.Env): TTQ {\n const { ttq } = getEnv(env).window;\n if (!isTTQ(ttq))\n throw new StepError('TikTok Pixel not ready: window.ttq is missing', {\n code: 'vendor-not-ready',\n });\n return ttq;\n}\n\n/**\n * The Pixel script `loadVendor` injects unless `loadScript` is false. ttq\n * replays its own queue, so it is called synchronously and only a failed\n * script is reported.\n */\nfunction vendor(\n config: { loadScript?: boolean; timeout?: number },\n env?: DestinationWeb.Env,\n) {\n return loadVendor(\n config.loadScript !== false ? SCRIPT_SRC : undefined,\n () => getEnv(env).window.ttq,\n { timeout: config.timeout, document: getEnv(env).document },\n );\n}\n\n/**\n * Resolve an identify mapping value and, if any Advanced Matching\n * parameter changed since last push, call ttq.identify() with the full\n * resolved object. Returns the updated lastIdentity snapshot.\n *\n * The destination never partial-updates identity - TikTok treats each\n * ttq.identify() call as a full replacement of Advanced Matching state.\n */\nasync function applyIdentify(\n ttq: TTQ,\n resolved: Record<string, unknown>,\n lastIdentity: NonNullable<RuntimeState['lastIdentity']> = {},\n): Promise<NonNullable<RuntimeState['lastIdentity']> | undefined> {\n const next: NonNullable<RuntimeState['lastIdentity']> = { ...lastIdentity };\n const params: IdentifyParams = {};\n let changed = false;\n\n for (const key of ['email', 'phone_number', 'external_id'] as const) {\n const raw = resolved[key];\n if (!isString(raw) || raw === '') continue;\n params[key] = raw;\n if (next[key] !== raw) {\n next[key] = raw;\n changed = true;\n }\n }\n\n // No non-empty value resolved: nothing to identify. An identity that is\n // already current skips ttq.identify() as well.\n if (Object.keys(params).length === 0) return undefined;\n if (!changed) return next;\n\n await stepCall(\n () => ttq.identify(params),\n 'transport',\n 'TikTok identify failed',\n );\n return next;\n}\n\n/**\n * A configured mapping that resolved to nothing skips its call: a declared\n * skip, said once at DEBUG with the event and the mapping key, never the\n * value, so a wrong mapping path does not look like an event without data.\n */\nfunction skipped(\n logger: Logger.Instance,\n event: WalkerOS.Event,\n call: string,\n key: string,\n): void {\n logger.debug(`TikTok skipped ${call}: ${key} resolved to nothing`, {\n event: event.name,\n });\n}\n\nexport const destinationTikTok: Destination = {\n type: 'tiktok',\n\n config: {},\n\n async init({ config, env }) {\n // A copy, so the destructure below does not read keys off the live settings\n const settings = { ...config.settings };\n if (!settings.apiKey)\n throw new StepError('TikTok needs settings.apiKey', { code: 'config' });\n\n // Ensure window.ttq exists (queue-based stub) before any call.\n setup(env);\n\n // Load the real TikTok Pixel SDK from the CDN. The queue replays\n // once the script executes.\n vendor(config, env);\n\n const ttq = getTTQ(env);\n\n // Build ttq.load() second-arg config by stripping walkerOS-specific\n // keys and forwarding the rest as TikTok passthrough options.\n const {\n apiKey,\n identify: _identify,\n _state: _existingState,\n ...passthrough\n } = settings;\n\n // TikTok's SDK auto-fires ttq.page() on load; we let it fire (no knob).\n const loadOptions: Record<string, unknown> = { ...passthrough };\n\n await stepCall(\n () => ttq.load(apiKey, loadOptions),\n 'vendor-not-ready',\n 'TikTok load call failed',\n );\n\n // Initialize runtime state. Identity is resolved on first push.\n const _state: RuntimeState = { lastIdentity: {} };\n return { ...config, settings: { ...settings, _state } };\n },\n\n async push(event, { config, rule, data, env, collector, logger }) {\n vendor(config, env).check();\n const ttq = getTTQ(env);\n const settings: Partial<Settings> = config.settings || {};\n const mappingSettings: Mapping = rule?.settings || {};\n\n const state: RuntimeState = settings._state || {};\n\n // Identity - rule-level override wins over destination-level.\n // Resolved and diffed against runtime state; only changes fire\n // ttq.identify(). Order: identify BEFORE track so Advanced Matching\n // is set for the conversion event.\n const identifyMapping = mappingSettings.identify ?? settings.identify;\n if (identifyMapping !== undefined) {\n const resolved = await getMappingValue(event, identifyMapping, {\n collector,\n });\n const identity = isObject(resolved)\n ? await applyIdentify(ttq, resolved, state.lastIdentity)\n : undefined;\n if (identity) state.lastIdentity = identity;\n else skipped(logger, event, 'identify', 'identify');\n }\n\n // Default track. rule.silent suppresses the call entirely (e.g., for\n // rules that are identify-only).\n if (rule?.silent !== true) {\n const eventName = isString(rule?.name) ? rule.name : event.name;\n const params: Record<string, unknown> = isObject(data) ? data : {};\n\n await stepCall(\n () => ttq.track(eventName, params, { event_id: event.id }),\n 'transport',\n 'TikTok track failed',\n );\n }\n\n // A silent rule whose identify resolves nothing (or an identity that is\n // already current) sends nothing: a declared skip, not a failure.\n // Persist runtime state mutations. walkerOS destinations are\n // long-lived singletons so mutating settings in place is safe and\n // matches the Snowplow precedent.\n settings._state = state;\n },\n\n // The destination's required consent decides on the whole consent state,\n // as it gates the pushes, and only a change that names a required key\n // reaches TikTok: a revocation disables its cookies, a grant enables them.\n async on(type, { config, data, env, collector }) {\n const required = config.consent;\n if (type !== 'consent' || !required || !isObject(data)) return;\n if (!Object.keys(required).some((key) => key in data)) return;\n\n const ttq = getTTQ(env);\n const granted = getGrantedConsent(required, collector.consent) !== false;\n await stepCall(\n () => (granted ? ttq.enableCookie() : ttq.disableCookie()),\n 'consent-not-applied',\n 'TikTok did not take the consent update',\n );\n },\n};\n\nexport default destinationTikTok;\n","import type { DestinationWeb } from '@walkeros/web-core';\nimport type { TTQStub } from './types';\nimport { getEnv } from '@walkeros/web-core';\n\n/** The TikTok Pixel library, loaded through `loadVendor`. */\nexport const SCRIPT_SRC = 'https://analytics.tiktok.com/i18n/pixel/events.js';\n\nconst METHODS = [\n 'page',\n 'track',\n 'identify',\n 'instances',\n 'debug',\n 'on',\n 'off',\n 'once',\n 'ready',\n 'alias',\n 'group',\n 'enableCookie',\n 'disableCookie',\n];\n\n/**\n * Create the `window.ttq` queue object (TikTok's snippet pattern).\n * Any method call before the real SDK loads is buffered; once the CDN\n * script runs, the queue is replayed in order. Idempotent: does nothing if\n * ttq already exists on the window (a loaded Pixel, or a mock in tests).\n */\nexport function setup(env?: DestinationWeb.Env): void {\n const { window } = getEnv(env);\n if (window.ttq) return;\n\n const record = (...args: unknown[]): void => {\n (stub._i.all ||= []).push(args);\n };\n const stub: TTQStub = Object.assign(record, {\n methods: [...METHODS],\n _i: {},\n loaded: false,\n });\n\n // Stub out each method to enqueue through the root function.\n window.ttq = Object.assign(\n stub,\n Object.fromEntries(\n METHODS.map((method) => [\n method,\n (...args: unknown[]) => stub(method, ...args),\n ]),\n ),\n );\n}\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 * TikTok's 14 recognized standard event names — rigid taxonomy for ad\n * optimization. `mapping.name` values from this union light up IDE\n * autocomplete; `| string` still allows arbitrary custom event names\n * (which TikTok treats as custom events with no optimization value).\n */\nexport type StandardEventNames =\n | 'ViewContent'\n | 'ClickButton'\n | 'Search'\n | 'AddToWishlist'\n | 'AddToCart'\n | 'InitiateCheckout'\n | 'AddPaymentInfo'\n | 'CompletePayment'\n | 'PlaceAnOrder'\n | 'Contact'\n | 'Download'\n | 'SubmitForm'\n | 'CompleteRegistration'\n | 'Subscribe'\n | (string & {});\n\n/**\n * Settings (destination-level).\n *\n * apiKey is the TikTok Pixel ID. `identify` resolves to Advanced Matching\n * parameters (email, phone_number, external_id) — TikTok auto-hashes these\n * before sending, so raw values are safe. `include` names walkerOS event\n * sections to flatten into prefixed TikTok event parameters. All other\n * TikTok-native snake_case options (e.g., `auto_config`, `limited_data_use`)\n * are passed through to `ttq.load(apiKey, options)`.\n */\nexport interface Settings {\n /** TikTok Pixel ID — first argument to ttq.load(...). Required. */\n apiKey: string;\n /**\n * walkerOS mapping value resolving to an Advanced Matching object with any\n * of: `email`, `phone_number`, `external_id`. Applied on first push and\n * re-fired only on change (runtime state in _state.lastIdentity).\n */\n identify?: WalkerOSMapping.Value;\n // `pageview` removed 2026-04-09 — TikTok's auto page view fires from the\n // SDK and cannot be reliably suppressed via init config. Letting it fire\n // is the expected behavior.\n /** TikTok default: true. Enable auto-detection of form fields for Advanced Matching. */\n auto_config?: boolean;\n /** TikTok default: false. Restrict data use under U.S. state privacy laws. */\n limited_data_use?: boolean;\n /**\n * Runtime state — populated by init() and mutated by push(). Not\n * user-facing. Stores last-resolved identity so subsequent pushes with\n * unchanged values skip redundant ttq.identify() calls.\n */\n _state?: RuntimeState;\n /**\n * Pass-through bucket: any additional TikTok Pixel init config (snake_case\n * keys per TikTok's docs). Merged into ttq.load()'s second argument.\n */\n [key: string]: unknown;\n}\n\nexport interface RuntimeState {\n lastIdentity?: {\n email?: string;\n phone_number?: string;\n external_id?: string;\n };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Mapping (rule.settings) — per-event overrides.\n *\n * identify — mapping value resolving to an Advanced Matching object (same\n * shape as settings.identify). Merged onto/replaces the\n * destination-level identity for this single push.\n * include — replaces destination-level include for this rule only.\n */\nexport interface Mapping {\n identify?: WalkerOSMapping.Value;\n}\n\n/**\n * Advanced Matching parameter shape — what settings.identify must resolve to.\n * All fields are optional; if the resolved object has no non-empty values,\n * the destination skips the ttq.identify() call entirely.\n */\nexport interface IdentifyParams {\n email?: string;\n phone_number?: string;\n external_id?: string;\n}\n\n/**\n * Minimal surface of TikTok's window.ttq this destination uses. Tests mock\n * this by attaching a plain object to env.window.ttq; production uses the\n * real SDK created by the snippet setup() call.\n */\nexport interface TTQ {\n (...args: unknown[]): void;\n load: (pixelId: string, config?: Record<string, unknown>) => void;\n page: () => void;\n track: (\n event: string,\n params?: Record<string, unknown>,\n options?: { event_id?: string },\n ) => void;\n identify: (params: IdentifyParams) => void;\n enableCookie: () => void;\n disableCookie: () => void;\n /** Queue-based pattern — snippet sets this; destination does not read it. */\n methods?: string[];\n /** Internal queue populated before the CDN SDK loads. */\n _i?: Record<string, unknown>;\n /** Flag the snippet sets after the real SDK loads. */\n loaded?: boolean;\n}\n\n/**\n * Env — the destination reads `window.ttq` via getEnv(env). Tests attach\n * a mock ttq to env.window; production reads window.ttq created by the\n * SDK snippet.\n */\nexport interface Env extends DestinationWeb.Env {\n window: {\n ttq: TTQ;\n };\n}\n\n/**\n * The queue `setup()` installs until the Pixel script replaces it: each\n * call is kept in `_i.all`.\n */\nexport type TTQStub = ((...args: unknown[]) => void) & {\n methods: string[];\n _i: Record<string, unknown[]>;\n loaded: boolean;\n};\n\ndeclare global {\n interface Window {\n ttq?: TTQ | TTQStub;\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 TikTokDestination 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":["import_web_core"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{getGrantedConsent as t,getMappingValue as e,isObject as n,isString as o,StepError as i,stepCall as a}from"@walkeros/core";import{getEnv as s,loadVendor as c}from"@walkeros/web-core";import{getEnv as r}from"@walkeros/web-core";var d=["page","track","identify","instances","debug","on","off","once","ready","alias","group","enableCookie","disableCookie"];var l={},f=["load","track","identify","enableCookie","disableCookie"];function k(t){const{ttq:e}=s(t).window;if("function"!=typeof(n=e)||!f.every(t=>"function"==typeof Reflect.get(n,t)))throw new i("TikTok Pixel not ready: window.ttq is missing",{code:"vendor-not-ready"});var n;return e}function u(t,e){return c(!1!==t.loadScript?"https://analytics.tiktok.com/i18n/pixel/events.js":void 0,()=>s(e).window.ttq,{timeout:t.timeout,document:s(e).document})}var y={type:"tiktok",config:{},async init({config:t,env:e}){const n={...t.settings};if(!n.apiKey)throw new i("TikTok needs settings.apiKey",{code:"config"});!function(t){const{window:e}=r(t);if(e.ttq)return;const n=Object.assign((...t)=>{var e;((e=n._i).all||(e.all=[])).push(t)},{methods:[...d],_i:{},loaded:!1});e.ttq=Object.assign(n,Object.fromEntries(d.map(t=>[t,(...e)=>n(t,...e)])))}(e),u(t,e);const o=k(e),{apiKey:s,identify:c,_state:l,...f}=n,y={...f};await a(()=>o.load(s,y),"vendor-not-ready","TikTok load call failed");return{...t,settings:{...n,_state:{lastIdentity:{}}}}},async push(t,{config:i,rule:s,data:c,env:r,collector:d,logger:l}){var f;u(i,r).check();const y=k(r),p=i.settings||{},g=(null==s?void 0:s.settings)||{},v=p._state||{},w=null!=(f=g.identify)?f:p.identify;if(void 0!==w){const i=await e(t,w,{collector:d}),s=n(i)?await async function(t,e,n={}){const i={...n},s={};let c=!1;for(const t of["email","phone_number","external_id"]){const n=e[t];o(n)&&""!==n&&(s[t]=n,i[t]!==n&&(i[t]=n,c=!0))}if(0!==Object.keys(s).length)return c?(await a(()=>t.identify(s),"transport","TikTok identify failed"),i):i}(y,i,v.lastIdentity):void 0;s?v.lastIdentity=s:function(t,e,n,o){t.debug(`TikTok skipped ${n}: ${o} resolved to nothing`,{event:e.name})}(l,t,"identify","identify")}if(!0!==(null==s?void 0:s.silent)){const e=o(null==s?void 0:s.name)?s.name:t.name,i=n(c)?c:{};await a(()=>y.track(e,i,{event_id:t.id}),"transport","TikTok track failed")}p._state=v},async on(e,{config:o,data:i,env:s,collector:c}){const r=o.consent;if("consent"!==e||!r||!n(i))return;if(!Object.keys(r).some(t=>t in i))return;const d=k(s),l=!1!==t(r,c.consent);await a(()=>l?d.enableCookie():d.disableCookie(),"consent-not-applied","TikTok did not take the consent update")}},p=y;export{l as DestinationTikTok,p as default,y as destinationTikTok};//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/types/index.ts"],"sourcesContent":["import type { WalkerOS } from '@walkeros/core';\nimport { getMappingValue, isObject, isString } from '@walkeros/core';\nimport { getEnv } from '@walkeros/web-core';\nimport { addScript, setup } from './setup';\nimport type {\n Destination,\n IdentifyParams,\n Mapping,\n RuntimeState,\n Settings,\n TTQ,\n} from './types';\n\n// Types export\nexport * as DestinationTikTok from './types';\n\n/**\n * Read the TikTok `ttq` function off env.window. getEnv() transparently\n * falls through to the global window when env is undefined (production),\n * or uses env.window when the caller provides a mock (tests).\n */\nfunction getTTQ(env: unknown): TTQ {\n const { window } = getEnv(env as Parameters<typeof getEnv>[0]);\n return (window as unknown as { ttq: TTQ }).ttq;\n}\n\n/**\n * Resolve an identify mapping value and, if any Advanced Matching\n * parameter changed since last push, call ttq.identify() with the full\n * resolved object. Returns the updated lastIdentity snapshot.\n *\n * The destination never partial-updates identity - TikTok treats each\n * ttq.identify() call as a full replacement of Advanced Matching state.\n */\nfunction applyIdentify(\n ttq: TTQ,\n resolved: Record<string, unknown>,\n lastIdentity: NonNullable<RuntimeState['lastIdentity']> = {},\n): NonNullable<RuntimeState['lastIdentity']> {\n const next: NonNullable<RuntimeState['lastIdentity']> = { ...lastIdentity };\n const params: IdentifyParams = {};\n let changed = false;\n\n for (const key of ['email', 'phone_number', 'external_id'] as const) {\n const raw = resolved[key];\n if (!isString(raw) || raw === '') continue;\n params[key] = raw;\n if (next[key] !== raw) {\n next[key] = raw;\n changed = true;\n }\n }\n\n // Skip ttq.identify() entirely if the resolved object has no non-empty\n // values (anonymous visitor) or if nothing changed.\n if (!changed || Object.keys(params).length === 0) return next;\n\n ttq.identify(params);\n return next;\n}\n\nexport const destinationTikTok: Destination = {\n type: 'tiktok',\n\n config: {},\n\n init({ config, env }) {\n const settings = config.settings;\n if (!settings?.apiKey) return false;\n\n // Ensure window.ttq exists (queue-based stub) before any call.\n setup(env);\n\n // Load the real TikTok Pixel SDK from the CDN. The queue replays\n // once the script executes.\n if (config.loadScript !== false) addScript(env);\n\n const ttq = getTTQ(env);\n\n // Build ttq.load() second-arg config by stripping walkerOS-specific\n // keys and forwarding the rest as TikTok passthrough options.\n const {\n apiKey,\n identify: _identify,\n _state: _existingState,\n ...passthrough\n } = settings;\n\n // TikTok's SDK auto-fires ttq.page() on load; we let it fire (no knob).\n const loadOptions: Record<string, unknown> = { ...passthrough };\n\n ttq.load(apiKey, loadOptions);\n\n // Initialize runtime state. Identity is resolved on first push.\n const _state: RuntimeState = { lastIdentity: {} };\n return { ...config, settings: { ...settings, _state } };\n },\n\n async push(event, { config, rule, data, env, collector }) {\n const ttq = getTTQ(env);\n const settings = (config.settings || {}) as Settings;\n const mappingSettings = (rule?.settings || {}) as Mapping;\n\n const state: RuntimeState = settings._state || {};\n\n // Identity - rule-level override wins over destination-level.\n // Resolved and diffed against runtime state; only changes fire\n // ttq.identify(). Order: identify BEFORE track so Advanced Matching\n // is set for the conversion event.\n const identifyMapping = mappingSettings.identify ?? settings.identify;\n if (identifyMapping !== undefined) {\n const resolved = await getMappingValue(event, identifyMapping, {\n collector,\n });\n if (isObject(resolved)) {\n state.lastIdentity = applyIdentify(\n ttq,\n resolved as Record<string, unknown>,\n state.lastIdentity,\n );\n }\n }\n\n // Default track. rule.silent suppresses the call entirely (e.g., for\n // rules that are identify-only).\n if (rule?.silent !== true) {\n const eventName = isString(rule?.name) ? rule.name : event.name;\n const params: Record<string, unknown> = isObject(data)\n ? (data as Record<string, unknown>)\n : {};\n\n ttq.track(eventName, params, { event_id: event.id });\n }\n\n // Persist runtime state mutations. walkerOS destinations are\n // long-lived singletons so mutating settings in place is safe and\n // matches the Snowplow precedent.\n settings._state = state;\n },\n\n on(type, context) {\n if (type !== 'consent' || !context.data) return;\n const ttq = getTTQ(context.env);\n\n const consent = context.data as WalkerOS.Consent;\n // Derive the consent key(s) to check from config.consent. TikTok is\n // an advertising tool, so the typical key is \"marketing\". If ALL\n // required keys are granted, enable cookies; otherwise disable. A\n // missing config.consent means nothing to check and we take no\n // action.\n const required = context.config?.consent;\n if (!required || Object.keys(required).length === 0) return;\n\n const allGranted = Object.keys(required).every(\n (key) => consent[key] === true,\n );\n\n if (allGranted) {\n ttq.enableCookie();\n } else {\n ttq.disableCookie();\n }\n },\n};\n\nexport default destinationTikTok;\n","import type { DestinationWeb } from '@walkeros/web-core';\nimport { getEnv } from '@walkeros/web-core';\n\nconst DEFAULT_TIKTOK_SRC = 'https://analytics.tiktok.com/i18n/pixel/events.js';\n\n/**\n * Inject the TikTok Pixel CDN script. The real SDK replaces the queueing\n * stub created by setup() with a full implementation.\n */\nexport function addScript(\n env?: DestinationWeb.Env,\n src = DEFAULT_TIKTOK_SRC,\n): void {\n const { document } = getEnv(env);\n const script = (document as Document).createElement('script');\n script.src = src;\n script.async = true;\n (document as Document).head.appendChild(script);\n}\n\ninterface TTQFunction {\n (...args: unknown[]): void;\n methods?: string[];\n _i?: Record<string, unknown>;\n loaded?: boolean;\n page?: (...args: unknown[]) => void;\n track?: (...args: unknown[]) => void;\n identify?: (...args: unknown[]) => void;\n enableCookie?: (...args: unknown[]) => void;\n disableCookie?: (...args: unknown[]) => void;\n load?: (...args: unknown[]) => void;\n}\n\n/**\n * Create the `window.ttq` queue object (TikTok's snippet pattern).\n * Any method call before the real SDK loads is buffered; once the CDN\n * script runs, the queue is replayed in order. Idempotent — does\n * nothing if ttq already exists on the window.\n */\nexport function setup(env?: DestinationWeb.Env): void {\n const { window } = getEnv(env);\n const w = window as unknown as { ttq?: TTQFunction };\n if (w.ttq && (w.ttq as TTQFunction).loaded) return;\n\n // Preserve any existing mock ttq (from env.window.ttq in tests). Only\n // construct a new queue if nothing is there.\n if (!w.ttq) {\n const n = function (...args: unknown[]): void {\n (n._i as Record<string, unknown[]>) ||= {};\n ((n._i as { all?: unknown[] }).all ||= []).push(args);\n } as TTQFunction;\n\n n.methods = [\n 'page',\n 'track',\n 'identify',\n 'instances',\n 'debug',\n 'on',\n 'off',\n 'once',\n 'ready',\n 'alias',\n 'group',\n 'enableCookie',\n 'disableCookie',\n ];\n\n // Stub out each method to enqueue through the root `n` function.\n for (const method of n.methods) {\n (n as unknown as Record<string, unknown>)[method] = (\n ...args: unknown[]\n ) => n(method, ...args);\n }\n\n n._i = {};\n n.loaded = false;\n w.ttq = n;\n }\n}\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 * TikTok's 14 recognized standard event names — rigid taxonomy for ad\n * optimization. `mapping.name` values from this union light up IDE\n * autocomplete; `| string` still allows arbitrary custom event names\n * (which TikTok treats as custom events with no optimization value).\n */\nexport type StandardEventNames =\n | 'ViewContent'\n | 'ClickButton'\n | 'Search'\n | 'AddToWishlist'\n | 'AddToCart'\n | 'InitiateCheckout'\n | 'AddPaymentInfo'\n | 'CompletePayment'\n | 'PlaceAnOrder'\n | 'Contact'\n | 'Download'\n | 'SubmitForm'\n | 'CompleteRegistration'\n | 'Subscribe'\n | (string & {});\n\n/**\n * Settings (destination-level).\n *\n * apiKey is the TikTok Pixel ID. `identify` resolves to Advanced Matching\n * parameters (email, phone_number, external_id) — TikTok auto-hashes these\n * before sending, so raw values are safe. `include` names walkerOS event\n * sections to flatten into prefixed TikTok event parameters. All other\n * TikTok-native snake_case options (e.g., `auto_config`, `limited_data_use`)\n * are passed through to `ttq.load(apiKey, options)`.\n */\nexport interface Settings {\n /** TikTok Pixel ID — first argument to ttq.load(...). Required. */\n apiKey: string;\n /**\n * walkerOS mapping value resolving to an Advanced Matching object with any\n * of: `email`, `phone_number`, `external_id`. Applied on first push and\n * re-fired only on change (runtime state in _state.lastIdentity).\n */\n identify?: WalkerOSMapping.Value;\n // `pageview` removed 2026-04-09 — TikTok's auto page view fires from the\n // SDK and cannot be reliably suppressed via init config. Letting it fire\n // is the expected behavior.\n /** TikTok default: true. Enable auto-detection of form fields for Advanced Matching. */\n auto_config?: boolean;\n /** TikTok default: false. Restrict data use under U.S. state privacy laws. */\n limited_data_use?: boolean;\n /**\n * Runtime state — populated by init() and mutated by push(). Not\n * user-facing. Stores last-resolved identity so subsequent pushes with\n * unchanged values skip redundant ttq.identify() calls.\n */\n _state?: RuntimeState;\n /**\n * Pass-through bucket: any additional TikTok Pixel init config (snake_case\n * keys per TikTok's docs). Merged into ttq.load()'s second argument.\n */\n [key: string]: unknown;\n}\n\nexport interface RuntimeState {\n lastIdentity?: {\n email?: string;\n phone_number?: string;\n external_id?: string;\n };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Mapping (rule.settings) — per-event overrides.\n *\n * identify — mapping value resolving to an Advanced Matching object (same\n * shape as settings.identify). Merged onto/replaces the\n * destination-level identity for this single push.\n * include — replaces destination-level include for this rule only.\n */\nexport interface Mapping {\n identify?: WalkerOSMapping.Value;\n}\n\n/**\n * Advanced Matching parameter shape — what settings.identify must resolve to.\n * All fields are optional; if the resolved object has no non-empty values,\n * the destination skips the ttq.identify() call entirely.\n */\nexport interface IdentifyParams {\n email?: string;\n phone_number?: string;\n external_id?: string;\n}\n\n/**\n * Minimal surface of TikTok's window.ttq this destination uses. Tests mock\n * this by attaching a plain object to env.window.ttq; production uses the\n * real SDK created by the snippet setup() call.\n */\nexport interface TTQ {\n (...args: unknown[]): void;\n load: (pixelId: string, config?: Record<string, unknown>) => void;\n page: () => void;\n track: (\n event: string,\n params?: Record<string, unknown>,\n options?: { event_id?: string },\n ) => void;\n identify: (params: IdentifyParams) => void;\n enableCookie: () => void;\n disableCookie: () => void;\n /** Queue-based pattern — snippet sets this; destination does not read it. */\n methods?: string[];\n /** Internal queue populated before the CDN SDK loads. */\n _i?: Record<string, unknown>;\n /** Flag the snippet sets after the real SDK loads. */\n loaded?: boolean;\n}\n\n/**\n * Env — the destination reads `window.ttq` via getEnv(env). Tests attach\n * a mock ttq to env.window; production reads window.ttq created by the\n * SDK snippet.\n */\nexport interface Env extends DestinationWeb.Env {\n window: {\n ttq: TTQ;\n };\n document: {\n createElement: (tagName: string) => Element;\n head: { appendChild: (node: unknown) => void };\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 TikTokDestination 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":["getEnv","getEnv"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/setup.ts","../src/types/index.ts"],"sourcesContent":["import type { Logger, WalkerOS } from '@walkeros/core';\nimport {\n getGrantedConsent,\n getMappingValue,\n isObject,\n isString,\n StepError,\n stepCall,\n} from '@walkeros/core';\nimport type { DestinationWeb } from '@walkeros/web-core';\nimport { getEnv, loadVendor } from '@walkeros/web-core';\nimport { SCRIPT_SRC, setup } from './setup';\nimport type {\n Destination,\n IdentifyParams,\n Mapping,\n RuntimeState,\n Settings,\n TTQ,\n} from './types';\n\nconst TTQ_METHODS = [\n 'load',\n 'track',\n 'identify',\n 'enableCookie',\n 'disableCookie',\n];\n\n/** A loaded Pixel (or a test mock), not the queue `setup()` installs. */\nfunction isTTQ(value: unknown): value is TTQ {\n return (\n typeof value === 'function' &&\n TTQ_METHODS.every((key) => typeof Reflect.get(value, key) === 'function')\n );\n}\n\n// Types export\nexport * as DestinationTikTok from './types';\n\n/**\n * Read the TikTok `ttq` function off env.window. getEnv() transparently\n * falls through to the global window when env is undefined (production),\n * or uses env.window when the caller provides a mock (tests).\n */\nfunction getTTQ(env?: DestinationWeb.Env): TTQ {\n const { ttq } = getEnv(env).window;\n if (!isTTQ(ttq))\n throw new StepError('TikTok Pixel not ready: window.ttq is missing', {\n code: 'vendor-not-ready',\n });\n return ttq;\n}\n\n/**\n * The Pixel script `loadVendor` injects unless `loadScript` is false. ttq\n * replays its own queue, so it is called synchronously and only a failed\n * script is reported.\n */\nfunction vendor(\n config: { loadScript?: boolean; timeout?: number },\n env?: DestinationWeb.Env,\n) {\n return loadVendor(\n config.loadScript !== false ? SCRIPT_SRC : undefined,\n () => getEnv(env).window.ttq,\n { timeout: config.timeout, document: getEnv(env).document },\n );\n}\n\n/**\n * Resolve an identify mapping value and, if any Advanced Matching\n * parameter changed since last push, call ttq.identify() with the full\n * resolved object. Returns the updated lastIdentity snapshot.\n *\n * The destination never partial-updates identity - TikTok treats each\n * ttq.identify() call as a full replacement of Advanced Matching state.\n */\nasync function applyIdentify(\n ttq: TTQ,\n resolved: Record<string, unknown>,\n lastIdentity: NonNullable<RuntimeState['lastIdentity']> = {},\n): Promise<NonNullable<RuntimeState['lastIdentity']> | undefined> {\n const next: NonNullable<RuntimeState['lastIdentity']> = { ...lastIdentity };\n const params: IdentifyParams = {};\n let changed = false;\n\n for (const key of ['email', 'phone_number', 'external_id'] as const) {\n const raw = resolved[key];\n if (!isString(raw) || raw === '') continue;\n params[key] = raw;\n if (next[key] !== raw) {\n next[key] = raw;\n changed = true;\n }\n }\n\n // No non-empty value resolved: nothing to identify. An identity that is\n // already current skips ttq.identify() as well.\n if (Object.keys(params).length === 0) return undefined;\n if (!changed) return next;\n\n await stepCall(\n () => ttq.identify(params),\n 'transport',\n 'TikTok identify failed',\n );\n return next;\n}\n\n/**\n * A configured mapping that resolved to nothing skips its call: a declared\n * skip, said once at DEBUG with the event and the mapping key, never the\n * value, so a wrong mapping path does not look like an event without data.\n */\nfunction skipped(\n logger: Logger.Instance,\n event: WalkerOS.Event,\n call: string,\n key: string,\n): void {\n logger.debug(`TikTok skipped ${call}: ${key} resolved to nothing`, {\n event: event.name,\n });\n}\n\nexport const destinationTikTok: Destination = {\n type: 'tiktok',\n\n config: {},\n\n async init({ config, env }) {\n // A copy, so the destructure below does not read keys off the live settings\n const settings = { ...config.settings };\n if (!settings.apiKey)\n throw new StepError('TikTok needs settings.apiKey', { code: 'config' });\n\n // Ensure window.ttq exists (queue-based stub) before any call.\n setup(env);\n\n // Load the real TikTok Pixel SDK from the CDN. The queue replays\n // once the script executes.\n vendor(config, env);\n\n const ttq = getTTQ(env);\n\n // Build ttq.load() second-arg config by stripping walkerOS-specific\n // keys and forwarding the rest as TikTok passthrough options.\n const {\n apiKey,\n identify: _identify,\n _state: _existingState,\n ...passthrough\n } = settings;\n\n // TikTok's SDK auto-fires ttq.page() on load; we let it fire (no knob).\n const loadOptions: Record<string, unknown> = { ...passthrough };\n\n await stepCall(\n () => ttq.load(apiKey, loadOptions),\n 'vendor-not-ready',\n 'TikTok load call failed',\n );\n\n // Initialize runtime state. Identity is resolved on first push.\n const _state: RuntimeState = { lastIdentity: {} };\n return { ...config, settings: { ...settings, _state } };\n },\n\n async push(event, { config, rule, data, env, collector, logger }) {\n vendor(config, env).check();\n const ttq = getTTQ(env);\n const settings: Partial<Settings> = config.settings || {};\n const mappingSettings: Mapping = rule?.settings || {};\n\n const state: RuntimeState = settings._state || {};\n\n // Identity - rule-level override wins over destination-level.\n // Resolved and diffed against runtime state; only changes fire\n // ttq.identify(). Order: identify BEFORE track so Advanced Matching\n // is set for the conversion event.\n const identifyMapping = mappingSettings.identify ?? settings.identify;\n if (identifyMapping !== undefined) {\n const resolved = await getMappingValue(event, identifyMapping, {\n collector,\n });\n const identity = isObject(resolved)\n ? await applyIdentify(ttq, resolved, state.lastIdentity)\n : undefined;\n if (identity) state.lastIdentity = identity;\n else skipped(logger, event, 'identify', 'identify');\n }\n\n // Default track. rule.silent suppresses the call entirely (e.g., for\n // rules that are identify-only).\n if (rule?.silent !== true) {\n const eventName = isString(rule?.name) ? rule.name : event.name;\n const params: Record<string, unknown> = isObject(data) ? data : {};\n\n await stepCall(\n () => ttq.track(eventName, params, { event_id: event.id }),\n 'transport',\n 'TikTok track failed',\n );\n }\n\n // A silent rule whose identify resolves nothing (or an identity that is\n // already current) sends nothing: a declared skip, not a failure.\n // Persist runtime state mutations. walkerOS destinations are\n // long-lived singletons so mutating settings in place is safe and\n // matches the Snowplow precedent.\n settings._state = state;\n },\n\n // The destination's required consent decides on the whole consent state,\n // as it gates the pushes, and only a change that names a required key\n // reaches TikTok: a revocation disables its cookies, a grant enables them.\n async on(type, { config, data, env, collector }) {\n const required = config.consent;\n if (type !== 'consent' || !required || !isObject(data)) return;\n if (!Object.keys(required).some((key) => key in data)) return;\n\n const ttq = getTTQ(env);\n const granted = getGrantedConsent(required, collector.consent) !== false;\n await stepCall(\n () => (granted ? ttq.enableCookie() : ttq.disableCookie()),\n 'consent-not-applied',\n 'TikTok did not take the consent update',\n );\n },\n};\n\nexport default destinationTikTok;\n","import type { DestinationWeb } from '@walkeros/web-core';\nimport type { TTQStub } from './types';\nimport { getEnv } from '@walkeros/web-core';\n\n/** The TikTok Pixel library, loaded through `loadVendor`. */\nexport const SCRIPT_SRC = 'https://analytics.tiktok.com/i18n/pixel/events.js';\n\nconst METHODS = [\n 'page',\n 'track',\n 'identify',\n 'instances',\n 'debug',\n 'on',\n 'off',\n 'once',\n 'ready',\n 'alias',\n 'group',\n 'enableCookie',\n 'disableCookie',\n];\n\n/**\n * Create the `window.ttq` queue object (TikTok's snippet pattern).\n * Any method call before the real SDK loads is buffered; once the CDN\n * script runs, the queue is replayed in order. Idempotent: does nothing if\n * ttq already exists on the window (a loaded Pixel, or a mock in tests).\n */\nexport function setup(env?: DestinationWeb.Env): void {\n const { window } = getEnv(env);\n if (window.ttq) return;\n\n const record = (...args: unknown[]): void => {\n (stub._i.all ||= []).push(args);\n };\n const stub: TTQStub = Object.assign(record, {\n methods: [...METHODS],\n _i: {},\n loaded: false,\n });\n\n // Stub out each method to enqueue through the root function.\n window.ttq = Object.assign(\n stub,\n Object.fromEntries(\n METHODS.map((method) => [\n method,\n (...args: unknown[]) => stub(method, ...args),\n ]),\n ),\n );\n}\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 * TikTok's 14 recognized standard event names — rigid taxonomy for ad\n * optimization. `mapping.name` values from this union light up IDE\n * autocomplete; `| string` still allows arbitrary custom event names\n * (which TikTok treats as custom events with no optimization value).\n */\nexport type StandardEventNames =\n | 'ViewContent'\n | 'ClickButton'\n | 'Search'\n | 'AddToWishlist'\n | 'AddToCart'\n | 'InitiateCheckout'\n | 'AddPaymentInfo'\n | 'CompletePayment'\n | 'PlaceAnOrder'\n | 'Contact'\n | 'Download'\n | 'SubmitForm'\n | 'CompleteRegistration'\n | 'Subscribe'\n | (string & {});\n\n/**\n * Settings (destination-level).\n *\n * apiKey is the TikTok Pixel ID. `identify` resolves to Advanced Matching\n * parameters (email, phone_number, external_id) — TikTok auto-hashes these\n * before sending, so raw values are safe. `include` names walkerOS event\n * sections to flatten into prefixed TikTok event parameters. All other\n * TikTok-native snake_case options (e.g., `auto_config`, `limited_data_use`)\n * are passed through to `ttq.load(apiKey, options)`.\n */\nexport interface Settings {\n /** TikTok Pixel ID — first argument to ttq.load(...). Required. */\n apiKey: string;\n /**\n * walkerOS mapping value resolving to an Advanced Matching object with any\n * of: `email`, `phone_number`, `external_id`. Applied on first push and\n * re-fired only on change (runtime state in _state.lastIdentity).\n */\n identify?: WalkerOSMapping.Value;\n // `pageview` removed 2026-04-09 — TikTok's auto page view fires from the\n // SDK and cannot be reliably suppressed via init config. Letting it fire\n // is the expected behavior.\n /** TikTok default: true. Enable auto-detection of form fields for Advanced Matching. */\n auto_config?: boolean;\n /** TikTok default: false. Restrict data use under U.S. state privacy laws. */\n limited_data_use?: boolean;\n /**\n * Runtime state — populated by init() and mutated by push(). Not\n * user-facing. Stores last-resolved identity so subsequent pushes with\n * unchanged values skip redundant ttq.identify() calls.\n */\n _state?: RuntimeState;\n /**\n * Pass-through bucket: any additional TikTok Pixel init config (snake_case\n * keys per TikTok's docs). Merged into ttq.load()'s second argument.\n */\n [key: string]: unknown;\n}\n\nexport interface RuntimeState {\n lastIdentity?: {\n email?: string;\n phone_number?: string;\n external_id?: string;\n };\n}\n\nexport type InitSettings = Partial<Settings>;\n\n/**\n * Mapping (rule.settings) — per-event overrides.\n *\n * identify — mapping value resolving to an Advanced Matching object (same\n * shape as settings.identify). Merged onto/replaces the\n * destination-level identity for this single push.\n * include — replaces destination-level include for this rule only.\n */\nexport interface Mapping {\n identify?: WalkerOSMapping.Value;\n}\n\n/**\n * Advanced Matching parameter shape — what settings.identify must resolve to.\n * All fields are optional; if the resolved object has no non-empty values,\n * the destination skips the ttq.identify() call entirely.\n */\nexport interface IdentifyParams {\n email?: string;\n phone_number?: string;\n external_id?: string;\n}\n\n/**\n * Minimal surface of TikTok's window.ttq this destination uses. Tests mock\n * this by attaching a plain object to env.window.ttq; production uses the\n * real SDK created by the snippet setup() call.\n */\nexport interface TTQ {\n (...args: unknown[]): void;\n load: (pixelId: string, config?: Record<string, unknown>) => void;\n page: () => void;\n track: (\n event: string,\n params?: Record<string, unknown>,\n options?: { event_id?: string },\n ) => void;\n identify: (params: IdentifyParams) => void;\n enableCookie: () => void;\n disableCookie: () => void;\n /** Queue-based pattern — snippet sets this; destination does not read it. */\n methods?: string[];\n /** Internal queue populated before the CDN SDK loads. */\n _i?: Record<string, unknown>;\n /** Flag the snippet sets after the real SDK loads. */\n loaded?: boolean;\n}\n\n/**\n * Env — the destination reads `window.ttq` via getEnv(env). Tests attach\n * a mock ttq to env.window; production reads window.ttq created by the\n * SDK snippet.\n */\nexport interface Env extends DestinationWeb.Env {\n window: {\n ttq: TTQ;\n };\n}\n\n/**\n * The queue `setup()` installs until the Pixel script replaces it: each\n * call is kept in `_i.all`.\n */\nexport type TTQStub = ((...args: unknown[]) => void) & {\n methods: string[];\n _i: Record<string, unknown[]>;\n loaded: boolean;\n};\n\ndeclare global {\n interface Window {\n ttq?: TTQ | TTQStub;\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 TikTokDestination 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":["getEnv","getEnv"]}
|
package/dist/walkerOS.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$meta": {
|
|
3
3
|
"package": "@walkeros/web-destination-tiktok",
|
|
4
|
-
"version": "4.8.
|
|
4
|
+
"version": "4.8.2",
|
|
5
5
|
"type": "destination",
|
|
6
6
|
"platform": [
|
|
7
7
|
"web"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"required": [
|
|
46
46
|
"apiKey"
|
|
47
47
|
],
|
|
48
|
-
"additionalProperties":
|
|
48
|
+
"additionalProperties": {}
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"examples": {
|
|
@@ -57,12 +57,15 @@
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
"document": {
|
|
60
|
+
"querySelectorAll": {
|
|
61
|
+
"$code": "()=>[...t]"
|
|
62
|
+
},
|
|
60
63
|
"createElement": {
|
|
61
|
-
"$code": "()=>
|
|
64
|
+
"$code": "()=>{const t=new Map,r={src:\"\",async:!1,getAttribute:e=>{var s;return\"src\"===e?r.src:null!=(s=t.get(e))?s:null},setAttribute:(e,r)=>{t.set(e,r)},addEventListener:(t,s)=>{var n;\"load\"===t&&e.set(r,[...null!=(n=e.get(r))?n:[],s])}};return r}"
|
|
62
65
|
},
|
|
63
66
|
"head": {
|
|
64
67
|
"appendChild": {
|
|
65
|
-
"$code": "()=>
|
|
68
|
+
"$code": "r=>{var s;t.push(r),(null!=(s=e.get(r))?s:[]).forEach(t=>t())}"
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -74,12 +77,15 @@
|
|
|
74
77
|
}
|
|
75
78
|
},
|
|
76
79
|
"document": {
|
|
80
|
+
"querySelectorAll": {
|
|
81
|
+
"$code": "()=>[...t]"
|
|
82
|
+
},
|
|
77
83
|
"createElement": {
|
|
78
|
-
"$code": "()=>
|
|
84
|
+
"$code": "()=>{const t=new Map,r={src:\"\",async:!1,getAttribute:e=>{var s;return\"src\"===e?r.src:null!=(s=t.get(e))?s:null},setAttribute:(e,r)=>{t.set(e,r)},addEventListener:(t,s)=>{var n;\"load\"===t&&e.set(r,[...null!=(n=e.get(r))?n:[],s])}};return r}"
|
|
79
85
|
},
|
|
80
86
|
"head": {
|
|
81
87
|
"appendChild": {
|
|
82
|
-
"$code": "()=>
|
|
88
|
+
"$code": "r=>{var s;t.push(r),(null!=(s=e.get(r))?s:[]).forEach(t=>t())}"
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
}
|
|
@@ -121,10 +127,7 @@
|
|
|
121
127
|
"settings": {},
|
|
122
128
|
"out": [
|
|
123
129
|
[
|
|
124
|
-
"ttq.
|
|
125
|
-
],
|
|
126
|
-
[
|
|
127
|
-
"ttq.disableCookie"
|
|
130
|
+
"window.ttq.disableCookie"
|
|
128
131
|
]
|
|
129
132
|
]
|
|
130
133
|
},
|
|
@@ -165,7 +168,7 @@
|
|
|
165
168
|
"trigger": "load",
|
|
166
169
|
"entity": "product",
|
|
167
170
|
"action": "view",
|
|
168
|
-
"timestamp":
|
|
171
|
+
"timestamp": 1700000000000,
|
|
169
172
|
"timing": 3.14,
|
|
170
173
|
"source": {
|
|
171
174
|
"count": 1,
|
|
@@ -226,7 +229,7 @@
|
|
|
226
229
|
"trigger": "load",
|
|
227
230
|
"entity": "page",
|
|
228
231
|
"action": "view",
|
|
229
|
-
"timestamp":
|
|
232
|
+
"timestamp": 1700000000000,
|
|
230
233
|
"timing": 3.14,
|
|
231
234
|
"source": {
|
|
232
235
|
"count": 1,
|
|
@@ -300,7 +303,7 @@
|
|
|
300
303
|
"trigger": "load",
|
|
301
304
|
"entity": "product",
|
|
302
305
|
"action": "view",
|
|
303
|
-
"timestamp":
|
|
306
|
+
"timestamp": 1700000000000,
|
|
304
307
|
"timing": 3.14,
|
|
305
308
|
"source": {
|
|
306
309
|
"count": 1,
|
|
@@ -339,7 +342,7 @@
|
|
|
339
342
|
"currency": "EUR",
|
|
340
343
|
"shipping": 5.22,
|
|
341
344
|
"taxes": 73.76,
|
|
342
|
-
"
|
|
345
|
+
"value": 555
|
|
343
346
|
},
|
|
344
347
|
"context": {
|
|
345
348
|
"shopping": [
|
|
@@ -413,7 +416,7 @@
|
|
|
413
416
|
"trigger": "load",
|
|
414
417
|
"entity": "order",
|
|
415
418
|
"action": "complete",
|
|
416
|
-
"timestamp":
|
|
419
|
+
"timestamp": 1700000000000,
|
|
417
420
|
"timing": 3.14,
|
|
418
421
|
"source": {
|
|
419
422
|
"count": 1,
|
|
@@ -429,7 +432,7 @@
|
|
|
429
432
|
"content_type": {
|
|
430
433
|
"value": "product"
|
|
431
434
|
},
|
|
432
|
-
"value": "data.
|
|
435
|
+
"value": "data.value",
|
|
433
436
|
"currency": {
|
|
434
437
|
"key": "data.currency",
|
|
435
438
|
"value": "EUR"
|
|
@@ -526,7 +529,7 @@
|
|
|
526
529
|
"trigger": "load",
|
|
527
530
|
"entity": "product",
|
|
528
531
|
"action": "view",
|
|
529
|
-
"timestamp":
|
|
532
|
+
"timestamp": 1700000000000,
|
|
530
533
|
"timing": 3.14,
|
|
531
534
|
"source": {
|
|
532
535
|
"count": 1,
|
|
@@ -581,7 +584,7 @@
|
|
|
581
584
|
"currency": "EUR",
|
|
582
585
|
"shipping": 5.22,
|
|
583
586
|
"taxes": 73.76,
|
|
584
|
-
"
|
|
587
|
+
"value": 555
|
|
585
588
|
},
|
|
586
589
|
"context": {
|
|
587
590
|
"shopping": [
|
|
@@ -655,7 +658,7 @@
|
|
|
655
658
|
"trigger": "load",
|
|
656
659
|
"entity": "order",
|
|
657
660
|
"action": "complete",
|
|
658
|
-
"timestamp":
|
|
661
|
+
"timestamp": 1700000000000,
|
|
659
662
|
"timing": 3.14,
|
|
660
663
|
"source": {
|
|
661
664
|
"count": 1,
|
|
@@ -725,7 +728,7 @@
|
|
|
725
728
|
"trigger": "test",
|
|
726
729
|
"entity": "search",
|
|
727
730
|
"action": "submit",
|
|
728
|
-
"timestamp":
|
|
731
|
+
"timestamp": 1700000000000,
|
|
729
732
|
"timing": 3.14,
|
|
730
733
|
"source": {
|
|
731
734
|
"count": 1,
|
|
@@ -756,6 +759,87 @@
|
|
|
756
759
|
]
|
|
757
760
|
]
|
|
758
761
|
},
|
|
762
|
+
"silentIdentifyWithoutData": {
|
|
763
|
+
"public": false,
|
|
764
|
+
"description": "A silent rule whose identify mapping finds no email, phone or id sends nothing: an unmet mapping, not a failure, so the push returns normally.",
|
|
765
|
+
"in": {
|
|
766
|
+
"name": "user update",
|
|
767
|
+
"entity": "user",
|
|
768
|
+
"action": "update"
|
|
769
|
+
},
|
|
770
|
+
"mapping": {
|
|
771
|
+
"silent": true,
|
|
772
|
+
"settings": {
|
|
773
|
+
"identify": {
|
|
774
|
+
"map": {
|
|
775
|
+
"email": "data.email",
|
|
776
|
+
"phone_number": "data.phone",
|
|
777
|
+
"external_id": "data.id"
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
"out": []
|
|
783
|
+
},
|
|
784
|
+
"silentRule": {
|
|
785
|
+
"public": false,
|
|
786
|
+
"description": "A rule with silent: true sends no ttq.track call.",
|
|
787
|
+
"in": {
|
|
788
|
+
"name": "page view",
|
|
789
|
+
"data": {
|
|
790
|
+
"domain": "www.example.com",
|
|
791
|
+
"title": "walkerOS documentation",
|
|
792
|
+
"referrer": "https://www.walkeros.io/",
|
|
793
|
+
"search": "?foo=bar",
|
|
794
|
+
"hash": "#hash",
|
|
795
|
+
"id": "/docs/"
|
|
796
|
+
},
|
|
797
|
+
"context": {
|
|
798
|
+
"dev": [
|
|
799
|
+
"test",
|
|
800
|
+
1
|
|
801
|
+
]
|
|
802
|
+
},
|
|
803
|
+
"globals": {
|
|
804
|
+
"pagegroup": "docs"
|
|
805
|
+
},
|
|
806
|
+
"custom": {
|
|
807
|
+
"completely": "random"
|
|
808
|
+
},
|
|
809
|
+
"user": {
|
|
810
|
+
"id": "us3r",
|
|
811
|
+
"device": "c00k13",
|
|
812
|
+
"session": "s3ss10n"
|
|
813
|
+
},
|
|
814
|
+
"nested": [
|
|
815
|
+
{
|
|
816
|
+
"entity": "child",
|
|
817
|
+
"data": {
|
|
818
|
+
"is": "subordinated"
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
],
|
|
822
|
+
"consent": {
|
|
823
|
+
"functional": true
|
|
824
|
+
},
|
|
825
|
+
"id": "f0cc5f6fec3d8eeb",
|
|
826
|
+
"trigger": "load",
|
|
827
|
+
"entity": "page",
|
|
828
|
+
"action": "view",
|
|
829
|
+
"timestamp": 1700000600,
|
|
830
|
+
"timing": 3.14,
|
|
831
|
+
"source": {
|
|
832
|
+
"count": 1,
|
|
833
|
+
"trace": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
|
|
834
|
+
"type": "collector",
|
|
835
|
+
"schema": "4"
|
|
836
|
+
}
|
|
837
|
+
},
|
|
838
|
+
"mapping": {
|
|
839
|
+
"silent": true
|
|
840
|
+
},
|
|
841
|
+
"out": []
|
|
842
|
+
},
|
|
759
843
|
"userRegisterCompleteRegistration": {
|
|
760
844
|
"title": "Complete registration",
|
|
761
845
|
"description": "A user register fires ttq.identify for advanced matching and then tracks CompleteRegistration.",
|
|
@@ -798,7 +882,7 @@
|
|
|
798
882
|
"trigger": "test",
|
|
799
883
|
"entity": "user",
|
|
800
884
|
"action": "register",
|
|
801
|
-
"timestamp":
|
|
885
|
+
"timestamp": 1700000000000,
|
|
802
886
|
"timing": 3.14,
|
|
803
887
|
"source": {
|
|
804
888
|
"count": 1,
|
|
@@ -880,11 +964,11 @@
|
|
|
880
964
|
"consent": {
|
|
881
965
|
"functional": true
|
|
882
966
|
},
|
|
883
|
-
"id": "
|
|
967
|
+
"id": "db22fb16c70f663d",
|
|
884
968
|
"trigger": "load",
|
|
885
969
|
"entity": "product",
|
|
886
970
|
"action": "view",
|
|
887
|
-
"timestamp":
|
|
971
|
+
"timestamp": 1700000000000,
|
|
888
972
|
"timing": 3.14,
|
|
889
973
|
"source": {
|
|
890
974
|
"count": 1,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkeros/web-destination-tiktok",
|
|
3
3
|
"description": "TikTok Pixel web destination for walkerOS (conversion tracking, Advanced Matching)",
|
|
4
|
-
"version": "4.8.
|
|
4
|
+
"version": "4.8.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
"update": "npx npm-check-updates -u && npm update"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@walkeros/core": "4.8.
|
|
42
|
-
"@walkeros/web-core": "4.8.
|
|
41
|
+
"@walkeros/core": "4.8.2",
|
|
42
|
+
"@walkeros/web-core": "4.8.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@walkeros/collector": "4.8.
|
|
45
|
+
"@walkeros/collector": "4.8.2"
|
|
46
46
|
},
|
|
47
47
|
"repository": {
|
|
48
48
|
"url": "git+https://github.com/elbwalker/walkerOS.git",
|