@shware/analytics 10.0.1 → 10.1.1

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.
Files changed (47) hide show
  1. package/dist/next/index.cjs +2 -1
  2. package/dist/next/index.cjs.map +1 -1
  3. package/dist/next/index.d.cts +11 -1
  4. package/dist/next/index.d.cts.map +1 -1
  5. package/dist/next/index.d.mts +11 -1
  6. package/dist/next/index.d.mts.map +1 -1
  7. package/dist/next/index.mjs +2 -1
  8. package/dist/next/index.mjs.map +1 -1
  9. package/dist/server/google-data-manager.cjs +11 -5
  10. package/dist/server/google-data-manager.cjs.map +1 -1
  11. package/dist/server/google-data-manager.d.cts +6 -3
  12. package/dist/server/google-data-manager.d.cts.map +1 -1
  13. package/dist/server/google-data-manager.d.mts +6 -3
  14. package/dist/server/google-data-manager.d.mts.map +1 -1
  15. package/dist/server/google-data-manager.mjs +11 -5
  16. package/dist/server/google-data-manager.mjs.map +1 -1
  17. package/dist/server/linkedin-conversions-api.cjs +17 -6
  18. package/dist/server/linkedin-conversions-api.cjs.map +1 -1
  19. package/dist/server/linkedin-conversions-api.d.cts.map +1 -1
  20. package/dist/server/linkedin-conversions-api.d.mts.map +1 -1
  21. package/dist/server/linkedin-conversions-api.mjs +17 -6
  22. package/dist/server/linkedin-conversions-api.mjs.map +1 -1
  23. package/dist/server/meta-capi.cjs +25 -7
  24. package/dist/server/meta-capi.cjs.map +1 -1
  25. package/dist/server/meta-capi.d.cts +14 -1
  26. package/dist/server/meta-capi.d.cts.map +1 -1
  27. package/dist/server/meta-capi.d.mts +14 -1
  28. package/dist/server/meta-capi.d.mts.map +1 -1
  29. package/dist/server/meta-capi.mjs +25 -8
  30. package/dist/server/meta-capi.mjs.map +1 -1
  31. package/dist/server/meta-conversions-api.cjs +9 -12
  32. package/dist/server/meta-conversions-api.cjs.map +1 -1
  33. package/dist/server/meta-conversions-api.d.cts.map +1 -1
  34. package/dist/server/meta-conversions-api.d.mts.map +1 -1
  35. package/dist/server/meta-conversions-api.mjs +9 -12
  36. package/dist/server/meta-conversions-api.mjs.map +1 -1
  37. package/dist/tanstack/analytics.cjs +2 -1
  38. package/dist/tanstack/analytics.cjs.map +1 -1
  39. package/dist/tanstack/analytics.d.cts +11 -1
  40. package/dist/tanstack/analytics.d.cts.map +1 -1
  41. package/dist/tanstack/analytics.d.mts +11 -1
  42. package/dist/tanstack/analytics.d.mts.map +1 -1
  43. package/dist/tanstack/analytics.mjs +2 -1
  44. package/dist/tanstack/analytics.mjs.map +1 -1
  45. package/dist/third-parties/index.d.cts +1 -1
  46. package/dist/third-parties/index.d.mts +1 -1
  47. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"meta-capi.mjs","names":[],"sources":["../../src/server/meta-capi.ts"],"sourcesContent":["/**\n * Meta Conversions API sender built on `capi-param-builder-nodejs` and plain `fetch`, replacing\n * the 31MB `facebook-nodejs-business-sdk` with ~0.5MB of parameter building. The SDK-based\n * sender survives untouched at `@shware/analytics/server/legacy`; this module is its drop-in\n * successor.\n *\n * The wire payload is byte-identical to what the business SDK's `ServerEvent.normalize()`\n * produces — enforced by a differential test that runs both builders over the same events.\n * Where the two libraries normalize differently (the business SDK keeps punctuation in names,\n * spaces in multi-word last names, accents in cities; the param builder strips them per the\n * current documented rules), this module follows the business SDK so that switching senders\n * cannot change a single hash Meta receives. The param builder is used where the two provably\n * agree — email, phone, gender, country — and its adoption-tracking appendix (`<hash>.<token>`)\n * is stripped for the same reason.\n *\n * Two deliberate behavior differences from the business SDK, both on invalid input only:\n * an invalid field (malformed email, non-ISO country, letters in a phone) is dropped from\n * user_data instead of throwing inside `execute()` and costing the whole batch; an invalid\n * currency is uppercased and forwarded instead of rejected. Valid input is unaffected.\n *\n * https://developers.facebook.com/docs/marketing-api/conversions-api/using-the-api\n * https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nimport { createHash } from 'node:crypto';\nimport { fetch } from '@shware/utils';\nimport { PII_DATA_TYPE, ParamBuilder } from 'capi-param-builder-nodejs';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\nimport { resolveActionSource } from './action-source';\nimport { metaFbc, metaFbp } from './click-ids';\nimport { pageLocation } from './page-location';\n\n/** Matches the Graph API version the pinned business SDK speaks (`FacebookAdsApi.VERSION`). */\nconst API_VERSION = 'v24.0';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\n/**\n * Lazy for the same reason as the session and the token bucket (see setup/session.ts):\n * Cloudflare Workers refuse some work in module scope, and nothing here is per-request state.\n */\nlet paramBuilder: ParamBuilder | undefined;\n\n/**\n * Normalize and hash one PII value through the param builder, stripped to the bare SHA-256 the\n * business SDK sends: the builder appends a `.<language-token>` adoption marker that Meta's\n * endpoint tolerates but the business SDK never produced. Pre-hashed input passes through, as\n * it does in the business SDK.\n */\nfunction pii(value: string | undefined, dataType: string): string | undefined {\n if (!value) return undefined;\n const built = (paramBuilder ??= new ParamBuilder()).getNormalizedAndHashedPII(value, dataType);\n return built?.split('.').at(0) ?? undefined;\n}\n\nconst SHA256_OR_MD5 = /^[a-f0-9]{64}$|^[a-f0-9]{32}$/;\n\n/**\n * The business SDK's `normalizeAndHash` for the fields where the param builder normalizes\n * differently (names, city, state, zip): trim + lowercase, pass a pre-hashed value through,\n * apply the field rule, SHA-256. Field rules are copied from `ServerSideUtils` verbatim so the\n * differential test can hold the outputs equal.\n */\nfunction hashed(\n input: string | undefined,\n rule: (lowered: string) => string | undefined\n): string | undefined {\n if (!input) return undefined;\n const lowered = input.trim().toLowerCase();\n if (!lowered) return undefined;\n if (SHA256_OR_MD5.test(lowered)) return lowered;\n const normalized = rule(lowered);\n if (normalized === undefined) return undefined;\n return createHash('sha256').update(normalized).digest('hex');\n}\n\nconst nameRule = (v: string) => v;\nconst f5Rule = (v: string) => (v.length <= 5 ? v : v.slice(0, 5));\nconst cityStateRule = (v: string) => v.replace(/[0-9\\s().-]/g, '');\nconst zipRule = (v: string) => {\n const zip = v.replace(/\\s/g, '').split('-', 1)[0];\n return zip.length < 2 ? undefined : zip;\n};\n\n/**\n * Every multi-value field is deduplicated after hashing, order preserved — the SDK runs each\n * through `dedupArray` (a Set), `external_id` included.\n */\nconst dedupe = (hashes: (string | undefined)[]) =>\n Array.from(new Set(hashes.filter((hash): hash is string => hash !== undefined)));\n\nconst list = (hashes: (string | undefined)[]) => {\n const deduped = dedupe(hashes);\n return deduped.length > 0 ? deduped : undefined;\n};\n\ninterface CapiUserData {\n em?: string[];\n ph?: string[];\n ge?: string[];\n fn?: string[];\n ln?: string[];\n ct?: string[];\n st?: string[];\n zp?: string[];\n country?: string[];\n external_id?: string[];\n client_ip_address?: string;\n client_user_agent?: string;\n fbc?: string;\n fbp?: string;\n fb_login_id?: string;\n page_id?: string;\n f5first?: string;\n f5last?: string;\n dobd?: string;\n dobm?: string;\n doby?: string;\n madid?: string;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData, eventTimeMs: number): CapiUserData {\n const userData: CapiUserData = {};\n\n if (data.email) {\n const emails = Array.isArray(data.email) ? data.email : [data.email];\n userData.em = list(emails.map((email) => pii(email, PII_DATA_TYPE.EMAIL)));\n }\n if (data.phone_number) {\n const phones = Array.isArray(data.phone_number) ? data.phone_number : [data.phone_number];\n userData.ph = list(phones.map((phone) => pii(phone, PII_DATA_TYPE.PHONE)));\n }\n if (data.gender === 'female' || data.gender === 'male') {\n userData.ge = list([pii(data.gender === 'female' ? 'f' : 'm', PII_DATA_TYPE.GENDER)]);\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n // The SDK sets these lists unconditionally in the multi-address branch, so an all-empty\n // column still goes out as `[]` — kept for byte-equality with the current sender.\n userData.fn = dedupe(data.address.map((a) => hashed(a.first_name, nameRule)));\n userData.ln = dedupe(data.address.map((a) => hashed(a.last_name, nameRule)));\n userData.ct = dedupe(data.address.map((a) => hashed(a.city, cityStateRule)));\n userData.st = dedupe(data.address.map((a) => hashed(a.region, cityStateRule)));\n userData.zp = dedupe(data.address.map((a) => hashed(a.postal_code, zipRule)));\n userData.country = dedupe(\n data.address.map((a) => pii(normalizeCountry(a.country), PII_DATA_TYPE.COUNTRY))\n );\n } else {\n userData.fn = list([hashed(data.address.first_name, nameRule)]);\n userData.ln = list([hashed(data.address.last_name, nameRule)]);\n userData.ct = list([hashed(data.address.city, cityStateRule)]);\n userData.st = list([hashed(data.address.region, cityStateRule)]);\n userData.zp = list([hashed(data.address.postal_code, zipRule)]);\n userData.country = list([pii(normalizeCountry(data.address.country), PII_DATA_TYPE.COUNTRY)]);\n // The 5-character prefixes, exactly as the current sender builds them: sliced first, then\n // normalized. Only the single-address branch sets them, as before.\n userData.f5first = hashed(data.address.first_name?.slice(0, 5), f5Rule);\n userData.f5last = hashed(data.address.last_name?.slice(0, 5), f5Rule);\n }\n }\n if (data.birthday) {\n const { year, month, day } = data.birthday;\n if (/^\\d{4}$/.test(year.toString())) {\n userData.doby = createHash('sha256').update(year.toString()).digest('hex');\n }\n if (month >= 1 && month <= 12) {\n userData.dobm = createHash('sha256').update(month.toString().padStart(2, '0')).digest('hex');\n }\n if (day >= 1 && day <= 31) {\n userData.dobd = createHash('sha256').update(day.toString().padStart(2, '0')).digest('hex');\n }\n }\n if (data.user_id && data.user_id.length !== 0) {\n // Unhashed, as the business SDK sends it. Meta documents hashing as recommended, not\n // required; changing it here would break continuity with every event sent so far.\n userData.external_id = [data.user_id];\n }\n if (data.ip_address) {\n userData.client_ip_address = data.ip_address;\n }\n if (data.user_agent) {\n userData.client_user_agent = data.user_agent;\n }\n if (data.fb_login_id) {\n userData.fb_login_id = data.fb_login_id;\n }\n if (data.fb_page_id) {\n userData.page_id = data.fb_page_id;\n }\n\n const fbc = metaFbc(tags, eventTimeMs);\n if (fbc) {\n userData.fbc = fbc;\n }\n const fbp = metaFbp(tags);\n if (fbp) {\n userData.fbp = fbp;\n }\n if (tags.advertising_id) {\n userData.madid = tags.advertising_id;\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.client_ip_address = tags.ip_address;\n }\n\n return userData;\n}\n\nconst DELIVERY_CATEGORIES = ['in_store', 'curbside', 'home_delivery'];\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>): Record<string, unknown> {\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n predicted_ltv,\n num_items,\n search_string,\n status,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n\n const customData: Record<string, unknown> = {};\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null && Number.isFinite(Number.parseFloat(String(value)))) {\n customData.value = Number.parseFloat(String(value));\n }\n if (currency) {\n // The business SDK also validates against the ISO 4217 list and throws; here an unknown\n // code is forwarded uppercased rather than costing the batch.\n customData.currency = currency\n .trim()\n .toUpperCase()\n .replace(/[^A-Z]/g, '');\n }\n if (content_name) customData.content_name = content_name;\n if (content_category) customData.content_category = content_category;\n if (content_ids) customData.content_ids = content_ids;\n if (contents) {\n customData.contents = contents.map((c) => {\n const content: Record<string, unknown> = {};\n if (c.id) content.id = c.id;\n if (c.quantity) content.quantity = c.quantity;\n if (c.item_price) content.item_price = c.item_price;\n if (c.title) content.title = c.title;\n if (c.description) content.description = c.description;\n if (c.brand) content.brand = c.brand;\n if (c.category) content.category = c.category;\n const delivery = c.delivery_category?.trim().toLowerCase();\n if (delivery && DELIVERY_CATEGORIES.includes(delivery)) content.delivery_category = delivery;\n return content;\n });\n }\n if (content_type) customData.content_type = content_type;\n if (predicted_ltv) customData.predicted_ltv = predicted_ltv;\n if (num_items) customData.num_items = num_items;\n if (search_string) customData.search_string = search_string;\n if (status) customData.status = status.toString();\n const delivery = delivery_category?.trim().toLowerCase();\n if (delivery && DELIVERY_CATEGORIES.includes(delivery)) customData.delivery_category = delivery;\n\n return { ...customData, ...custom_properties };\n}\n\n/**\n * `extinfo` is keyed by field index. The business SDK builds the same object and leaves unset\n * indexes out when it serializes; the documented 16-element array is what the Graph endpoint\n * decodes this into. Indexes: 0 version, 1 package, 2 short version, 3 long version, 4 OS\n * version, 5 device model, 6 locale, 9 screen width, 10 height, 11 density.\n */\nfunction getAppData(tags: TrackTags, appPackageName: string): Record<string, unknown> {\n const extinfo: Record<number, string | number> = {};\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo[0] = 'i2';\n } else if (tags.os_name === 'Android') {\n extinfo[0] = 'a2';\n }\n extinfo[1] = appPackageName;\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) extinfo[2] = shortVersion;\n if (tags.release) extinfo[3] = tags.release;\n if (tags.os_version) extinfo[4] = tags.os_version;\n if (tags.device_model_id) extinfo[5] = tags.device_model_id;\n if (tags.language) extinfo[6] = tags.language;\n if (tags.screen_width) extinfo[9] = tags.screen_width;\n if (tags.screen_height) extinfo[10] = tags.screen_height;\n if (tags.device_pixel_ratio) extinfo[11] = tags.device_pixel_ratio.toString();\n\n const appData: Record<string, unknown> = { extinfo };\n if (tags.advertising_id) appData.advertiser_tracking_enabled = true;\n if (tags.install_referrer) appData.install_referrer = tags.install_referrer;\n return appData;\n}\n\n/** One entry of the `data` array POSTed to `/{pixel_id}/events`. */\nexport interface CapiEvent {\n event_name: string;\n event_time: number;\n event_id: string;\n event_source_url?: string;\n action_source: 'app' | 'website' | 'other';\n user_data: CapiUserData;\n custom_data: Record<string, unknown>;\n app_data?: Record<string, unknown>;\n}\n\n/**\n * `action_source` is derived from `event.platform` alone: an OS platform is an app, `web` is a\n * website, and everything else — including a backend-built offline conversion, which should\n * declare `platform: 'unknown'` — lands in Meta's `other`, exactly where the business-SDK\n * sender put an explicit `'offline'` too.\n */\nexport function getCapiEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string\n): CapiEvent {\n const eventTimeMs = new Date(event.created_at).getTime();\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const source = resolveActionSource(event.platform);\n\n const capiEvent: CapiEvent = {\n event_name: eventName,\n event_time: Math.round(eventTimeMs / 1000),\n event_id: event.tags.idempotency_key ?? event.id,\n // `action_source` is required; an offline conversion and an undeterminable platform both\n // land in Meta's catch-all, exactly as in the business-SDK sender.\n action_source: source === 'app' ? 'app' : source === 'web' ? 'website' : 'other',\n user_data: getUserData(event.tags, data, eventTimeMs),\n custom_data: getCustomData(event),\n };\n if (source === 'app' && appPackageName) {\n capiEvent.app_data = getAppData(event.tags, appPackageName);\n }\n const eventSourceUrl = pageLocation(event.tags);\n if (eventSourceUrl) {\n capiEvent.event_source_url = eventSourceUrl;\n }\n return capiEvent;\n}\n\nexport interface MetaConversionsOptions {\n /** Attaches `app_data`/extinfo to events whose platform resolves to an app. */\n appPackageName?: string;\n /** Routes the batch to Events Manager's Test Events tab instead of production. */\n testEventCode?: string;\n /** Graph API version, e.g. 'v24.0'. */\n apiVersion?: string;\n}\n\nexport interface MetaConversionsResponse {\n events_received?: number;\n messages?: string[];\n fbtrace_id?: string;\n}\n\n/**\n * An `event_time` over 7 days old fails the whole request — \"we return an error for the entire\n * request and process no events\" — so such an event is left out; a minute's margin for the trip.\n * https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/server-event\n */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\n/**\n * The lightweight counterpart to the legacy SDK sender (`@shware/analytics/server/legacy`):\n * same filtering, same payload, same never-throws contract, minus the 31MB SDK. The token\n * travels in the JSON body, never in the URL, and is never logged.\n */\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n options: MetaConversionsOptions = {}\n): Promise<MetaConversionsResponse | undefined> {\n const capiEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getCapiEvent(event, data, options.appPackageName));\n if (capiEvents.length === 0) return undefined;\n\n const version = options.apiVersion ?? API_VERSION;\n try {\n const response = await fetch(`https://graph.facebook.com/${version}/${pixelId}/events`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n data: capiEvents,\n test_event_code: options.testEventCode,\n access_token: accessToken,\n }),\n });\n if (response.ok) return (await response.json()) as MetaConversionsResponse;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Meta conversion, status: ${status}, body: ${message}`);\n return undefined;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Failed to send Meta conversion, network error: ${message}`);\n return undefined;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"meta-capi.mjs","names":[],"sources":["../../src/server/meta-capi.ts"],"sourcesContent":["/**\n * Meta Conversions API sender built on `capi-param-builder-nodejs` and plain `fetch`, replacing\n * the 31MB `facebook-nodejs-business-sdk` with ~0.5MB of parameter building. The SDK-based\n * sender survives untouched at `@shware/analytics/server/legacy`; this module is its drop-in\n * successor.\n *\n * The wire payload is byte-identical to what the business SDK's `ServerEvent.normalize()`\n * produces — enforced by a differential test that runs both builders over the same events.\n * Where the two libraries normalize differently (the business SDK keeps punctuation in names,\n * spaces in multi-word last names, accents in cities; the param builder strips them per the\n * current documented rules), this module follows the business SDK so that switching senders\n * cannot change a single hash Meta receives. The param builder is used where the two provably\n * agree — email, phone, gender, country — and its adoption-tracking appendix (`<hash>.<token>`)\n * is stripped for the same reason.\n *\n * Two deliberate behavior differences from the business SDK, both on invalid input only:\n * an invalid field (malformed email, non-ISO country, letters in a phone) is dropped from\n * user_data instead of throwing inside `execute()` and costing the whole batch; an invalid\n * currency is uppercased and forwarded instead of rejected. Valid input is unaffected.\n *\n * https://developers.facebook.com/docs/marketing-api/conversions-api/using-the-api\n * https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nimport { createHash } from 'node:crypto';\nimport { fetch } from '@shware/utils';\nimport { PII_DATA_TYPE, ParamBuilder } from 'capi-param-builder-nodejs';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\nimport { resolveActionSource } from './action-source';\nimport { metaFbc, metaFbp } from './click-ids';\nimport { pageLocation } from './page-location';\n\n/** Matches the Graph API version the pinned business SDK speaks (`FacebookAdsApi.VERSION`). */\nconst API_VERSION = 'v24.0';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\n/**\n * Lazy for the same reason as the session and the token bucket (see setup/session.ts):\n * Cloudflare Workers refuse some work in module scope, and nothing here is per-request state.\n */\nlet paramBuilder: ParamBuilder | undefined;\n\n/**\n * Normalize and hash one PII value through the param builder, stripped to the bare SHA-256 the\n * business SDK sends: the builder appends a `.<language-token>` adoption marker that Meta's\n * endpoint tolerates but the business SDK never produced. Pre-hashed input passes through, as\n * it does in the business SDK.\n */\nfunction pii(value: string | undefined, dataType: string): string | undefined {\n if (!value) return undefined;\n const built = (paramBuilder ??= new ParamBuilder()).getNormalizedAndHashedPII(value, dataType);\n return built?.split('.').at(0) ?? undefined;\n}\n\nconst SHA256_OR_MD5 = /^[a-f0-9]{64}$|^[a-f0-9]{32}$/;\n\n/**\n * The business SDK's `normalizeAndHash` for the fields where the param builder normalizes\n * differently (names, city, state, zip): trim + lowercase, pass a pre-hashed value through,\n * apply the field rule, SHA-256. Field rules are copied from `ServerSideUtils` verbatim so the\n * differential test can hold the outputs equal.\n */\nfunction hashed(\n input: string | undefined,\n rule: (lowered: string) => string | undefined\n): string | undefined {\n if (!input) return undefined;\n const lowered = input.trim().toLowerCase();\n if (!lowered) return undefined;\n if (SHA256_OR_MD5.test(lowered)) return lowered;\n const normalized = rule(lowered);\n if (normalized === undefined) return undefined;\n return createHash('sha256').update(normalized).digest('hex');\n}\n\nconst nameRule = (v: string) => v;\nconst f5Rule = (v: string) => (v.length <= 5 ? v : v.slice(0, 5));\nconst cityStateRule = (v: string) => v.replace(/[0-9\\s().-]/g, '');\nconst zipRule = (v: string) => {\n const zip = v.replace(/\\s/g, '').split('-', 1)[0];\n return zip.length < 2 ? undefined : zip;\n};\n\n/**\n * Every multi-value field is deduplicated after hashing, order preserved — the SDK runs each\n * through `dedupArray` (a Set), `external_id` included.\n */\nconst dedupe = (hashes: (string | undefined)[]) =>\n Array.from(new Set(hashes.filter((hash): hash is string => hash !== undefined)));\n\nconst list = (hashes: (string | undefined)[]) => {\n const deduped = dedupe(hashes);\n return deduped.length > 0 ? deduped : undefined;\n};\n\ninterface CapiUserData {\n em?: string[];\n ph?: string[];\n ge?: string[];\n fn?: string[];\n ln?: string[];\n ct?: string[];\n st?: string[];\n zp?: string[];\n country?: string[];\n external_id?: string[];\n client_ip_address?: string;\n client_user_agent?: string;\n fbc?: string;\n fbp?: string;\n fb_login_id?: string;\n page_id?: string;\n f5first?: string;\n f5last?: string;\n dobd?: string;\n dobm?: string;\n doby?: string;\n madid?: string;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData, eventTimeMs: number): CapiUserData {\n const userData: CapiUserData = {};\n\n if (data.email) {\n const emails = Array.isArray(data.email) ? data.email : [data.email];\n userData.em = list(emails.map((email) => pii(email, PII_DATA_TYPE.EMAIL)));\n }\n if (data.phone_number) {\n const phones = Array.isArray(data.phone_number) ? data.phone_number : [data.phone_number];\n userData.ph = list(phones.map((phone) => pii(phone, PII_DATA_TYPE.PHONE)));\n }\n if (data.gender === 'female' || data.gender === 'male') {\n userData.ge = list([pii(data.gender === 'female' ? 'f' : 'm', PII_DATA_TYPE.GENDER)]);\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n // The SDK sets these lists unconditionally in the multi-address branch, so an all-empty\n // column still goes out as `[]` — kept for byte-equality with the current sender.\n userData.fn = dedupe(data.address.map((a) => hashed(a.first_name, nameRule)));\n userData.ln = dedupe(data.address.map((a) => hashed(a.last_name, nameRule)));\n userData.ct = dedupe(data.address.map((a) => hashed(a.city, cityStateRule)));\n userData.st = dedupe(data.address.map((a) => hashed(a.region, cityStateRule)));\n userData.zp = dedupe(data.address.map((a) => hashed(a.postal_code, zipRule)));\n userData.country = dedupe(\n data.address.map((a) => pii(normalizeCountry(a.country), PII_DATA_TYPE.COUNTRY))\n );\n } else {\n userData.fn = list([hashed(data.address.first_name, nameRule)]);\n userData.ln = list([hashed(data.address.last_name, nameRule)]);\n userData.ct = list([hashed(data.address.city, cityStateRule)]);\n userData.st = list([hashed(data.address.region, cityStateRule)]);\n userData.zp = list([hashed(data.address.postal_code, zipRule)]);\n userData.country = list([pii(normalizeCountry(data.address.country), PII_DATA_TYPE.COUNTRY)]);\n // The 5-character prefixes, exactly as the current sender builds them: sliced first, then\n // normalized. Only the single-address branch sets them, as before.\n userData.f5first = hashed(data.address.first_name?.slice(0, 5), f5Rule);\n userData.f5last = hashed(data.address.last_name?.slice(0, 5), f5Rule);\n }\n }\n if (data.birthday) {\n const { year, month, day } = data.birthday;\n if (/^\\d{4}$/.test(year.toString())) {\n userData.doby = createHash('sha256').update(year.toString()).digest('hex');\n }\n if (month >= 1 && month <= 12) {\n userData.dobm = createHash('sha256').update(month.toString().padStart(2, '0')).digest('hex');\n }\n if (day >= 1 && day <= 31) {\n userData.dobd = createHash('sha256').update(day.toString().padStart(2, '0')).digest('hex');\n }\n }\n if (data.user_id && data.user_id.length !== 0) {\n // Unhashed, as the business SDK sends it. Meta documents hashing as recommended, not\n // required; changing it here would break continuity with every event sent so far.\n userData.external_id = [data.user_id];\n }\n if (data.ip_address) {\n userData.client_ip_address = data.ip_address;\n }\n if (data.user_agent) {\n userData.client_user_agent = data.user_agent;\n }\n if (data.fb_login_id) {\n userData.fb_login_id = data.fb_login_id;\n }\n if (data.fb_page_id) {\n userData.page_id = data.fb_page_id;\n }\n\n const fbc = metaFbc(tags, eventTimeMs);\n if (fbc) {\n userData.fbc = fbc;\n }\n const fbp = metaFbp(tags);\n if (fbp) {\n userData.fbp = fbp;\n }\n if (tags.advertising_id) {\n userData.madid = tags.advertising_id;\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.client_ip_address = tags.ip_address;\n }\n\n return userData;\n}\n\nconst DELIVERY_CATEGORIES = ['in_store', 'curbside', 'home_delivery'];\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>): Record<string, unknown> {\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n predicted_ltv,\n num_items,\n search_string,\n status,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n\n const customData: Record<string, unknown> = {};\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null && Number.isFinite(Number.parseFloat(String(value)))) {\n customData.value = Number.parseFloat(String(value));\n }\n if (currency) {\n // The business SDK also validates against the ISO 4217 list and throws; here an unknown\n // code is forwarded uppercased rather than costing the batch.\n customData.currency = currency\n .trim()\n .toUpperCase()\n .replace(/[^A-Z]/g, '');\n }\n if (content_name) customData.content_name = content_name;\n if (content_category) customData.content_category = content_category;\n if (content_ids) customData.content_ids = content_ids;\n if (contents) {\n customData.contents = contents.map((c) => {\n const content: Record<string, unknown> = {};\n if (c.id) content.id = c.id;\n if (c.quantity) content.quantity = c.quantity;\n if (c.item_price) content.item_price = c.item_price;\n if (c.title) content.title = c.title;\n if (c.description) content.description = c.description;\n if (c.brand) content.brand = c.brand;\n if (c.category) content.category = c.category;\n const delivery = c.delivery_category?.trim().toLowerCase();\n if (delivery && DELIVERY_CATEGORIES.includes(delivery)) content.delivery_category = delivery;\n return content;\n });\n }\n if (content_type) customData.content_type = content_type;\n if (predicted_ltv) customData.predicted_ltv = predicted_ltv;\n if (num_items) customData.num_items = num_items;\n if (search_string) customData.search_string = search_string;\n if (status) customData.status = status.toString();\n const delivery = delivery_category?.trim().toLowerCase();\n if (delivery && DELIVERY_CATEGORIES.includes(delivery)) customData.delivery_category = delivery;\n\n return { ...customData, ...custom_properties };\n}\n\n/**\n * `extinfo` is keyed by field index. The business SDK builds the same object and leaves unset\n * indexes out when it serializes; the documented 16-element array is what the Graph endpoint\n * decodes this into. Indexes: 0 version, 1 package, 2 short version, 3 long version, 4 OS\n * version, 5 device model, 6 locale, 9 screen width, 10 height, 11 density.\n */\n/**\n * The `extinfo` version of an app event's OS — `i2` for iOS, `a2` for Android, the only two Meta\n * documents. Undefined for any other OS (a desktop app): such an event cannot carry the\n * `app_data` an app event requires, and is sent as `other`.\n * https://developers.facebook.com/docs/marketing-api/conversions-api/app-events\n */\nexport function metaExtinfoVersion(tags: TrackTags): 'i2' | 'a2' | undefined {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') return 'i2';\n if (tags.os_name === 'Android') return 'a2';\n return undefined;\n}\n\nfunction getAppData(\n tags: TrackTags,\n appPackageName: string,\n version: 'i2' | 'a2'\n): Record<string, unknown> {\n const extinfo: Record<number, string | number> = {};\n extinfo[0] = version;\n extinfo[1] = appPackageName;\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) extinfo[2] = shortVersion;\n if (tags.release) extinfo[3] = tags.release;\n if (tags.os_version) extinfo[4] = tags.os_version;\n if (tags.device_model_id) extinfo[5] = tags.device_model_id;\n if (tags.language) extinfo[6] = tags.language;\n if (tags.screen_width) extinfo[9] = tags.screen_width;\n if (tags.screen_height) extinfo[10] = tags.screen_height;\n if (tags.device_pixel_ratio) extinfo[11] = tags.device_pixel_ratio.toString();\n\n // Required on every app event. An advertising id is only readable once the user allowed\n // tracking (ATT on iOS, the ad id setting on Android), so its presence is the answer.\n const appData: Record<string, unknown> = {\n extinfo,\n advertiser_tracking_enabled: !!tags.advertising_id,\n };\n if (tags.install_referrer) appData.install_referrer = tags.install_referrer;\n return appData;\n}\n\n/** One entry of the `data` array POSTed to `/{pixel_id}/events`. */\nexport interface CapiEvent {\n event_name: string;\n event_time: number;\n event_id: string;\n event_source_url?: string;\n action_source: 'app' | 'website' | 'other';\n user_data: CapiUserData;\n custom_data: Record<string, unknown>;\n app_data?: Record<string, unknown>;\n}\n\n/**\n * `action_source` is derived from `event.platform` alone: an OS platform is an app, `web` is a\n * website, and everything else — including a backend-built offline conversion, which should\n * declare `platform: 'unknown'` — lands in Meta's `other`, exactly where the business-SDK\n * sender put an explicit `'offline'` too.\n */\nexport function getCapiEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string\n): CapiEvent {\n const eventTimeMs = new Date(event.created_at).getTime();\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const source = resolveActionSource(event.platform);\n const version = metaExtinfoVersion(event.tags);\n const app =\n source === 'app' && appPackageName && version\n ? { packageName: appPackageName, version }\n : undefined;\n\n const capiEvent: CapiEvent = {\n event_name: eventName,\n event_time: Math.round(eventTimeMs / 1000),\n event_id: event.tags.idempotency_key ?? event.id,\n // `action_source` is required; an offline conversion and an undeterminable platform both\n // land in Meta's catch-all, exactly as in the business-SDK sender. So does an app event that\n // cannot carry the `app_data` Meta requires of one: no package name, or a desktop OS.\n action_source: app ? 'app' : source === 'web' ? 'website' : 'other',\n user_data: getUserData(event.tags, data, eventTimeMs),\n custom_data: getCustomData(event),\n };\n if (app) {\n capiEvent.app_data = getAppData(event.tags, app.packageName, app.version);\n }\n const eventSourceUrl = pageLocation(event.tags);\n if (eventSourceUrl) {\n capiEvent.event_source_url = eventSourceUrl;\n }\n return capiEvent;\n}\n\nexport interface MetaConversionsOptions {\n /** Attaches `app_data`/extinfo to events whose platform resolves to an app. */\n appPackageName?: string;\n /** Routes the batch to Events Manager's Test Events tab instead of production. */\n testEventCode?: string;\n /** Graph API version, e.g. 'v24.0'. */\n apiVersion?: string;\n}\n\nexport interface MetaConversionsResponse {\n events_received?: number;\n messages?: string[];\n fbtrace_id?: string;\n}\n\n/**\n * An `event_time` over 7 days old fails the whole request — \"we return an error for the entire\n * request and process no events\" — so such an event is left out; a minute's margin for the trip.\n * https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/server-event\n */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\n/**\n * The lightweight counterpart to the legacy SDK sender (`@shware/analytics/server/legacy`):\n * same filtering, same payload, same never-throws contract, minus the 31MB SDK. The token\n * travels in the JSON body, never in the URL, and is never logged.\n */\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n options: MetaConversionsOptions = {}\n): Promise<MetaConversionsResponse | undefined> {\n const capiEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getCapiEvent(event, data, options.appPackageName));\n if (capiEvents.length === 0) return undefined;\n // \"The client_user_agent is required for website events\" — sent from `data.user_agent`.\n if (!data.user_agent && capiEvents.some((event) => event.action_source === 'website')) {\n console.warn(\n 'Meta conversion: website events sent without client_user_agent (data.user_agent)'\n );\n }\n\n const version = options.apiVersion ?? API_VERSION;\n try {\n const response = await fetch(`https://graph.facebook.com/${version}/${pixelId}/events`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', Accept: 'application/json' },\n body: JSON.stringify({\n data: capiEvents,\n test_event_code: options.testEventCode,\n access_token: accessToken,\n }),\n });\n if (response.ok) return (await response.json()) as MetaConversionsResponse;\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Meta conversion, status: ${status}, body: ${message}`);\n return undefined;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Failed to send Meta conversion, network error: ${message}`);\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -4,6 +4,7 @@ const require_track_fbq = require("../track/fbq.cjs");
4
4
  const require_server_action_source = require("./action-source.cjs");
5
5
  const require_server_click_ids = require("./click-ids.cjs");
6
6
  const require_server_page_location = require("./page-location.cjs");
7
+ const require_server_meta_capi = require("./meta-capi.cjs");
7
8
  let facebook_nodejs_business_sdk = require("facebook-nodejs-business-sdk");
8
9
  //#region src/server/meta-conversions-api.ts
9
10
  const USER_ASSIGNED_COUNTRIES = ["xk"];
@@ -76,12 +77,9 @@ function getUserData(tags, data, eventTimeMs) {
76
77
  if (tags.ip_address && typeof tags.ip_address === "string") userData.setClientIpAddress(tags.ip_address);
77
78
  return userData;
78
79
  }
79
- function getAppData(tags, appPackageName) {
80
+ function getAppData(tags, appPackageName, version) {
80
81
  const extinfo = new facebook_nodejs_business_sdk.ExtendedDeviceInfo();
81
- if (tags.os_name) {
82
- if (tags.os_name === "iOS" || tags.os_name === "iPadOS") extinfo.setExtInfoVersion("i2");
83
- else if (tags.os_name === "Android") extinfo.setExtInfoVersion("a2");
84
- }
82
+ extinfo.setExtInfoVersion(version);
85
83
  extinfo.setAppPackageName(appPackageName);
86
84
  const shortVersion = tags.release?.split(".").at(0);
87
85
  if (shortVersion) extinfo.setShortVersion(shortVersion);
@@ -94,8 +92,7 @@ function getAppData(tags, appPackageName) {
94
92
  if (tags.device_pixel_ratio) extinfo.setScreenDensity(tags.device_pixel_ratio.toString());
95
93
  const appData = new facebook_nodejs_business_sdk.AppData();
96
94
  appData.setExtinfo(extinfo);
97
- if (tags.install_referrer) appData.setInstallReferrer(tags.install_referrer);
98
- if (tags.advertising_id) appData.setAdvertiserTrackingEnabled(true);
95
+ appData.setAdvertiserTrackingEnabled(!!tags.advertising_id);
99
96
  if (tags.install_referrer) appData.setInstallReferrer(tags.install_referrer);
100
97
  return appData;
101
98
  }
@@ -134,13 +131,12 @@ function getServerEvent(event, data, appPackageName, actionSource) {
134
131
  const [_, eventName] = require_track_fbq.mapFBEvent(event.name, event.properties);
135
132
  const serverEvent = new facebook_nodejs_business_sdk.ServerEvent().setEventId(event.tags.idempotency_key ?? event.id).setEventName(eventName).setEventTime(Math.round(eventTimeMs / 1e3)).setUserData(userData).setCustomData(customData);
136
133
  const source = require_server_action_source.resolveActionSource(event.platform, actionSource);
137
- if (source === "app" && appPackageName) {
138
- const appData = getAppData(event.tags, appPackageName);
139
- serverEvent.setAppData(appData);
140
- }
134
+ const version = require_server_meta_capi.metaExtinfoVersion(event.tags);
135
+ const app = source === "app" && appPackageName && version ? version : void 0;
136
+ if (app && appPackageName) serverEvent.setAppData(getAppData(event.tags, appPackageName, app));
141
137
  const eventSourceUrl = require_server_page_location.pageLocation(event.tags);
142
138
  if (eventSourceUrl) serverEvent.setEventSourceUrl(eventSourceUrl);
143
- switch (source) {
139
+ switch (app ? "app" : source === "app" ? void 0 : source) {
144
140
  case "app":
145
141
  serverEvent.setActionSource("app");
146
142
  break;
@@ -181,6 +177,7 @@ const MAX_EVENT_AGE_MS = 60474e4;
181
177
  async function sendEvents(accessToken, pixelId, events, data = {}, appPackageName, actionSource) {
182
178
  const fbEvents = events.filter((event) => !require_third_parties_ignored_events.IGNORED_EVENTS.includes(event.name)).filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS).map((event) => getServerEvent(event, data, appPackageName, actionSource));
183
179
  if (fbEvents.length === 0) return void 0;
180
+ if (!data.user_agent && fbEvents.some((event) => event.action_source === "website")) console.warn("Meta conversion: website events sent without client_user_agent (data.user_agent)");
184
181
  const request = new facebook_nodejs_business_sdk.EventRequest(accessToken, pixelId);
185
182
  request.setEvents(fbEvents);
186
183
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.cjs","names":["UserData","metaFbc","metaFbp","ExtendedDeviceInfo","AppData","CustomData","mapFBEvent","Content","ServerEvent","resolveActionSource","pageLocation","IGNORED_EVENTS","EventRequest"],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\nimport { type EventActionSource, resolveActionSource } from './action-source';\nimport { metaFbc, metaFbp } from './click-ids';\nimport { pageLocation } from './page-location';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData, eventTimeMs: number) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n const fbc = metaFbc(tags, eventTimeMs);\n if (fbc) {\n userData.setFbc(fbc);\n }\n const fbp = metaFbp(tags);\n if (fbp) {\n userData.setFbp(fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string) {\n const extinfo = new ExtendedDeviceInfo();\n if (tags.os_name) {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo.setExtInfoVersion('i2');\n } else if (tags.os_name === 'Android') {\n extinfo.setExtInfoVersion('a2');\n }\n }\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n if (tags.advertising_id) {\n appData.setAdvertiserTrackingEnabled(true);\n }\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const eventTimeMs = new Date(event.created_at).getTime();\n const userData = getUserData(event.tags, data, eventTimeMs);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(eventTimeMs / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n const source = resolveActionSource(event.platform, actionSource);\n if (source === 'app' && appPackageName) {\n const appData = getAppData(event.tags, appPackageName);\n serverEvent.setAppData(appData);\n }\n const eventSourceUrl = pageLocation(event.tags);\n if (eventSourceUrl) {\n serverEvent.setEventSourceUrl(eventSourceUrl);\n }\n switch (source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n // `action_source` is required on a server event, so both an offline conversion and an\n // undeterminable platform still have to send something. Meta has no generic offline\n // value: 'physical_store' and 'system_generated' are narrower claims only the caller can\n // make, so everything left lands in Meta's own catch-all.\n serverEvent.setActionSource('other');\n break;\n }\n return serverEvent;\n}\n\n/**\n * The shape `FacebookRequestError` exposes: `status` and `response` are the HTTP status and the\n * parsed error body, and both are null when the request never received a response.\n */\ntype MetaRequestError = { status?: number | null; message?: string; response?: unknown };\n\n/**\n * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.\n * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.\n * Never log the error itself: it also carries the access token, in both `url` and `data`.\n */\nfunction logError(error: unknown) {\n const { status, message, response } = (error ?? {}) as MetaRequestError;\n if (typeof status !== 'number') {\n console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);\n return;\n }\n console.error(\n `Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`\n );\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName, actionSource);\n request.setEvents([fbEvent]);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\n/** An `event_time` over 7 days old fails the whole request; see `meta-capi.ts`. */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getServerEvent(event, data, appPackageName, actionSource));\n if (fbEvents.length === 0) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"meta-conversions-api.cjs","names":["UserData","metaFbc","metaFbp","ExtendedDeviceInfo","AppData","CustomData","mapFBEvent","Content","ServerEvent","resolveActionSource","metaExtinfoVersion","pageLocation","IGNORED_EVENTS","EventRequest"],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\nimport { type EventActionSource, resolveActionSource } from './action-source';\nimport { metaFbc, metaFbp } from './click-ids';\nimport { metaExtinfoVersion } from './meta-capi';\nimport { pageLocation } from './page-location';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData, eventTimeMs: number) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n const fbc = metaFbc(tags, eventTimeMs);\n if (fbc) {\n userData.setFbc(fbc);\n }\n const fbp = metaFbp(tags);\n if (fbp) {\n userData.setFbp(fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string, version: 'i2' | 'a2') {\n const extinfo = new ExtendedDeviceInfo();\n extinfo.setExtInfoVersion(version);\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n // Required on every app event; see `meta-capi.ts`.\n appData.setAdvertiserTrackingEnabled(!!tags.advertising_id);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const eventTimeMs = new Date(event.created_at).getTime();\n const userData = getUserData(event.tags, data, eventTimeMs);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(eventTimeMs / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n const source = resolveActionSource(event.platform, actionSource);\n // An app event that cannot carry the required `app_data` — no package name, or a desktop OS —\n // is sent as `other`, as in `meta-capi.ts`.\n const version = metaExtinfoVersion(event.tags);\n const app = source === 'app' && appPackageName && version ? version : undefined;\n if (app && appPackageName) {\n serverEvent.setAppData(getAppData(event.tags, appPackageName, app));\n }\n const eventSourceUrl = pageLocation(event.tags);\n if (eventSourceUrl) {\n serverEvent.setEventSourceUrl(eventSourceUrl);\n }\n switch (app ? 'app' : source === 'app' ? undefined : source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n // `action_source` is required on a server event, so both an offline conversion and an\n // undeterminable platform still have to send something. Meta has no generic offline\n // value: 'physical_store' and 'system_generated' are narrower claims only the caller can\n // make, so everything left lands in Meta's own catch-all.\n serverEvent.setActionSource('other');\n break;\n }\n return serverEvent;\n}\n\n/**\n * The shape `FacebookRequestError` exposes: `status` and `response` are the HTTP status and the\n * parsed error body, and both are null when the request never received a response.\n */\ntype MetaRequestError = { status?: number | null; message?: string; response?: unknown };\n\n/**\n * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.\n * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.\n * Never log the error itself: it also carries the access token, in both `url` and `data`.\n */\nfunction logError(error: unknown) {\n const { status, message, response } = (error ?? {}) as MetaRequestError;\n if (typeof status !== 'number') {\n console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);\n return;\n }\n console.error(\n `Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`\n );\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName, actionSource);\n request.setEvents([fbEvent]);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\n/** An `event_time` over 7 days old fails the whole request; see `meta-capi.ts`. */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getServerEvent(event, data, appPackageName, actionSource));\n if (fbEvents.length === 0) return undefined;\n // \"The client_user_agent is required for website events\"; see `meta-capi.ts`.\n if (!data.user_agent && fbEvents.some((event) => event.action_source === 'website')) {\n console.warn(\n 'Meta conversion: website events sent without client_user_agent (data.user_agent)'\n );\n }\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":";;;;;;;;;AAiBA,MAAM,0BAAoC,CAAC,IAAI;AAC/C,SAAS,iBAAiB,OAA+C;CACvE,MAAM,UAAU,OAAO,MAAM,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,YAAY;CACxD,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,OAAO,wBAAwB,SAAS,OAAO,IAAI,KAAA,IAAY;AACjE;AAEA,SAAS,YAAY,MAAiB,MAAwB,aAAqB;CACjF,MAAM,WAAW,IAAIA,6BAAAA,SAAS;CAG9B,IAAI,KAAK,OAAO;EACd,IAAI,MAAM,QAAQ,KAAK,KAAK,GAC1B,SAAS,UAAU,KAAK,KAAK;OAE7B,SAAS,SAAS,KAAK,KAAK;CAEhC;CACA,IAAI,KAAK,cAAc;EACrB,IAAI,MAAM,QAAQ,KAAK,YAAY,GACjC,SAAS,UAAU,KAAK,YAAY;OAEpC,SAAS,SAAS,KAAK,YAAY;CAEvC;CACA,IAAI,KAAK,QAAQ;EACf,IAAI,KAAK,WAAW,UAClB,SAAS,UAAU,GAAG;OAEjB,IAAI,KAAK,WAAW,QACzB,SAAS,UAAU,GAAG;CAE1B;CACA,IAAI,KAAK,SAAS;EAChB,IAAI,MAAM,QAAQ,KAAK,OAAO,GAAG;GAC/B,MAAM,aAAa,KAAK,QAAQ,KAAK,MAAM,EAAE,UAAU,CAAC,CAAC,OAAO,OAAO;GACvE,MAAM,YAAY,KAAK,QAAQ,KAAK,MAAM,EAAE,SAAS,CAAC,CAAC,OAAO,OAAO;GACrE,MAAM,SAAS,KAAK,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,OAAO,OAAO;GAC7D,MAAM,SAAS,KAAK,QAAQ,KAAK,MAAM,EAAE,MAAM,CAAC,CAAC,OAAO,OAAO;GAC/D,MAAM,cAAc,KAAK,QAAQ,KAAK,MAAM,EAAE,WAAW,CAAC,CAAC,OAAO,OAAO;GACzE,MAAM,YAAY,KAAK,QAAQ,KAAK,MAAM,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,OAAO;GAErF,SAAS,cAAc,UAAsB;GAC7C,SAAS,aAAa,SAAqB;GAC3C,SAAS,UAAU,MAAkB;GACrC,SAAS,UAAU,MAAkB;GACrC,SAAS,QAAQ,WAAuB;GACxC,SAAS,aAAa,SAAqB;EAC7C,OAAO;GACL,IAAI,KAAK,QAAQ,YAAY;IAC3B,SAAS,aAAa,KAAK,QAAQ,UAAU;IAC7C,SAAS,WAAW,KAAK,QAAQ,WAAW,MAAM,GAAG,CAAC,CAAC;GACzD;GACA,IAAI,KAAK,QAAQ,WAAW;IAC1B,SAAS,YAAY,KAAK,QAAQ,SAAS;IAC3C,SAAS,UAAU,KAAK,QAAQ,UAAU,MAAM,GAAG,CAAC,CAAC;GACvD;GACA,IAAI,KAAK,QAAQ,MAAM,SAAS,QAAQ,KAAK,QAAQ,IAAI;GACzD,IAAI,KAAK,QAAQ,QAAQ,SAAS,SAAS,KAAK,QAAQ,MAAM;GAC9D,IAAI,KAAK,QAAQ,aAAa,SAAS,OAAO,KAAK,QAAQ,WAAW;GACtE,IAAI,KAAK,QAAQ,SAAS;IACxB,MAAM,UAAU,iBAAiB,KAAK,QAAQ,OAAO;IACrD,IAAI,SAAS,SAAS,WAAW,OAAO;GAC1C;EACF;CACF;CACA,IAAI,KAAK,UAAU;EACjB,SAAS,QAAQ,KAAK,SAAS,KAAK,SAAS,CAAC;EAC9C,SAAS,QAAQ,KAAK,SAAS,MAAM,SAAS,CAAC;EAC/C,SAAS,QAAQ,KAAK,SAAS,IAAI,SAAS,CAAC;CAC/C;CACA,IAAI,KAAK,WAAW,KAAK,QAAQ,WAAW,GAC1C,SAAS,cAAc,KAAK,OAAO;CAErC,IAAI,KAAK,YACP,SAAS,mBAAmB,KAAK,UAAU;CAE7C,IAAI,KAAK,YACP,SAAS,mBAAmB,KAAK,UAAU;CAE7C,IAAI,KAAK,aACP,SAAS,aAAa,KAAK,WAAW;CAExC,IAAI,KAAK,YACP,SAAS,UAAU,KAAK,UAAU;CAIpC,MAAM,MAAMC,yBAAAA,QAAQ,MAAM,WAAW;CACrC,IAAI,KACF,SAAS,OAAO,GAAG;CAErB,MAAM,MAAMC,yBAAAA,QAAQ,IAAI;CACxB,IAAI,KACF,SAAS,OAAO,GAAG;CAErB,IAAI,KAAK,gBACP,SAAS,SAAS,KAAK,cAAc;CAEvC,IAAI,KAAK,cAAc,OAAO,KAAK,eAAe,UAChD,SAAS,mBAAmB,KAAK,UAAU;CAG7C,OAAO;AACT;AAEA,SAAS,WAAW,MAAiB,gBAAwB,SAAsB;CACjF,MAAM,UAAU,IAAIC,6BAAAA,mBAAmB;CACvC,QAAQ,kBAAkB,OAAO;CACjC,QAAQ,kBAAkB,cAAc;CACxC,MAAM,eAAe,KAAK,SAAS,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC;CAClD,IAAI,cACF,QAAQ,gBAAgB,YAAY;CAEtC,IAAI,KAAK,SACP,QAAQ,eAAe,KAAK,OAAO;CAErC,IAAI,KAAK,YACP,QAAQ,aAAa,KAAK,UAAU;CAEtC,IAAI,KAAK,iBACP,QAAQ,mBAAmB,KAAK,eAAe;CAEjD,IAAI,KAAK,UACP,QAAQ,UAAU,KAAK,QAAQ;CAEjC,IAAI,KAAK,cACP,QAAQ,eAAe,KAAK,YAAY;CAE1C,IAAI,KAAK,eACP,QAAQ,gBAAgB,KAAK,aAAa;CAE5C,IAAI,KAAK,oBACP,QAAQ,iBAAiB,KAAK,mBAAmB,SAAS,CAAC;CAG7D,MAAM,UAAU,IAAIC,6BAAAA,QAAQ;CAC5B,QAAQ,WAAW,OAAO;CAE1B,QAAQ,6BAA6B,CAAC,CAAC,KAAK,cAAc;CAC1D,IAAI,KAAK,kBACP,QAAQ,mBAAmB,KAAK,gBAAgB;CAGlD,OAAO;AACT;AAGA,SAAS,cAAc,EAAE,MAAM,cAA+B;CAC5D,MAAM,OAAO,IAAIC,6BAAAA,WAAW;CAC5B,MAAM,CAAC,GAAG,OAAO,qBAAqBC,kBAAAA,WAAW,MAAM,UAAU;CACjE,MAAM,EACJ,OACA,UACA,cACA,kBACA,aACA,UACA,cAEA,eACA,WACA,eACA,QAEA,mBACA,GAAG,sBACD;CAEJ,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,KAAK,SAAS,KAAK;CAC9D,IAAI,UAAU,KAAK,YAAY,QAAQ;CACvC,IAAI,cAAc,KAAK,eAAe,YAAY;CAClD,IAAI,kBAAkB,KAAK,mBAAmB,gBAAgB;CAC9D,IAAI,aAAa,KAAK,cAAc,WAAW;CAC/C,IAAI,UACF,KAAK,YACH,SAAS,KAAK,MAAM;EAClB,MAAM,SAAS,IAAIC,6BAAAA,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,YAAY,EAAE,QAAQ;EAC/D,IAAI,EAAE,YAAY,OAAO,aAAa,EAAE,UAAU;EAClD,IAAI,EAAE,OAAO,OAAO,SAAS,EAAE,KAAK;EACpC,IAAI,EAAE,aAAa,OAAO,eAAe,EAAE,WAAW;EACtD,IAAI,EAAE,OAAO,OAAO,SAAS,EAAE,KAAK;EACpC,IAAI,EAAE,UAAU,OAAO,YAAY,EAAE,QAAQ;EAC7C,IAAI,EAAE,mBAAmB,OAAO,oBAAoB,EAAE,iBAAiB;EACvE,OAAO;CACT,CAAC,CACH;CACF,IAAI,cAAc,KAAK,eAAe,YAAY;CAElD,IAAI,eAAe,KAAK,gBAAgB,aAAa;CACrD,IAAI,WAAW,KAAK,YAAY,SAAS;CACzC,IAAI,eAAe,KAAK,gBAAgB,aAAa;CACrD,IAAI,QAAQ,KAAK,UAAU,OAAO,SAAS,CAAC;CAE5C,IAAI,mBAAmB,KAAK,oBAAoB,iBAAiB;CACjE,KAAK,oBAAoB,iBAAiB;CAC1C,OAAO;AACT;AAEA,SAAgB,eAEd,OACA,MACA,gBACA,cACA;CACA,MAAM,cAAc,IAAI,KAAK,MAAM,UAAU,CAAC,CAAC,QAAQ;CACvD,MAAM,WAAW,YAAY,MAAM,MAAM,MAAM,WAAW;CAC1D,MAAM,aAAa,cAAc,KAAK;CACtC,MAAM,CAAC,GAAG,aAAaD,kBAAAA,WAAW,MAAM,MAAM,MAAM,UAAU;CAC9D,MAAM,cAAc,IAAIE,6BAAAA,YAAY,CAAC,CAClC,WAAW,MAAM,KAAK,mBAAmB,MAAM,EAAE,CAAC,CAClD,aAAa,SAAS,CAAC,CACvB,aAAa,KAAK,MAAM,cAAc,GAAI,CAAC,CAAC,CAC5C,YAAY,QAAQ,CAAC,CACrB,cAAc,UAAU;CAE3B,MAAM,SAASC,6BAAAA,oBAAoB,MAAM,UAAU,YAAY;CAG/D,MAAM,UAAUC,yBAAAA,mBAAmB,MAAM,IAAI;CAC7C,MAAM,MAAM,WAAW,SAAS,kBAAkB,UAAU,UAAU,KAAA;CACtE,IAAI,OAAO,gBACT,YAAY,WAAW,WAAW,MAAM,MAAM,gBAAgB,GAAG,CAAC;CAEpE,MAAM,iBAAiBC,6BAAAA,aAAa,MAAM,IAAI;CAC9C,IAAI,gBACF,YAAY,kBAAkB,cAAc;CAE9C,QAAQ,MAAM,QAAQ,WAAW,QAAQ,KAAA,IAAY,QAArD;EACE,KAAK;GACH,YAAY,gBAAgB,KAAK;GACjC;EACF,KAAK;GACH,YAAY,gBAAgB,SAAS;GACrC;EACF,SAKE,YAAY,gBAAgB,OAAO;CAEvC;CACA,OAAO;AACT;;;;;;AAaA,SAAS,SAAS,OAAgB;CAChC,MAAM,EAAE,QAAQ,SAAS,aAAc,SAAS,CAAC;CACjD,IAAI,OAAO,WAAW,UAAU;EAC9B,QAAQ,MAAM,kDAAkD,WAAW,OAAO,KAAK,GAAG;EAC1F;CACF;CACA,QAAQ,MACN,2CAA2C,OAAO,UAAU,KAAK,UAAU,QAAQ,GACrF;AACF;AAEA,eAAsB,UACpB,aACA,SAEA,OACA,OAAyB,CAAC,GAC1B,gBACA,cACA;CACA,IAAIC,qCAAAA,eAAe,SAAS,MAAM,IAAI,GAAG,OAAO,KAAA;CAChD,MAAM,UAAU,IAAIC,6BAAAA,aAAa,aAAa,OAAO;CACrD,MAAM,UAAU,eAAe,OAAO,MAAM,gBAAgB,YAAY;CACxE,QAAQ,UAAU,CAAC,OAAO,CAAC;CAC3B,IAAI;EACF,OAAO,MAAM,QAAQ,QAAQ;CAC/B,SAAS,OAAO;EACd,SAAS,KAAK;EACd;CACF;AACF;;AAGA,MAAM,mBAAmB;AAEzB,eAAsB,WACpB,aACA,SAEA,QACA,OAAyB,CAAC,GAC1B,gBACA,cACA;CACA,MAAM,WAAW,OACd,QAAQ,UAAU,CAACD,qCAAAA,eAAe,SAAS,MAAM,IAAI,CAAC,CAAC,CACvD,QAAQ,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM,MAAM,UAAU,KAAK,gBAAgB,CAAC,CAChF,KAAK,UAAU,eAAe,OAAO,MAAM,gBAAgB,YAAY,CAAC;CAC3E,IAAI,SAAS,WAAW,GAAG,OAAO,KAAA;CAElC,IAAI,CAAC,KAAK,cAAc,SAAS,MAAM,UAAU,MAAM,kBAAkB,SAAS,GAChF,QAAQ,KACN,kFACF;CAEF,MAAM,UAAU,IAAIC,6BAAAA,aAAa,aAAa,OAAO;CACrD,QAAQ,UAAU,QAAQ;CAC1B,IAAI;EACF,OAAO,MAAM,QAAQ,QAAQ;CAC/B,SAAS,OAAO;EACd,SAAS,KAAK;EACd;CACF;AACF;AAEA,eAAsB,cAAc,aAAqB,SAAiB,eAAuB;CAC/F,MAAM,UAAU,IAAIV,6BAAAA,mBAAmB,CAAC,CACrC,kBAAkB,IAAI,CAAC,CACvB,kBAAkB,cAAc,CAAC,CACjC,gBAAgB,KAAK,CAAC,CACtB,eAAe,eAAe,CAAC,CAC/B,aAAa,QAAQ,CAAC,CACtB,mBAAmB,UAAU,CAAC,CAC9B,UAAU,OAAO,CAAC,CAClB,wBAAwB,OAAO,CAAC,CAChC,WAAW,SAAS,CAAC,CACrB,eAAe,IAAI,CAAC,CACpB,gBAAgB,IAAI,CAAC,CACrB,iBAAiB,MAAM,CAAC,CACxB,gBAAgB,CAAC,CAAC,CAClB,oBAAoB,GAAG,CAAC,CACxB,mBAAmB,CAAC,CAAC,CACrB,kBAAkB,cAAc;CAEnC,MAAM,WAAW,IAAIH,6BAAAA,SAAS,CAAC,CAAC,SAAS,kBAAkB;CAC3D,MAAM,UAAU,IAAII,6BAAAA,QAAQ,CAAC,CAAC,WAAW,OAAO;CAChD,MAAM,QAAQ,IAAII,6BAAAA,YAAY,CAAC,CAC5B,WAAW,KAAK,MAAM,KAAK,OAAO,IAAI,GAAQ,CAAC,CAAC,SAAS,CAAC,CAAC,CAC3D,aAAa,WAAW,CAAC,CACzB,aAAa,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC,CAAC,CAC3C,YAAY,QAAQ,CAAC,CACrB,WAAW,OAAO,CAAC,CACnB,gBAAgB,KAAK;CAExB,MAAM,UAAU,IAAIK,6BAAAA,aAAa,aAAa,OAAO;CACrD,QAAQ,iBAAiB,aAAa;CACtC,QAAQ,UAAU,CAAC,KAAK,CAAC;CACzB,OAAO,QAAQ,QAAQ;AACzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.d.cts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;;wBAiOgB,eAEd,OAAO,iBACP,MAAM,kBACN,yBACA,eAAe,oBAAiB;wBA8DZ,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAiBZ,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAiBZ,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
1
+ {"version":3,"file":"meta-conversions-api.d.cts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;;wBAwNgB,eAEd,OAAO,iBACP,MAAM,kBACN,yBACA,eAAe,oBAAiB;wBAiEZ,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAiBZ,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAuBZ,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.d.mts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;;wBAiOgB,eAEd,OAAO,iBACP,MAAM,kBACN,yBACA,eAAe,oBAAiB;wBA8DZ,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAiBZ,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAiBZ,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
1
+ {"version":3,"file":"meta-conversions-api.d.mts","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"mappings":";;;;wBAwNgB,eAEd,OAAO,iBACP,MAAM,kBACN,yBACA,eAAe,oBAAiB;wBAiEZ,UACpB,qBACA,iBAEA,OAAO,iBACP,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAiBZ,WACpB,qBACA,iBAEA,QAAQ,mBACR,OAAM,kBACN,yBACA,eAAe,oBAAiB,+CAAA;wBAuBZ,cAAc,qBAAqB,iBAAiB,wBAAqB,+CAAA"}
@@ -3,6 +3,7 @@ import { mapFBEvent } from "../track/fbq.mjs";
3
3
  import { resolveActionSource } from "./action-source.mjs";
4
4
  import { metaFbc, metaFbp } from "./click-ids.mjs";
5
5
  import { pageLocation } from "./page-location.mjs";
6
+ import { metaExtinfoVersion } from "./meta-capi.mjs";
6
7
  import { AppData, Content, CustomData, EventRequest, ExtendedDeviceInfo, ServerEvent, UserData } from "facebook-nodejs-business-sdk";
7
8
  //#region src/server/meta-conversions-api.ts
8
9
  const USER_ASSIGNED_COUNTRIES = ["xk"];
@@ -75,12 +76,9 @@ function getUserData(tags, data, eventTimeMs) {
75
76
  if (tags.ip_address && typeof tags.ip_address === "string") userData.setClientIpAddress(tags.ip_address);
76
77
  return userData;
77
78
  }
78
- function getAppData(tags, appPackageName) {
79
+ function getAppData(tags, appPackageName, version) {
79
80
  const extinfo = new ExtendedDeviceInfo();
80
- if (tags.os_name) {
81
- if (tags.os_name === "iOS" || tags.os_name === "iPadOS") extinfo.setExtInfoVersion("i2");
82
- else if (tags.os_name === "Android") extinfo.setExtInfoVersion("a2");
83
- }
81
+ extinfo.setExtInfoVersion(version);
84
82
  extinfo.setAppPackageName(appPackageName);
85
83
  const shortVersion = tags.release?.split(".").at(0);
86
84
  if (shortVersion) extinfo.setShortVersion(shortVersion);
@@ -93,8 +91,7 @@ function getAppData(tags, appPackageName) {
93
91
  if (tags.device_pixel_ratio) extinfo.setScreenDensity(tags.device_pixel_ratio.toString());
94
92
  const appData = new AppData();
95
93
  appData.setExtinfo(extinfo);
96
- if (tags.install_referrer) appData.setInstallReferrer(tags.install_referrer);
97
- if (tags.advertising_id) appData.setAdvertiserTrackingEnabled(true);
94
+ appData.setAdvertiserTrackingEnabled(!!tags.advertising_id);
98
95
  if (tags.install_referrer) appData.setInstallReferrer(tags.install_referrer);
99
96
  return appData;
100
97
  }
@@ -133,13 +130,12 @@ function getServerEvent(event, data, appPackageName, actionSource) {
133
130
  const [_, eventName] = mapFBEvent(event.name, event.properties);
134
131
  const serverEvent = new ServerEvent().setEventId(event.tags.idempotency_key ?? event.id).setEventName(eventName).setEventTime(Math.round(eventTimeMs / 1e3)).setUserData(userData).setCustomData(customData);
135
132
  const source = resolveActionSource(event.platform, actionSource);
136
- if (source === "app" && appPackageName) {
137
- const appData = getAppData(event.tags, appPackageName);
138
- serverEvent.setAppData(appData);
139
- }
133
+ const version = metaExtinfoVersion(event.tags);
134
+ const app = source === "app" && appPackageName && version ? version : void 0;
135
+ if (app && appPackageName) serverEvent.setAppData(getAppData(event.tags, appPackageName, app));
140
136
  const eventSourceUrl = pageLocation(event.tags);
141
137
  if (eventSourceUrl) serverEvent.setEventSourceUrl(eventSourceUrl);
142
- switch (source) {
138
+ switch (app ? "app" : source === "app" ? void 0 : source) {
143
139
  case "app":
144
140
  serverEvent.setActionSource("app");
145
141
  break;
@@ -180,6 +176,7 @@ const MAX_EVENT_AGE_MS = 60474e4;
180
176
  async function sendEvents(accessToken, pixelId, events, data = {}, appPackageName, actionSource) {
181
177
  const fbEvents = events.filter((event) => !IGNORED_EVENTS.includes(event.name)).filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS).map((event) => getServerEvent(event, data, appPackageName, actionSource));
182
178
  if (fbEvents.length === 0) return void 0;
179
+ if (!data.user_agent && fbEvents.some((event) => event.action_source === "website")) console.warn("Meta conversion: website events sent without client_user_agent (data.user_agent)");
183
180
  const request = new EventRequest(accessToken, pixelId);
184
181
  request.setEvents(fbEvents);
185
182
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"meta-conversions-api.mjs","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\nimport { type EventActionSource, resolveActionSource } from './action-source';\nimport { metaFbc, metaFbp } from './click-ids';\nimport { pageLocation } from './page-location';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData, eventTimeMs: number) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n const fbc = metaFbc(tags, eventTimeMs);\n if (fbc) {\n userData.setFbc(fbc);\n }\n const fbp = metaFbp(tags);\n if (fbp) {\n userData.setFbp(fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string) {\n const extinfo = new ExtendedDeviceInfo();\n if (tags.os_name) {\n if (tags.os_name === 'iOS' || tags.os_name === 'iPadOS') {\n extinfo.setExtInfoVersion('i2');\n } else if (tags.os_name === 'Android') {\n extinfo.setExtInfoVersion('a2');\n }\n }\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n if (tags.advertising_id) {\n appData.setAdvertiserTrackingEnabled(true);\n }\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const eventTimeMs = new Date(event.created_at).getTime();\n const userData = getUserData(event.tags, data, eventTimeMs);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(eventTimeMs / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n const source = resolveActionSource(event.platform, actionSource);\n if (source === 'app' && appPackageName) {\n const appData = getAppData(event.tags, appPackageName);\n serverEvent.setAppData(appData);\n }\n const eventSourceUrl = pageLocation(event.tags);\n if (eventSourceUrl) {\n serverEvent.setEventSourceUrl(eventSourceUrl);\n }\n switch (source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n // `action_source` is required on a server event, so both an offline conversion and an\n // undeterminable platform still have to send something. Meta has no generic offline\n // value: 'physical_store' and 'system_generated' are narrower claims only the caller can\n // make, so everything left lands in Meta's own catch-all.\n serverEvent.setActionSource('other');\n break;\n }\n return serverEvent;\n}\n\n/**\n * The shape `FacebookRequestError` exposes: `status` and `response` are the HTTP status and the\n * parsed error body, and both are null when the request never received a response.\n */\ntype MetaRequestError = { status?: number | null; message?: string; response?: unknown };\n\n/**\n * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.\n * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.\n * Never log the error itself: it also carries the access token, in both `url` and `data`.\n */\nfunction logError(error: unknown) {\n const { status, message, response } = (error ?? {}) as MetaRequestError;\n if (typeof status !== 'number') {\n console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);\n return;\n }\n console.error(\n `Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`\n );\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName, actionSource);\n request.setEvents([fbEvent]);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\n/** An `event_time` over 7 days old fails the whole request; see `meta-capi.ts`. */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getServerEvent(event, data, appPackageName, actionSource));\n if (fbEvents.length === 0) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"meta-conversions-api.mjs","names":[],"sources":["../../src/server/meta-conversions-api.ts"],"sourcesContent":["import {\n AppData,\n Content,\n CustomData,\n EventRequest,\n ExtendedDeviceInfo,\n ServerEvent,\n UserData,\n} from 'facebook-nodejs-business-sdk';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { mapFBEvent } from '../track/fbq';\nimport type { TrackEvent, TrackTags, UserProvidedData } from '../track/types';\nimport { type EventActionSource, resolveActionSource } from './action-source';\nimport { metaFbc, metaFbp } from './click-ids';\nimport { metaExtinfoVersion } from './meta-capi';\nimport { pageLocation } from './page-location';\n\nconst USER_ASSIGNED_COUNTRIES: string[] = ['xk'];\nfunction normalizeCountry(input: string | undefined): string | undefined {\n const country = input?.split(/[-_]/).at(0)?.toLowerCase();\n if (!country) return undefined;\n return USER_ASSIGNED_COUNTRIES.includes(country) ? undefined : country;\n}\n\nfunction getUserData(tags: TrackTags, data: UserProvidedData, eventTimeMs: number) {\n const userData = new UserData();\n\n // set user-provided data\n if (data.email) {\n if (Array.isArray(data.email)) {\n userData.setEmails(data.email);\n } else {\n userData.setEmail(data.email);\n }\n }\n if (data.phone_number) {\n if (Array.isArray(data.phone_number)) {\n userData.setPhones(data.phone_number);\n } else {\n userData.setPhone(data.phone_number);\n }\n }\n if (data.gender) {\n if (data.gender === 'female') {\n userData.setGender('f');\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- user-provided data may carry values outside the declared union at runtime\n } else if (data.gender === 'male') {\n userData.setGender('m');\n }\n }\n if (data.address) {\n if (Array.isArray(data.address)) {\n const firstNames = data.address.map((a) => a.first_name).filter(Boolean);\n const lastNames = data.address.map((a) => a.last_name).filter(Boolean);\n const cities = data.address.map((a) => a.city).filter(Boolean);\n const states = data.address.map((a) => a.region).filter(Boolean);\n const postalCodes = data.address.map((a) => a.postal_code).filter(Boolean);\n const countries = data.address.map((a) => normalizeCountry(a.country)).filter(Boolean);\n\n userData.setFirstNames(firstNames as string[]);\n userData.setLastNames(lastNames as string[]);\n userData.setCities(cities as string[]);\n userData.setStates(states as string[]);\n userData.setZips(postalCodes as string[]);\n userData.setCountries(countries as string[]);\n } else {\n if (data.address.first_name) {\n userData.setFirstName(data.address.first_name);\n userData.setF5First(data.address.first_name.slice(0, 5));\n }\n if (data.address.last_name) {\n userData.setLastName(data.address.last_name);\n userData.setF5Last(data.address.last_name.slice(0, 5));\n }\n if (data.address.city) userData.setCity(data.address.city);\n if (data.address.region) userData.setState(data.address.region);\n if (data.address.postal_code) userData.setZip(data.address.postal_code);\n if (data.address.country) {\n const country = normalizeCountry(data.address.country);\n if (country) userData.setCountry(country);\n }\n }\n }\n if (data.birthday) {\n userData.setDoby(data.birthday.year.toString());\n userData.setDobm(data.birthday.month.toString());\n userData.setDobd(data.birthday.day.toString());\n }\n if (data.user_id && data.user_id.length !== 0) {\n userData.setExternalId(data.user_id);\n }\n if (data.ip_address) {\n userData.setClientIpAddress(data.ip_address);\n }\n if (data.user_agent) {\n userData.setClientUserAgent(data.user_agent);\n }\n if (data.fb_login_id) {\n userData.setFbLoginId(data.fb_login_id);\n }\n if (data.fb_page_id) {\n userData.setPageId(data.fb_page_id);\n }\n\n // set tags info\n const fbc = metaFbc(tags, eventTimeMs);\n if (fbc) {\n userData.setFbc(fbc);\n }\n const fbp = metaFbp(tags);\n if (fbp) {\n userData.setFbp(fbp);\n }\n if (tags.advertising_id) {\n userData.setMadid(tags.advertising_id);\n }\n if (tags.ip_address && typeof tags.ip_address === 'string') {\n userData.setClientIpAddress(tags.ip_address);\n }\n\n return userData;\n}\n\nfunction getAppData(tags: TrackTags, appPackageName: string, version: 'i2' | 'a2') {\n const extinfo = new ExtendedDeviceInfo();\n extinfo.setExtInfoVersion(version);\n extinfo.setAppPackageName(appPackageName);\n const shortVersion = tags.release?.split('.').at(0);\n if (shortVersion) {\n extinfo.setShortVersion(shortVersion);\n }\n if (tags.release) {\n extinfo.setLongVersion(tags.release);\n }\n if (tags.os_version) {\n extinfo.setOsVersion(tags.os_version);\n }\n if (tags.device_model_id) {\n extinfo.setDeviceModelName(tags.device_model_id);\n }\n if (tags.language) {\n extinfo.setLocale(tags.language);\n }\n if (tags.screen_width) {\n extinfo.setScreenWidth(tags.screen_width);\n }\n if (tags.screen_height) {\n extinfo.setScreenHeight(tags.screen_height);\n }\n if (tags.device_pixel_ratio) {\n extinfo.setScreenDensity(tags.device_pixel_ratio.toString());\n }\n\n const appData = new AppData();\n appData.setExtinfo(extinfo);\n // Required on every app event; see `meta-capi.ts`.\n appData.setAdvertiserTrackingEnabled(!!tags.advertising_id);\n if (tags.install_referrer) {\n appData.setInstallReferrer(tags.install_referrer);\n }\n\n return appData;\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData({ name, properties }: TrackEvent<any>) {\n const data = new CustomData();\n const [_, _name, fbEventProperties] = mapFBEvent(name, properties);\n const {\n value,\n currency,\n content_name,\n content_category,\n content_ids,\n contents,\n content_type,\n // order_id,\n predicted_ltv,\n num_items,\n search_string,\n status,\n // item_number,\n delivery_category,\n ...custom_properties\n } = fbEventProperties;\n // oxlint-disable-next-line typescript/no-unnecessary-condition -- custom event properties may carry `value: null` at runtime despite the declared type\n if (value !== undefined && value !== null) data.setValue(value);\n if (currency) data.setCurrency(currency);\n if (content_name) data.setContentName(content_name);\n if (content_category) data.setContentCategory(content_category);\n if (content_ids) data.setContentIds(content_ids);\n if (contents)\n data.setContents(\n contents.map((c) => {\n const result = new Content().setId(c.id).setQuantity(c.quantity);\n if (c.item_price) result.setItemPrice(c.item_price);\n if (c.title) result.setTitle(c.title);\n if (c.description) result.setDescription(c.description);\n if (c.brand) result.setBrand(c.brand);\n if (c.category) result.setCategory(c.category);\n if (c.delivery_category) result.setDeliveryCategory(c.delivery_category);\n return result;\n })\n );\n if (content_type) data.setContentType(content_type);\n // if (order_id) data.setOrderId(order_id);\n if (predicted_ltv) data.setPredictedLtv(predicted_ltv);\n if (num_items) data.setNumItems(num_items);\n if (search_string) data.setSearchString(search_string);\n if (status) data.setStatus(status.toString());\n // if (item_number) data.setItemNumber(item_number);\n if (delivery_category) data.setDeliveryCategory(delivery_category);\n data.setCustomProperties(custom_properties);\n return data;\n}\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData,\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const eventTimeMs = new Date(event.created_at).getTime();\n const userData = getUserData(event.tags, data, eventTimeMs);\n const customData = getCustomData(event);\n const [_, eventName] = mapFBEvent(event.name, event.properties);\n const serverEvent = new ServerEvent()\n .setEventId(event.tags.idempotency_key ?? event.id)\n .setEventName(eventName)\n .setEventTime(Math.round(eventTimeMs / 1000))\n .setUserData(userData)\n .setCustomData(customData);\n\n const source = resolveActionSource(event.platform, actionSource);\n // An app event that cannot carry the required `app_data` — no package name, or a desktop OS —\n // is sent as `other`, as in `meta-capi.ts`.\n const version = metaExtinfoVersion(event.tags);\n const app = source === 'app' && appPackageName && version ? version : undefined;\n if (app && appPackageName) {\n serverEvent.setAppData(getAppData(event.tags, appPackageName, app));\n }\n const eventSourceUrl = pageLocation(event.tags);\n if (eventSourceUrl) {\n serverEvent.setEventSourceUrl(eventSourceUrl);\n }\n switch (app ? 'app' : source === 'app' ? undefined : source) {\n case 'app':\n serverEvent.setActionSource('app');\n break;\n case 'web':\n serverEvent.setActionSource('website');\n break;\n default:\n // `action_source` is required on a server event, so both an offline conversion and an\n // undeterminable platform still have to send something. Meta has no generic offline\n // value: 'physical_store' and 'system_generated' are narrower claims only the caller can\n // make, so everything left lands in Meta's own catch-all.\n serverEvent.setActionSource('other');\n break;\n }\n return serverEvent;\n}\n\n/**\n * The shape `FacebookRequestError` exposes: `status` and `response` are the HTTP status and the\n * parsed error body, and both are null when the request never received a response.\n */\ntype MetaRequestError = { status?: number | null; message?: string; response?: unknown };\n\n/**\n * Meta's SDK rejects on API errors, where every other conversion sender here logs and resolves.\n * Keep the failure inside this module, so one rejected batch cannot fail the caller's request.\n * Never log the error itself: it also carries the access token, in both `url` and `data`.\n */\nfunction logError(error: unknown) {\n const { status, message, response } = (error ?? {}) as MetaRequestError;\n if (typeof status !== 'number') {\n console.error(`Failed to send Meta conversion, network error: ${message ?? String(error)}`);\n return;\n }\n console.error(\n `Failed to send Meta conversion, status: ${status}, body: ${JSON.stringify(response)}`\n );\n}\n\nexport async function sendEvent(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n if (IGNORED_EVENTS.includes(event.name)) return undefined;\n const request = new EventRequest(accessToken, pixelId);\n const fbEvent = getServerEvent(event, data, appPackageName, actionSource);\n request.setEvents([fbEvent]);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\n/** An `event_time` over 7 days old fails the whole request; see `meta-capi.ts`. */\nconst MAX_EVENT_AGE_MS = 7 * 24 * 60 * 60 * 1000 - 60 * 1000;\n\nexport async function sendEvents(\n accessToken: string,\n pixelId: string,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n appPackageName?: string,\n actionSource?: EventActionSource\n) {\n const fbEvents = events\n .filter((event) => !IGNORED_EVENTS.includes(event.name))\n .filter((event) => Date.now() - Date.parse(event.created_at) <= MAX_EVENT_AGE_MS)\n .map((event) => getServerEvent(event, data, appPackageName, actionSource));\n if (fbEvents.length === 0) return undefined;\n // \"The client_user_agent is required for website events\"; see `meta-capi.ts`.\n if (!data.user_agent && fbEvents.some((event) => event.action_source === 'website')) {\n console.warn(\n 'Meta conversion: website events sent without client_user_agent (data.user_agent)'\n );\n }\n const request = new EventRequest(accessToken, pixelId);\n request.setEvents(fbEvents);\n try {\n return await request.execute();\n } catch (error) {\n logError(error);\n return undefined;\n }\n}\n\nexport async function sendTestEvent(accessToken: string, pixelId: string, testEventCode: string) {\n const extinfo = new ExtendedDeviceInfo()\n .setExtInfoVersion('a2')\n .setAppPackageName('com.some.app')\n .setShortVersion('771')\n .setLongVersion('Version 7.7.1')\n .setOsVersion('10.1.1')\n .setDeviceModelName('OnePlus6')\n .setLocale('en_US')\n .setTimezoneAbbreviation('GMT-1')\n .setCarrier('TMobile')\n .setScreenWidth(1920)\n .setScreenHeight(1080)\n .setScreenDensity('2.00')\n .setCpuCoreCount(2)\n .setTotalDiskSpaceGb(128)\n .setFreeDiskSpaceGb(8)\n .setDeviceTimeZone('USA/New York');\n\n const userData = new UserData().setEmail('test@example.com');\n const appData = new AppData().setExtinfo(extinfo);\n const event = new ServerEvent()\n .setEventId(Math.round(Math.random() * 1000_000).toString())\n .setEventName('TestEvent')\n .setEventTime(Math.round(Date.now() / 1000))\n .setUserData(userData)\n .setAppData(appData)\n .setActionSource('app');\n\n const request = new EventRequest(accessToken, pixelId);\n request.setTestEventCode(testEventCode);\n request.setEvents([event]);\n return request.execute();\n}\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs"
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
8
  let _tanstack_react_router = require("@tanstack/react-router");
9
9
  //#region src/tanstack/analytics.tsx
10
- function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, hotjarId, facebookAppId, reportWebVitals = true }) {
10
+ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, uetConsent = { ad_storage: "granted" }, hotjarId, facebookAppId, reportWebVitals = true }) {
11
11
  const { pathname, searchStr } = (0, _tanstack_react_router.useLocation)();
12
12
  require_hooks_use_web_analytics.useWebAnalytics(pathname, searchStr);
13
13
  require_hooks_use_outbound_click_analytics.useOutboundClickAnalytics();
@@ -140,6 +140,7 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId,
140
140
  dangerouslySetInnerHTML: { __html: `
141
141
  (function (w, d, t, u, o) {
142
142
  w[u] = w[u] || [], o.ts = (new Date).getTime();
143
+ ${uetConsent ? `w[u].push("consent", "default", ${JSON.stringify(uetConsent)});` : ""}
143
144
  var n = d.createElement(t);
144
145
  n.src = "https://bat.bing.net/bat.js?ti=" + o.ti + ("uetq" != u ? "&q=" + u : ""),
145
146
  n.async = 1, n.onload = n.onreadystatechange = function() {
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.cjs","names":["useLocation"],"sources":["../../src/tanstack/analytics.tsx"],"sourcesContent":["import { useLocation } from '@tanstack/react-router';\nimport { useOutboundClickAnalytics } from '../hooks/use-outbound-click-analytics';\nimport { useReportWebVitals } from '../hooks/use-report-web-vitals';\nimport { useUETIdSync } from '../hooks/use-uet-id-sync';\nimport { useWebAnalytics } from '../hooks/use-web-analytics';\nimport type { PixelId as MetaPixelId } from '../track/fbq';\nimport type { GaId, GtmId } from '../track/gtag';\nimport { track } from '../track/index';\nimport type { PixelId as RedditPixelId } from '../track/rdt';\n\ninterface Props {\n gaId?: GaId;\n gaSrc?: string;\n gtmId?: GtmId;\n metaPixelId?: MetaPixelId;\n openaiPixelId?: string;\n redditPixelId?: RedditPixelId;\n linkedInPartnerId?: `${number}`;\n /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */\n uetTagId?: `${number}`;\n /** Microsoft Advertising customer id (`cid` in the ads UI's URLs): enables the Conversions API's ID Sync pixel. */\n uetCustomerId?: `${number}`;\n hotjarId?: `${number}`;\n facebookAppId?: string;\n nonce?: string;\n debugMode?: boolean;\n reportWebVitals?: boolean;\n}\n\nexport function Analytics({\n gaId,\n gaSrc,\n nonce,\n debugMode,\n metaPixelId,\n openaiPixelId,\n redditPixelId,\n linkedInPartnerId,\n uetTagId,\n uetCustomerId,\n hotjarId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const { pathname, searchStr } = useLocation();\n useWebAnalytics(pathname, searchStr);\n useOutboundClickAnalytics();\n useUETIdSync(uetCustomerId);\n\n useReportWebVitals((metric) => {\n if (!reportWebVitals) return;\n const properties = {\n value: metric.delta,\n metric_id: metric.id,\n metric_value: metric.value,\n metric_delta: metric.delta,\n metric_rating: metric.rating,\n metric_navigation_type: metric.navigationType,\n non_interaction: true, // avoids affecting bounce rate.\n };\n track(metric.name, properties);\n });\n\n return (\n <>\n {facebookAppId && <meta property=\"fb:app_id\" content={facebookAppId} />}\n {gaId && (\n <>\n <script\n async\n id=\"gtag\"\n nonce={nonce}\n src={gaSrc ?? `https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n />\n <script\n async\n nonce={nonce}\n id=\"gtag-init\"\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', '${gaId}'${debugMode ? \" ,{ 'debug_mode': true }\" : ''});\n `,\n }}\n />\n </>\n )}\n {metaPixelId && (\n <script\n async\n id=\"meta-pixel\"\n dangerouslySetInnerHTML={{\n __html: `\n !(function (f, b, e, v, n, t, s) {\n if (f.fbq) return;\n n = f.fbq = function () {\n n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);\n };\n if (!f._fbq) f._fbq = n;\n n.push = n;\n n.loaded = !0;\n n.version = '2.0';\n n.queue = [];\n t = b.createElement(e);\n t.async = !0;\n t.src = v;\n s = b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t, s);\n })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');\n fbq('init', '${metaPixelId}');\n fbq('track', 'PageView');`,\n }}\n />\n )}\n {openaiPixelId && (\n <script\n async\n id=\"openai-pixel\"\n dangerouslySetInnerHTML={{\n __html: `\n (function (w, d, s, u) {\n if (w.oaiq) return;\n var q = function () {\n q.q.push(arguments);\n };\n q.q = [];\n w.oaiq = q;\n var js = d.createElement(s);\n js.async = true;\n js.src = u;\n var f = d.getElementsByTagName(s)[0];\n f.parentNode.insertBefore(js, f);\n })(window, document, \"script\", \"https://bzrcdn.openai.com/sdk/oaiq.min.js\");\n\n oaiq(\"init\", { pixelId: \"${openaiPixelId}\" });\n oaiq(\"measure\", \"page_viewed\", { type: \"contents\" });\n `,\n }}\n />\n )}\n {redditPixelId && (\n <script\n async\n id=\"reddit-pixel\"\n dangerouslySetInnerHTML={{\n __html: `\n !function(w,d) {\n if(!w.rdt) {\n var p = w.rdt = function() {\n p.sendEvent ? p.sendEvent.apply(p,arguments) : p.callQueue.push(arguments)\n };\n p.callQueue = [];\n var t = d.createElement(\"script\");\n t.src = \"https://www.redditstatic.com/ads/pixel.js\";\n t.async = !0;\n var s = d.getElementsByTagName(\"script\")[0];\n s.parentNode.insertBefore(t,s)\n }\n }(window, document);\n rdt('init', '${redditPixelId}');\n rdt('track', 'PageVisit');`,\n }}\n />\n )}\n {linkedInPartnerId && (\n <script\n async\n id=\"linkedin-insight-tag\"\n dangerouslySetInnerHTML={{\n __html: `\n _linkedin_partner_id = \"${linkedInPartnerId}\";\n window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];\n window._linkedin_data_partner_ids.push(_linkedin_partner_id);\n\n (function(l) {\n if (!l){\n window.lintrk = function(a,b){\n window.lintrk.q.push([a,b])\n };\n window.lintrk.q=[]\n }\n var s = document.getElementsByTagName(\"script\")[0];\n var b = document.createElement(\"script\");\n b.type = \"text/javascript\";b.async = true;\n b.src = \"https://snap.licdn.com/li.lms-analytics/insight.min.js\";\n s.parentNode.insertBefore(b, s);\n })(window.lintrk);\n `,\n }}\n />\n )}\n {uetTagId && (\n <script\n async\n id=\"uet-tag\"\n dangerouslySetInnerHTML={{\n __html: `\n (function (w, d, t, u, o) {\n w[u] = w[u] || [], o.ts = (new Date).getTime();\n var n = d.createElement(t);\n n.src = \"https://bat.bing.net/bat.js?ti=\" + o.ti + (\"uetq\" != u ? \"&q=\" + u : \"\"),\n n.async = 1, n.onload = n.onreadystatechange = function() {\n var s = this.readyState;\n s && \"loaded\" !== s && \"complete\" !== s ||\n (o.q = w[u], w[u] = new UET(o), w[u].push(\"pageLoad\"),\n n.onload = n.onreadystatechange = null)\n };\n var i = d.getElementsByTagName(t)[0];\n i.parentNode.insertBefore(n, i);\n })(window, document, \"script\", \"uetq\", {\n ti: \"${uetTagId}\",\n enableAutoSpaTracking: true\n });\n `,\n }}\n />\n )}\n {hotjarId && (\n <script\n async\n id=\"hotjar\"\n dangerouslySetInnerHTML={{\n __html: `\n (function(h,o,t,j,a,r){\n h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};\n h._hjSettings={hjid:${hotjarId},hjsv:6};\n a=o.getElementsByTagName('head')[0];\n r=o.createElement('script');r.async=1;\n r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;\n a.appendChild(r);\n })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');\n `,\n }}\n />\n )}\n </>\n );\n}\n"],"mappings":";;;;;;;;;AA6BA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,UACA,eACA,kBAAkB,QACV;CACR,MAAM,EAAE,UAAU,eAAA,GAAcA,uBAAAA,YAAAA,CAAY;CAC5C,gCAAA,gBAAgB,UAAU,SAAS;CACnC,2CAAA,0BAA0B;CAC1B,8BAAA,aAAa,aAAa;CAE1B,oCAAA,oBAAoB,WAAW;EAC7B,IAAI,CAAC,iBAAiB;EACtB,MAAM,aAAa;GACjB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,cAAc,OAAO;GACrB,eAAe,OAAO;GACtB,wBAAwB,OAAO;GAC/B,iBAAiB;EACnB;EACA,oBAAA,MAAM,OAAO,MAAM,UAAU;CAC/B,CAAC;CAED,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,iBAAiB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,UAAS;GAAY,SAAS;EAAgB,CAAA;EACrE,QACC,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACI;GACP,KAAK,SAAS,+CAA+C;EAC9D,CAAA,GACD,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACO;GACP,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;gCAIU,KAAK,GAAG,YAAY,6BAA6B,GAAG;cAExE;EACD,CAAA,CACD,EAAA,CAAA;EAEH,eACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;;;2BAiBO,YAAY;uCAE7B;EACD,CAAA;EAEF,iBACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;2CAeuB,cAAc;;cAG/C;EACD,CAAA;EAEF,iBACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;2BAcO,cAAc;wCAE/B;EACD,CAAA;EAEF,qBACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;sCACkB,kBAAkB;;;;;;;;;;;;;;;;;cAkB9C;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;uBAcG,SAAS;;;cAItB;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;oCAGgB,SAAS;;;;;;cAOnC;EACD,CAAA;CAEH,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"analytics.cjs","names":["useLocation"],"sources":["../../src/tanstack/analytics.tsx"],"sourcesContent":["import { useLocation } from '@tanstack/react-router';\nimport { useOutboundClickAnalytics } from '../hooks/use-outbound-click-analytics';\nimport { useReportWebVitals } from '../hooks/use-report-web-vitals';\nimport { useUETIdSync } from '../hooks/use-uet-id-sync';\nimport { useWebAnalytics } from '../hooks/use-web-analytics';\nimport type { PixelId as MetaPixelId } from '../track/fbq';\nimport type { GaId, GtmId } from '../track/gtag';\nimport { track } from '../track/index';\nimport type { PixelId as RedditPixelId } from '../track/rdt';\nimport type { UETConsent } from '../track/uetq';\n\ninterface Props {\n gaId?: GaId;\n gaSrc?: string;\n gtmId?: GtmId;\n metaPixelId?: MetaPixelId;\n openaiPixelId?: string;\n redditPixelId?: RedditPixelId;\n linkedInPartnerId?: `${number}`;\n /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */\n uetTagId?: `${number}`;\n /** Microsoft Advertising customer id (`cid` in the ads UI's URLs): enables the Conversions API's ID Sync pixel. */\n uetCustomerId?: `${number}`;\n /**\n * UET consent mode default, pushed inside the tag snippet so bat.js reads it before the first\n * page load. Defaults to `ad_storage: granted`, which is how the tag behaves without a signal,\n * so Microsoft stops flagging EEA/UK/CH events as missing consent. Sites with a consent banner\n * pass `{ ad_storage: 'denied' }` and send `update` with `setUETConsent` when the visitor\n * decides; `false` pushes no default.\n * https://help.ads.microsoft.com/#apex/ads/en/60119/1\n */\n uetConsent?: UETConsent | false;\n hotjarId?: `${number}`;\n facebookAppId?: string;\n nonce?: string;\n debugMode?: boolean;\n reportWebVitals?: boolean;\n}\n\nexport function Analytics({\n gaId,\n gaSrc,\n nonce,\n debugMode,\n metaPixelId,\n openaiPixelId,\n redditPixelId,\n linkedInPartnerId,\n uetTagId,\n uetCustomerId,\n uetConsent = { ad_storage: 'granted' },\n hotjarId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const { pathname, searchStr } = useLocation();\n useWebAnalytics(pathname, searchStr);\n useOutboundClickAnalytics();\n useUETIdSync(uetCustomerId);\n\n useReportWebVitals((metric) => {\n if (!reportWebVitals) return;\n const properties = {\n value: metric.delta,\n metric_id: metric.id,\n metric_value: metric.value,\n metric_delta: metric.delta,\n metric_rating: metric.rating,\n metric_navigation_type: metric.navigationType,\n non_interaction: true, // avoids affecting bounce rate.\n };\n track(metric.name, properties);\n });\n\n return (\n <>\n {facebookAppId && <meta property=\"fb:app_id\" content={facebookAppId} />}\n {gaId && (\n <>\n <script\n async\n id=\"gtag\"\n nonce={nonce}\n src={gaSrc ?? `https://www.googletagmanager.com/gtag/js?id=${gaId}`}\n />\n <script\n async\n nonce={nonce}\n id=\"gtag-init\"\n dangerouslySetInnerHTML={{\n __html: `\n window.dataLayer = window.dataLayer || [];\n function gtag(){dataLayer.push(arguments);}\n gtag('js', new Date());\n gtag('config', '${gaId}'${debugMode ? \" ,{ 'debug_mode': true }\" : ''});\n `,\n }}\n />\n </>\n )}\n {metaPixelId && (\n <script\n async\n id=\"meta-pixel\"\n dangerouslySetInnerHTML={{\n __html: `\n !(function (f, b, e, v, n, t, s) {\n if (f.fbq) return;\n n = f.fbq = function () {\n n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments);\n };\n if (!f._fbq) f._fbq = n;\n n.push = n;\n n.loaded = !0;\n n.version = '2.0';\n n.queue = [];\n t = b.createElement(e);\n t.async = !0;\n t.src = v;\n s = b.getElementsByTagName(e)[0];\n s.parentNode.insertBefore(t, s);\n })(window, document, 'script', 'https://connect.facebook.net/en_US/fbevents.js');\n fbq('init', '${metaPixelId}');\n fbq('track', 'PageView');`,\n }}\n />\n )}\n {openaiPixelId && (\n <script\n async\n id=\"openai-pixel\"\n dangerouslySetInnerHTML={{\n __html: `\n (function (w, d, s, u) {\n if (w.oaiq) return;\n var q = function () {\n q.q.push(arguments);\n };\n q.q = [];\n w.oaiq = q;\n var js = d.createElement(s);\n js.async = true;\n js.src = u;\n var f = d.getElementsByTagName(s)[0];\n f.parentNode.insertBefore(js, f);\n })(window, document, \"script\", \"https://bzrcdn.openai.com/sdk/oaiq.min.js\");\n\n oaiq(\"init\", { pixelId: \"${openaiPixelId}\" });\n oaiq(\"measure\", \"page_viewed\", { type: \"contents\" });\n `,\n }}\n />\n )}\n {redditPixelId && (\n <script\n async\n id=\"reddit-pixel\"\n dangerouslySetInnerHTML={{\n __html: `\n !function(w,d) {\n if(!w.rdt) {\n var p = w.rdt = function() {\n p.sendEvent ? p.sendEvent.apply(p,arguments) : p.callQueue.push(arguments)\n };\n p.callQueue = [];\n var t = d.createElement(\"script\");\n t.src = \"https://www.redditstatic.com/ads/pixel.js\";\n t.async = !0;\n var s = d.getElementsByTagName(\"script\")[0];\n s.parentNode.insertBefore(t,s)\n }\n }(window, document);\n rdt('init', '${redditPixelId}');\n rdt('track', 'PageVisit');`,\n }}\n />\n )}\n {linkedInPartnerId && (\n <script\n async\n id=\"linkedin-insight-tag\"\n dangerouslySetInnerHTML={{\n __html: `\n _linkedin_partner_id = \"${linkedInPartnerId}\";\n window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];\n window._linkedin_data_partner_ids.push(_linkedin_partner_id);\n\n (function(l) {\n if (!l){\n window.lintrk = function(a,b){\n window.lintrk.q.push([a,b])\n };\n window.lintrk.q=[]\n }\n var s = document.getElementsByTagName(\"script\")[0];\n var b = document.createElement(\"script\");\n b.type = \"text/javascript\";b.async = true;\n b.src = \"https://snap.licdn.com/li.lms-analytics/insight.min.js\";\n s.parentNode.insertBefore(b, s);\n })(window.lintrk);\n `,\n }}\n />\n )}\n {uetTagId && (\n <script\n async\n id=\"uet-tag\"\n dangerouslySetInnerHTML={{\n __html: `\n (function (w, d, t, u, o) {\n w[u] = w[u] || [], o.ts = (new Date).getTime();\n ${uetConsent ? `w[u].push(\"consent\", \"default\", ${JSON.stringify(uetConsent)});` : ''}\n var n = d.createElement(t);\n n.src = \"https://bat.bing.net/bat.js?ti=\" + o.ti + (\"uetq\" != u ? \"&q=\" + u : \"\"),\n n.async = 1, n.onload = n.onreadystatechange = function() {\n var s = this.readyState;\n s && \"loaded\" !== s && \"complete\" !== s ||\n (o.q = w[u], w[u] = new UET(o), w[u].push(\"pageLoad\"),\n n.onload = n.onreadystatechange = null)\n };\n var i = d.getElementsByTagName(t)[0];\n i.parentNode.insertBefore(n, i);\n })(window, document, \"script\", \"uetq\", {\n ti: \"${uetTagId}\",\n enableAutoSpaTracking: true\n });\n `,\n }}\n />\n )}\n {hotjarId && (\n <script\n async\n id=\"hotjar\"\n dangerouslySetInnerHTML={{\n __html: `\n (function(h,o,t,j,a,r){\n h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};\n h._hjSettings={hjid:${hotjarId},hjsv:6};\n a=o.getElementsByTagName('head')[0];\n r=o.createElement('script');r.async=1;\n r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;\n a.appendChild(r);\n })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');\n `,\n }}\n />\n )}\n </>\n );\n}\n"],"mappings":";;;;;;;;;AAuCA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,aAAa,EAAE,YAAY,UAAU,GACrC,UACA,eACA,kBAAkB,QACV;CACR,MAAM,EAAE,UAAU,eAAA,GAAcA,uBAAAA,YAAAA,CAAY;CAC5C,gCAAA,gBAAgB,UAAU,SAAS;CACnC,2CAAA,0BAA0B;CAC1B,8BAAA,aAAa,aAAa;CAE1B,oCAAA,oBAAoB,WAAW;EAC7B,IAAI,CAAC,iBAAiB;EACtB,MAAM,aAAa;GACjB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,cAAc,OAAO;GACrB,cAAc,OAAO;GACrB,eAAe,OAAO;GACtB,wBAAwB,OAAO;GAC/B,iBAAiB;EACnB;EACA,oBAAA,MAAM,OAAO,MAAM,UAAU;CAC/B,CAAC;CAED,OACE,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA;EACG,iBAAiB,iBAAA,GAAA,kBAAA,IAAA,CAAC,QAAD;GAAM,UAAS;GAAY,SAAS;EAAgB,CAAA;EACrE,QACC,iBAAA,GAAA,kBAAA,KAAA,CAAA,kBAAA,UAAA,EAAA,UAAA,CACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACI;GACP,KAAK,SAAS,+CAA+C;EAC9D,CAAA,GACD,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACO;GACP,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;gCAIU,KAAK,GAAG,YAAY,6BAA6B,GAAG;cAExE;EACD,CAAA,CACD,EAAA,CAAA;EAEH,eACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;;;2BAiBO,YAAY;uCAE7B;EACD,CAAA;EAEF,iBACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;2CAeuB,cAAc;;cAG/C;EACD,CAAA;EAEF,iBACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;2BAcO,cAAc;wCAE/B;EACD,CAAA;EAEF,qBACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;sCACkB,kBAAkB;;;;;;;;;;;;;;;;;cAkB9C;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;kBAGF,aAAa,mCAAmC,KAAK,UAAU,UAAU,EAAE,MAAM,GAAG;;;;;;;;;;;;uBAY/E,SAAS;;;cAItB;EACD,CAAA;EAEF,YACC,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;oCAGgB,SAAS;;;;;;cAOnC;EACD,CAAA;CAEH,EAAA,CAAA;AAEN"}
@@ -1,6 +1,7 @@
1
1
  import { GaId, GtmId } from "../track/gtag.cjs";
2
2
  import { PixelId } from "../track/fbq.cjs";
3
3
  import { PixelId as PixelId$1 } from "../track/rdt.cjs";
4
+ import { UETConsent } from "../track/uetq.cjs";
4
5
  //#region src/tanstack/analytics.d.ts
5
6
  interface Props {
6
7
  gaId?: GaId;
@@ -14,12 +15,21 @@ interface Props {
14
15
  uetTagId?: `${number}`;
15
16
  /** Microsoft Advertising customer id (`cid` in the ads UI's URLs): enables the Conversions API's ID Sync pixel. */
16
17
  uetCustomerId?: `${number}`;
18
+ /**
19
+ * UET consent mode default, pushed inside the tag snippet so bat.js reads it before the first
20
+ * page load. Defaults to `ad_storage: granted`, which is how the tag behaves without a signal,
21
+ * so Microsoft stops flagging EEA/UK/CH events as missing consent. Sites with a consent banner
22
+ * pass `{ ad_storage: 'denied' }` and send `update` with `setUETConsent` when the visitor
23
+ * decides; `false` pushes no default.
24
+ * https://help.ads.microsoft.com/#apex/ads/en/60119/1
25
+ */
26
+ uetConsent?: UETConsent | false;
17
27
  hotjarId?: `${number}`;
18
28
  facebookAppId?: string;
19
29
  nonce?: string;
20
30
  debugMode?: boolean;
21
31
  reportWebVitals?: boolean;
22
32
  }
23
- export declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, hotjarId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
33
+ export declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, uetCustomerId, uetConsent, hotjarId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
24
34
  //#endregion
25
35
  //# sourceMappingURL=analytics.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.cts","names":["MetaPixelId","RedditPixelId"],"sources":["../../src/tanstack/analytics.tsx"],"mappings":";;;;UAUU;EACR,OAAO;EACP;EACA,QAAQ;EACR,cAAcA;EACd;EACA,gBAAgBC;EAChB;;EAEA;;EAEA;EACA;EACA;EACA;EACA;EACA;;wBAGc,YACd,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,UACA,eACA,mBACC,wBAAK,IAAA"}
1
+ {"version":3,"file":"analytics.d.cts","names":["MetaPixelId","RedditPixelId"],"sources":["../../src/tanstack/analytics.tsx"],"mappings":";;;;;UAWU;EACR,OAAO;EACP;EACA,QAAQ;EACR,cAAcA;EACd;EACA,gBAAgBC;EAChB;;EAEA;;EAEA;;;;;;;;;EASA,aAAa;EACb;EACA;EACA;EACA;EACA;;wBAGc,YACd,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,YACA,UACA,eACA,mBACC,wBAAK,IAAA"}