@shware/analytics 8.3.0 → 8.5.0

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 (70) hide show
  1. package/README.md +23 -8
  2. package/dist/hooks/use-web-analytics.cjs +11 -5
  3. package/dist/hooks/use-web-analytics.cjs.map +1 -1
  4. package/dist/hooks/use-web-analytics.d.cts +4 -1
  5. package/dist/hooks/use-web-analytics.d.cts.map +1 -1
  6. package/dist/hooks/use-web-analytics.d.mts +4 -1
  7. package/dist/hooks/use-web-analytics.d.mts.map +1 -1
  8. package/dist/hooks/use-web-analytics.mjs +11 -5
  9. package/dist/hooks/use-web-analytics.mjs.map +1 -1
  10. package/dist/next/index.cjs +38 -2
  11. package/dist/next/index.cjs.map +1 -1
  12. package/dist/next/index.d.cts +3 -1
  13. package/dist/next/index.d.cts.map +1 -1
  14. package/dist/next/index.d.mts +3 -1
  15. package/dist/next/index.d.mts.map +1 -1
  16. package/dist/next/index.mjs +39 -3
  17. package/dist/next/index.mjs.map +1 -1
  18. package/dist/react-router/index.cjs +25 -3
  19. package/dist/react-router/index.cjs.map +1 -1
  20. package/dist/react-router/index.d.cts +3 -1
  21. package/dist/react-router/index.d.cts.map +1 -1
  22. package/dist/react-router/index.d.mts +3 -1
  23. package/dist/react-router/index.d.mts.map +1 -1
  24. package/dist/react-router/index.mjs +25 -3
  25. package/dist/react-router/index.mjs.map +1 -1
  26. package/dist/schema/index.cjs +1 -0
  27. package/dist/schema/index.cjs.map +1 -1
  28. package/dist/schema/index.d.cts +4 -0
  29. package/dist/schema/index.d.cts.map +1 -1
  30. package/dist/schema/index.d.mts +4 -0
  31. package/dist/schema/index.d.mts.map +1 -1
  32. package/dist/schema/index.mjs +1 -0
  33. package/dist/schema/index.mjs.map +1 -1
  34. package/dist/server/index.d.cts +2 -2
  35. package/dist/server/index.d.mts +2 -2
  36. package/dist/server/microsoft-conversions-api.cjs +7 -7
  37. package/dist/server/microsoft-conversions-api.cjs.map +1 -1
  38. package/dist/server/microsoft-conversions-api.d.cts +15 -8
  39. package/dist/server/microsoft-conversions-api.d.cts.map +1 -1
  40. package/dist/server/microsoft-conversions-api.d.mts +15 -8
  41. package/dist/server/microsoft-conversions-api.d.mts.map +1 -1
  42. package/dist/server/microsoft-conversions-api.mjs +7 -7
  43. package/dist/server/microsoft-conversions-api.mjs.map +1 -1
  44. package/dist/tanstack/analytics.cjs +25 -3
  45. package/dist/tanstack/analytics.cjs.map +1 -1
  46. package/dist/tanstack/analytics.d.cts +3 -1
  47. package/dist/tanstack/analytics.d.cts.map +1 -1
  48. package/dist/tanstack/analytics.d.mts +3 -1
  49. package/dist/tanstack/analytics.d.mts.map +1 -1
  50. package/dist/tanstack/analytics.mjs +25 -3
  51. package/dist/tanstack/analytics.mjs.map +1 -1
  52. package/dist/track/types.d.cts +7 -0
  53. package/dist/track/types.d.cts.map +1 -1
  54. package/dist/track/types.d.mts +7 -0
  55. package/dist/track/types.d.mts.map +1 -1
  56. package/dist/web/index.cjs +25 -1
  57. package/dist/web/index.cjs.map +1 -1
  58. package/dist/web/index.d.cts.map +1 -1
  59. package/dist/web/index.d.mts.map +1 -1
  60. package/dist/web/index.mjs +25 -1
  61. package/dist/web/index.mjs.map +1 -1
  62. package/dist/web/page-key.cjs +53 -0
  63. package/dist/web/page-key.cjs.map +1 -0
  64. package/dist/web/page-key.d.cts +24 -0
  65. package/dist/web/page-key.d.cts.map +1 -0
  66. package/dist/web/page-key.d.mts +24 -0
  67. package/dist/web/page-key.d.mts.map +1 -0
  68. package/dist/web/page-key.mjs +52 -0
  69. package/dist/web/page-key.mjs.map +1 -0
  70. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-conversions-api.cjs","names":["createHash","formatMsclkid","mapUETEvent","pageLocation","IGNORED_EVENTS","chunk","fetch"],"sources":["../../src/server/microsoft-conversions-api.ts"],"sourcesContent":["/**\n * Microsoft Advertising Conversions API (CAPI) sender — the server-side counterpart of the UET\n * tag, in the same shape as the other senders here: a pure per-event builder, a never-throws\n * `sendEvents`, the token in a header and never in a URL or a log line.\n *\n * Microsoft's recommended deployment is hybrid: keep the UET JavaScript on the page and send the\n * same conversions here too, deduplicated by (`tagId`, `eventName`, `eventId`). Both channels in\n * this SDK are fed from the same `track()` call — the browser sends the internal name as the UET\n * action with the event id as `event_id`, and this builder sends the same name as `eventName`\n * with the same id — so the pair agrees by construction.\n *\n * The token comes from the UET tag's \"Use Conversions API\" setup step, or from the Campaign\n * Management API's `UetTagAuthKey/Query`. Access is provisioned per customer account: an account\n * whose tag setup has no \"Use Conversions API\" option has to ask Microsoft Advertising support.\n *\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration\n */\nimport { createHash } from 'node:crypto';\nimport { chunk, fetch } from '@shware/utils';\nimport { formatMsclkid } from '../click-id/index';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { mapUETEvent } from '../track/uetq';\nimport { pageLocation } from './page-location';\n\nconst ENDPOINT = 'https://capi.uet.microsoft.com/v1';\n\n/** The API's hard limit per request. */\nconst MAX_EVENTS_PER_REQUEST = 1000;\n\nconst SHA256 = /^[a-f0-9]{64}$/;\n\nconst sha256 = (value: string) => createHash('sha256').update(value).digest('hex');\n\n/**\n * Microsoft's email normalization: trim, drop the dots and any `+alias` from the user part —\n * for EVERY domain, unlike Google's gmail-only rule — lowercase, SHA-256. A value that is\n * already a SHA-256 hex digest passes through.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#hashed-identifiers\n */\nexport function normalizeEmail(input: string): string {\n const email = input.trim().toLowerCase();\n const at = email.lastIndexOf('@');\n if (at === -1) return email;\n const user = email.slice(0, at).replace(/\\./g, '').split('+', 1)[0] ?? '';\n return `${user}@${email.slice(at + 1)}`;\n}\n\nfunction hashEmail(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n return SHA256.test(value) ? value : sha256(normalizeEmail(value));\n}\n\n/**\n * Phones are E.164 by the schema (`+` then 11–15 digits, the range bat.js validates too); the\n * separators bat.js tolerates are stripped, and anything else is dropped rather than guessed —\n * a number with no country code hashed under an assumed one can never match.\n */\nfunction hashPhone(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n if (SHA256.test(value)) return value;\n const phone = value.replace(/[-() \\t]/g, '');\n return /^\\+\\d{11,15}$/.test(phone) ? sha256(phone) : undefined;\n}\n\nexport interface MicrosoftUserData {\n /** Microsoft click id, as a dashed UUID. */\n msclkid?: string;\n /** SHA-256 of the normalized email. */\n em?: string;\n /** SHA-256 of the E.164 phone number. */\n ph?: string;\n /** The SDK visitor id — what the client-side ID Sync pixel (`sendUETIdSync`) sends as `VID`. */\n anonymousId?: string;\n /** SHA-256 of the signed-in user id — what `sendUETIdSync` sends as `UID`. */\n externalId?: string;\n clientUserAgent?: string;\n clientIpAddress?: string;\n idfa?: string;\n gaid?: string;\n}\n\nexport interface MicrosoftItem {\n id?: string;\n name?: string;\n price?: number;\n quantity?: number;\n}\n\nexport interface MicrosoftCustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: MicrosoftItem[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n}\n\n/** One entry of the `data` array POSTed to `/v1/{tagId}/events`. */\nexport interface MicrosoftEvent {\n eventType: 'custom' | 'pageLoad';\n eventId: string;\n /** The UET action; absent on `pageLoad` events. */\n eventName?: string;\n /** UNIX seconds, UTC; the API rejects anything older than 7 days. */\n eventTime: number;\n eventSourceUrl?: string;\n referrerUrl?: string;\n pageTitle?: string;\n /** `G` granted, `D` denied. Absent means granted. */\n adStorageConsent?: 'G' | 'D';\n userData: MicrosoftUserData;\n customData?: MicrosoftCustomData;\n}\n\nexport interface MicrosoftConversionsOptions {\n /**\n * EEA/UK/CH consent for the whole batch. `denied` events are accepted but used for nothing —\n * no attribution, no audiences. Absent means granted, which is also the API's default.\n */\n consent?: 'granted' | 'denied';\n /**\n * Also send `page_view` events, as CAPI `pageLoad` events. Off by default because the UET\n * JavaScript already reports every page load and the two would double-count destination-URL\n * goals; turn it on for a CAPI-only site with no tag on the page.\n *\n * Not yet modelled: `pageLoadId`, which links custom events to the page load they happened on,\n * and the revenue-only custom event a `pageLoad` needs for a destination goal with variable\n * revenue. Both matter only in this CAPI-only mode.\n */\n pageLoads?: boolean;\n /** Identifies a third-party sender in Microsoft's monitoring; leave unset for a first-party build. */\n dataProvider?: string;\n /**\n * Process the valid events of a batch and report the invalid ones, instead of the API's default\n * all-or-nothing rejection. On by default — one malformed event should cost itself, not the\n * batch it travelled with.\n */\n continueOnValidationError?: boolean;\n}\n\nexport interface MicrosoftValidationDetail {\n index?: number;\n propertyName?: string;\n attemptedValue?: unknown;\n errorMessage?: string;\n errorCode?: string;\n isWarning?: boolean;\n}\n\nexport interface MicrosoftConversionsResponse {\n eventsReceived?: number;\n error?: { code?: string; message?: string; details?: MicrosoftValidationDetail[] };\n}\n\nfunction getUserData(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData\n): MicrosoftUserData {\n const { tags, platform, visitor_id } = event;\n const email = Array.isArray(data.email) ? data.email[0] : data.email;\n const phone = Array.isArray(data.phone_number) ? data.phone_number[0] : data.phone_number;\n\n return {\n msclkid: tags.msclkid ? formatMsclkid(tags.msclkid) : undefined,\n em: hashEmail(email),\n ph: hashPhone(phone),\n // Microsoft requires this to equal the `VID` of the client-side ID Sync pixel; both sides use\n // the SDK visitor id, which every event carries and the client can always read.\n anonymousId: visitor_id || undefined,\n // Microsoft asks for an anonymized id and never a real one. SHA-256, the same digest\n // `sendUETIdSync` applies client-side, so `externalId` and the pixel's `UID` agree too.\n externalId: data.user_id ? sha256(data.user_id) : undefined,\n clientUserAgent: data.user_agent,\n clientIpAddress:\n typeof tags.ip_address === 'string' && tags.ip_address ? tags.ip_address : data.ip_address,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n gaid: platform === 'android' ? tags.advertising_id : undefined,\n };\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData(event: TrackEvent<any>): MicrosoftCustomData | undefined {\n const [, params] = mapUETEvent(event.name, event.properties);\n const customData: MicrosoftCustomData = {\n eventCategory: params.event_category,\n eventLabel: params.event_label,\n eventValue: params.event_value,\n searchTerm: params.search_term,\n transactionId: params.transaction_id,\n value: params.revenue_value,\n currency: params.currency,\n items: params.items?.map(({ id, name, price, quantity }) => ({ id, name, price, quantity })),\n itemIds: Array.isArray(params.ecomm_prodid)\n ? params.ecomm_prodid\n : params.ecomm_prodid\n ? [params.ecomm_prodid]\n : undefined,\n pageType: params.ecomm_pagetype,\n ecommTotalValue: params.ecomm_totalvalue,\n ecommCategory: params.ecomm_category,\n };\n return Object.values(customData).some((value) => value !== undefined) ? customData : undefined;\n}\n\n/**\n * Build one CAPI event. `page_view` becomes a `pageLoad` event (the caller decides whether to\n * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event\n * named after the track event, which is also the UET action the browser sent.\n */\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n consent?: 'granted' | 'denied'\n): MicrosoftEvent {\n const { id, name, tags, created_at } = event;\n const isPageLoad = name === 'page_view';\n\n return {\n eventType: isPageLoad ? 'pageLoad' : 'custom',\n eventId: tags.idempotency_key ?? id,\n eventName: isPageLoad ? undefined : name,\n eventTime: Math.round(new Date(created_at).getTime() / 1000),\n eventSourceUrl: pageLocation(tags),\n referrerUrl: tags.page_referrer,\n pageTitle: tags.page_title,\n adStorageConsent: consent === 'granted' ? 'G' : consent === 'denied' ? 'D' : undefined,\n userData: getUserData(event, data),\n customData: isPageLoad ? undefined : getCustomData(event),\n };\n}\n\n/** Strip undefined fields so the wire payload carries only what was set. */\nfunction compact<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\n/**\n * Send events to `POST /v1/{tagId}/events`. Batches above the API's 1,000-event limit are split\n * into consecutive requests; one response is returned per request made. Never throws: an HTTP\n * or network failure is logged (without the token) and yields no response for that request.\n */\nexport async function sendEvents(\n token: string,\n tagId: string | number,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n options: MicrosoftConversionsOptions = {}\n): Promise<MicrosoftConversionsResponse[]> {\n const { consent, pageLoads = false, dataProvider, continueOnValidationError = true } = options;\n\n const capiEvents = events\n .filter(\n (event) => (pageLoads && event.name === 'page_view') || !IGNORED_EVENTS.includes(event.name)\n )\n .map((event) => getServerEvent(event, data, consent))\n // A pageLoad without a URL is rejected by the API, and a page-less event has nothing to say.\n .filter((event) => event.eventType !== 'pageLoad' || event.eventSourceUrl);\n if (capiEvents.length === 0) return [];\n\n const responses: MicrosoftConversionsResponse[] = [];\n for (const batch of chunk(capiEvents, MAX_EVENTS_PER_REQUEST)) {\n const body = compact({ data: batch, continueOnValidationError, dataProvider });\n try {\n const response = await fetch(`${ENDPOINT}/${tagId}/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(body),\n });\n if (response.ok) {\n const result = (await response.json()) as MicrosoftConversionsResponse;\n // A 200 can still carry field-level warnings (the field was removed) and, with\n // `continueOnValidationError`, skipped events. Surface them: silent partial data loss is\n // the one failure mode the caller cannot see from the status code.\n if (result.error?.details?.length) {\n console.warn(\n `Microsoft conversion accepted with validation issues: ${JSON.stringify(result.error.details)}`\n );\n }\n responses.push(result);\n continue;\n }\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Microsoft conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Failed to send Microsoft conversion, network error: ${message}`);\n }\n }\n return responses;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"microsoft-conversions-api.cjs","names":["createHash","formatMsclkid","mapUETEvent","pageLocation","IGNORED_EVENTS","chunk","fetch"],"sources":["../../src/server/microsoft-conversions-api.ts"],"sourcesContent":["/**\n * Microsoft Advertising Conversions API (CAPI) sender — the server-side counterpart of the UET\n * tag, in the same shape as the other senders here: a pure per-event builder, a never-throws\n * `sendEvents`, the token in a header and never in a URL or a log line.\n *\n * Microsoft's recommended deployment is hybrid: keep the UET JavaScript on the page and send the\n * same conversions here too, deduplicated by (`tagId`, `eventName`, `eventId`). Both channels in\n * this SDK are fed from the same `track()` call — the browser sends the internal name as the UET\n * action with the event id as `event_id`, and this builder sends the same name as `eventName`\n * with the same id — so the pair agrees by construction.\n *\n * The token comes from the UET tag's \"Use Conversions API\" setup step, or from the Campaign\n * Management API's `UetTagAuthKey/Query`. Access is provisioned per customer account: an account\n * whose tag setup has no \"Use Conversions API\" option has to ask Microsoft Advertising support.\n *\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration\n */\nimport { createHash } from 'node:crypto';\nimport { chunk, fetch } from '@shware/utils';\nimport { formatMsclkid } from '../click-id/index';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { mapUETEvent } from '../track/uetq';\nimport { pageLocation } from './page-location';\n\nconst ENDPOINT = 'https://capi.uet.microsoft.com/v1';\n\n/** The API's hard limit per request. */\nconst MAX_EVENTS_PER_REQUEST = 1000;\n\nconst SHA256 = /^[a-f0-9]{64}$/;\n\nconst sha256 = (value: string) => createHash('sha256').update(value).digest('hex');\n\n/**\n * Microsoft's email normalization: trim, drop the dots and any `+alias` from the user part —\n * for EVERY domain, unlike Google's gmail-only rule — lowercase, SHA-256. A value that is\n * already a SHA-256 hex digest passes through.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#hashed-identifiers\n */\nexport function normalizeEmail(input: string): string {\n const email = input.trim().toLowerCase();\n const at = email.lastIndexOf('@');\n if (at === -1) return email;\n const user = email.slice(0, at).replace(/\\./g, '').split('+', 1)[0] ?? '';\n return `${user}@${email.slice(at + 1)}`;\n}\n\nfunction hashEmail(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n return SHA256.test(value) ? value : sha256(normalizeEmail(value));\n}\n\n/**\n * Phones are E.164 by the schema (`+` then 11–15 digits, the range bat.js validates too); the\n * separators bat.js tolerates are stripped, and anything else is dropped rather than guessed —\n * a number with no country code hashed under an assumed one can never match.\n */\nfunction hashPhone(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n if (SHA256.test(value)) return value;\n const phone = value.replace(/[-() \\t]/g, '');\n return /^\\+\\d{11,15}$/.test(phone) ? sha256(phone) : undefined;\n}\n\nexport interface MicrosoftUserData {\n /** Microsoft click id, as a dashed UUID. */\n msclkid?: string;\n /** SHA-256 of the normalized email. */\n em?: string;\n /** SHA-256 of the E.164 phone number. */\n ph?: string;\n /** The SDK visitor id — what the client-side ID Sync pixel (`sendUETIdSync`) sends as `VID`. */\n anonymousId?: string;\n /** SHA-256 of the signed-in user id — what `sendUETIdSync` sends as `UID`. */\n externalId?: string;\n clientUserAgent?: string;\n clientIpAddress?: string;\n idfa?: string;\n gaid?: string;\n}\n\nexport interface MicrosoftItem {\n id?: string;\n name?: string;\n price?: number;\n quantity?: number;\n}\n\nexport interface MicrosoftCustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: MicrosoftItem[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n}\n\n/** One entry of the `data` array POSTed to `/v1/{tagId}/events`. */\nexport interface MicrosoftEvent {\n eventType: 'custom' | 'pageLoad';\n eventId: string;\n /** The UET action; absent on `pageLoad` events. */\n eventName?: string;\n /** UNIX seconds, UTC; the API rejects anything older than 7 days. */\n eventTime: number;\n eventSourceUrl?: string;\n /** Links a `custom` event to its `pageLoad` event; a v4 UUID (the SDK's `page_load_id` tag). */\n pageLoadId?: string;\n referrerUrl?: string;\n pageTitle?: string;\n /** `G` granted, `D` denied. Absent means granted. */\n adStorageConsent?: 'G' | 'D';\n userData: MicrosoftUserData;\n customData?: MicrosoftCustomData;\n}\n\nexport interface MicrosoftConversionsOptions {\n /**\n * EEA/UK/CH consent for the whole batch. `denied` events are accepted but used for nothing —\n * no attribution, no audiences. Absent means granted, which is also the API's default.\n */\n consent?: 'granted' | 'denied';\n /**\n * Also send `page_view` events, as CAPI `pageLoad` events, and link every custom event to the\n * page load it happened on (`pageLoadId`, from the `page_load_id` tag). Off by default because\n * the UET JavaScript already reports every page load and the two would double-count\n * destination-URL goals; turn it on for a CAPI-only site with no tag on the page. Off, no\n * `pageLoadId` is sent either: it would point at a page load Microsoft only saw from the tag,\n * under the tag's own id.\n *\n * Not yet modelled: the revenue-only custom event a `pageLoad` needs for a destination goal\n * with variable revenue.\n */\n pageLoads?: boolean;\n /** Identifies a third-party sender in Microsoft's monitoring; leave unset for a first-party build. */\n dataProvider?: string;\n /**\n * Process the valid events of a batch and report the invalid ones, instead of the API's default\n * all-or-nothing rejection. On by default — one malformed event should cost itself, not the\n * batch it travelled with.\n */\n continueOnValidationError?: boolean;\n}\n\nexport interface MicrosoftValidationDetail {\n index?: number;\n propertyName?: string;\n attemptedValue?: unknown;\n errorMessage?: string;\n errorCode?: string;\n isWarning?: boolean;\n}\n\nexport interface MicrosoftConversionsResponse {\n eventsReceived?: number;\n error?: { code?: string; message?: string; details?: MicrosoftValidationDetail[] };\n}\n\nfunction getUserData(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData\n): MicrosoftUserData {\n const { tags, platform, visitor_id } = event;\n const email = Array.isArray(data.email) ? data.email[0] : data.email;\n const phone = Array.isArray(data.phone_number) ? data.phone_number[0] : data.phone_number;\n\n return {\n msclkid: tags.msclkid ? formatMsclkid(tags.msclkid) : undefined,\n em: hashEmail(email),\n ph: hashPhone(phone),\n // Microsoft requires this to equal the `VID` of the client-side ID Sync pixel; both sides use\n // the SDK visitor id, which every event carries and the client can always read.\n anonymousId: visitor_id || undefined,\n // Microsoft asks for an anonymized id and never a real one. SHA-256, the same digest\n // `sendUETIdSync` applies client-side, so `externalId` and the pixel's `UID` agree too.\n externalId: data.user_id ? sha256(data.user_id) : undefined,\n clientUserAgent: data.user_agent,\n clientIpAddress:\n typeof tags.ip_address === 'string' && tags.ip_address ? tags.ip_address : data.ip_address,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n gaid: platform === 'android' ? tags.advertising_id : undefined,\n };\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData(event: TrackEvent<any>): MicrosoftCustomData | undefined {\n const [, params] = mapUETEvent(event.name, event.properties);\n const customData: MicrosoftCustomData = {\n eventCategory: params.event_category,\n eventLabel: params.event_label,\n eventValue: params.event_value,\n searchTerm: params.search_term,\n transactionId: params.transaction_id,\n value: params.revenue_value,\n currency: params.currency,\n items: params.items?.map(({ id, name, price, quantity }) => ({ id, name, price, quantity })),\n itemIds: Array.isArray(params.ecomm_prodid)\n ? params.ecomm_prodid\n : params.ecomm_prodid\n ? [params.ecomm_prodid]\n : undefined,\n pageType: params.ecomm_pagetype,\n ecommTotalValue: params.ecomm_totalvalue,\n ecommCategory: params.ecomm_category,\n };\n return Object.values(customData).some((value) => value !== undefined) ? customData : undefined;\n}\n\n/**\n * Build one CAPI event. `page_view` becomes a `pageLoad` event (the caller decides whether to\n * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event\n * named after the track event, which is also the UET action the browser sent.\n */\nexport type MicrosoftEventOptions = Pick<MicrosoftConversionsOptions, 'consent' | 'pageLoads'>;\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n /** Options, or — the 8.3.0 signature, still accepted — the bare consent value. */\n options: MicrosoftEventOptions | MicrosoftEventOptions['consent'] = {}\n): MicrosoftEvent {\n const { consent, pageLoads = false } =\n typeof options === 'string' ? { consent: options } : options;\n const { id, name, tags, created_at } = event;\n const isPageLoad = name === 'page_view';\n\n return {\n eventType: isPageLoad ? 'pageLoad' : 'custom',\n eventId: tags.idempotency_key ?? id,\n eventName: isPageLoad ? undefined : name,\n eventTime: Math.round(new Date(created_at).getTime() / 1000),\n eventSourceUrl: pageLocation(tags),\n pageLoadId: pageLoads ? tags.page_load_id : undefined,\n referrerUrl: tags.page_referrer,\n pageTitle: tags.page_title,\n adStorageConsent: consent === 'granted' ? 'G' : consent === 'denied' ? 'D' : undefined,\n userData: getUserData(event, data),\n customData: isPageLoad ? undefined : getCustomData(event),\n };\n}\n\n/** Strip undefined fields so the wire payload carries only what was set. */\nfunction compact<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\n/**\n * Send events to `POST /v1/{tagId}/events`. Batches above the API's 1,000-event limit are split\n * into consecutive requests; one response is returned per request made. Never throws: an HTTP\n * or network failure is logged (without the token) and yields no response for that request.\n */\nexport async function sendEvents(\n token: string,\n tagId: string | number,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n options: MicrosoftConversionsOptions = {}\n): Promise<MicrosoftConversionsResponse[]> {\n const { consent, pageLoads = false, dataProvider, continueOnValidationError = true } = options;\n\n const capiEvents = events\n .filter(\n (event) => (pageLoads && event.name === 'page_view') || !IGNORED_EVENTS.includes(event.name)\n )\n .map((event) => getServerEvent(event, data, { consent, pageLoads }))\n // A pageLoad without a URL is rejected by the API, and a page-less event has nothing to say.\n .filter((event) => event.eventType !== 'pageLoad' || event.eventSourceUrl);\n if (capiEvents.length === 0) return [];\n\n const responses: MicrosoftConversionsResponse[] = [];\n for (const batch of chunk(capiEvents, MAX_EVENTS_PER_REQUEST)) {\n const body = compact({ data: batch, continueOnValidationError, dataProvider });\n try {\n const response = await fetch(`${ENDPOINT}/${tagId}/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(body),\n });\n if (response.ok) {\n const result = (await response.json()) as MicrosoftConversionsResponse;\n // A 200 can still carry field-level warnings (the field was removed) and, with\n // `continueOnValidationError`, skipped events. Surface them: silent partial data loss is\n // the one failure mode the caller cannot see from the status code.\n if (result.error?.details?.length) {\n console.warn(\n `Microsoft conversion accepted with validation issues: ${JSON.stringify(result.error.details)}`\n );\n }\n responses.push(result);\n continue;\n }\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Microsoft conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Failed to send Microsoft conversion, network error: ${message}`);\n }\n }\n return responses;\n}\n"],"mappings":"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"}
@@ -52,6 +52,8 @@ interface MicrosoftEvent {
52
52
  /** UNIX seconds, UTC; the API rejects anything older than 7 days. */
53
53
  eventTime: number;
54
54
  eventSourceUrl?: string;
55
+ /** Links a `custom` event to its `pageLoad` event; a v4 UUID (the SDK's `page_load_id` tag). */
56
+ pageLoadId?: string;
55
57
  referrerUrl?: string;
56
58
  pageTitle?: string;
57
59
  /** `G` granted, `D` denied. Absent means granted. */
@@ -66,13 +68,15 @@ interface MicrosoftConversionsOptions {
66
68
  */
67
69
  consent?: 'granted' | 'denied';
68
70
  /**
69
- * Also send `page_view` events, as CAPI `pageLoad` events. Off by default because the UET
70
- * JavaScript already reports every page load and the two would double-count destination-URL
71
- * goals; turn it on for a CAPI-only site with no tag on the page.
71
+ * Also send `page_view` events, as CAPI `pageLoad` events, and link every custom event to the
72
+ * page load it happened on (`pageLoadId`, from the `page_load_id` tag). Off by default because
73
+ * the UET JavaScript already reports every page load and the two would double-count
74
+ * destination-URL goals; turn it on for a CAPI-only site with no tag on the page. Off, no
75
+ * `pageLoadId` is sent either: it would point at a page load Microsoft only saw from the tag,
76
+ * under the tag's own id.
72
77
  *
73
- * Not yet modelled: `pageLoadId`, which links custom events to the page load they happened on,
74
- * and the revenue-only custom event a `pageLoad` needs for a destination goal with variable
75
- * revenue. Both matter only in this CAPI-only mode.
78
+ * Not yet modelled: the revenue-only custom event a `pageLoad` needs for a destination goal
79
+ * with variable revenue.
76
80
  */
77
81
  pageLoads?: boolean;
78
82
  /** Identifies a third-party sender in Microsoft's monitoring; leave unset for a first-party build. */
@@ -105,7 +109,10 @@ interface MicrosoftConversionsResponse {
105
109
  * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event
106
110
  * named after the track event, which is also the UET action the browser sent.
107
111
  */
108
- declare function getServerEvent(event: TrackEvent<any>, data?: UserProvidedData, consent?: 'granted' | 'denied'): MicrosoftEvent;
112
+ type MicrosoftEventOptions = Pick<MicrosoftConversionsOptions, 'consent' | 'pageLoads'>;
113
+ declare function getServerEvent(event: TrackEvent<any>, data?: UserProvidedData,
114
+ /** Options, or — the 8.3.0 signature, still accepted — the bare consent value. */
115
+ options?: MicrosoftEventOptions | MicrosoftEventOptions['consent']): MicrosoftEvent;
109
116
  /**
110
117
  * Send events to `POST /v1/{tagId}/events`. Batches above the API's 1,000-event limit are split
111
118
  * into consecutive requests; one response is returned per request made. Never throws: an HTTP
@@ -113,5 +120,5 @@ declare function getServerEvent(event: TrackEvent<any>, data?: UserProvidedData,
113
120
  */
114
121
  declare function sendEvents(token: string, tagId: string | number, events: TrackEvent<any>[], data?: UserProvidedData, options?: MicrosoftConversionsOptions): Promise<MicrosoftConversionsResponse[]>;
115
122
  //#endregion
116
- export { MicrosoftConversionsOptions, MicrosoftConversionsResponse, MicrosoftCustomData, MicrosoftEvent, MicrosoftItem, MicrosoftUserData, MicrosoftValidationDetail, getServerEvent, normalizeEmail, sendEvents };
123
+ export { MicrosoftConversionsOptions, MicrosoftConversionsResponse, MicrosoftCustomData, MicrosoftEvent, MicrosoftEventOptions, MicrosoftItem, MicrosoftUserData, MicrosoftValidationDetail, getServerEvent, normalizeEmail, sendEvents };
117
124
  //# sourceMappingURL=microsoft-conversions-api.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-conversions-api.d.cts","names":[],"sources":["../../src/server/microsoft-conversions-api.ts"],"mappings":";;;;;;;;iBAwCgB,eAAe;UA2Bd;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;;;UAIe;EACf;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;EACA,UAAU;EACV,aAAa;;UAGE;;;;;EAKf;;;;;;;;;;EAUA;;EAEA;;;;;;EAMA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;IAAU;IAAe;IAAkB,UAAU;;;;;;;;iBA2DvC,eAEd,OAAO,iBACP,OAAM,kBACN,iCACC;;;;;;iBA4BmB,WACpB,eACA,wBAEA,QAAQ,mBACR,OAAM,kBACN,UAAS,8BACR,QAAQ"}
1
+ {"version":3,"file":"microsoft-conversions-api.d.cts","names":[],"sources":["../../src/server/microsoft-conversions-api.ts"],"mappings":";;;;;;;;iBAwCgB,eAAe;UA2Bd;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;;;UAIe;EACf;EACA;;EAEA;;EAEA;EACA;;EAEA;EACA;EACA;;EAEA;EACA,UAAU;EACV,aAAa;;UAGE;;;;;EAKf;;;;;;;;;;;;EAYA;;EAEA;;;;;;EAMA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;IAAU;IAAe;IAAkB,UAAU;;;;;;;;KA2D3C,wBAAwB,KAAK;iBAEzB,eAEd,OAAO,iBACP,OAAM,kBAEN;;AAAA,UAAS,wBAAwB,mCAChC;;;;;;iBA+BmB,WACpB,eACA,wBAEA,QAAQ,mBACR,OAAM,kBACN,UAAS,8BACR,QAAQ"}
@@ -52,6 +52,8 @@ interface MicrosoftEvent {
52
52
  /** UNIX seconds, UTC; the API rejects anything older than 7 days. */
53
53
  eventTime: number;
54
54
  eventSourceUrl?: string;
55
+ /** Links a `custom` event to its `pageLoad` event; a v4 UUID (the SDK's `page_load_id` tag). */
56
+ pageLoadId?: string;
55
57
  referrerUrl?: string;
56
58
  pageTitle?: string;
57
59
  /** `G` granted, `D` denied. Absent means granted. */
@@ -66,13 +68,15 @@ interface MicrosoftConversionsOptions {
66
68
  */
67
69
  consent?: 'granted' | 'denied';
68
70
  /**
69
- * Also send `page_view` events, as CAPI `pageLoad` events. Off by default because the UET
70
- * JavaScript already reports every page load and the two would double-count destination-URL
71
- * goals; turn it on for a CAPI-only site with no tag on the page.
71
+ * Also send `page_view` events, as CAPI `pageLoad` events, and link every custom event to the
72
+ * page load it happened on (`pageLoadId`, from the `page_load_id` tag). Off by default because
73
+ * the UET JavaScript already reports every page load and the two would double-count
74
+ * destination-URL goals; turn it on for a CAPI-only site with no tag on the page. Off, no
75
+ * `pageLoadId` is sent either: it would point at a page load Microsoft only saw from the tag,
76
+ * under the tag's own id.
72
77
  *
73
- * Not yet modelled: `pageLoadId`, which links custom events to the page load they happened on,
74
- * and the revenue-only custom event a `pageLoad` needs for a destination goal with variable
75
- * revenue. Both matter only in this CAPI-only mode.
78
+ * Not yet modelled: the revenue-only custom event a `pageLoad` needs for a destination goal
79
+ * with variable revenue.
76
80
  */
77
81
  pageLoads?: boolean;
78
82
  /** Identifies a third-party sender in Microsoft's monitoring; leave unset for a first-party build. */
@@ -105,7 +109,10 @@ interface MicrosoftConversionsResponse {
105
109
  * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event
106
110
  * named after the track event, which is also the UET action the browser sent.
107
111
  */
108
- declare function getServerEvent(event: TrackEvent<any>, data?: UserProvidedData, consent?: 'granted' | 'denied'): MicrosoftEvent;
112
+ type MicrosoftEventOptions = Pick<MicrosoftConversionsOptions, 'consent' | 'pageLoads'>;
113
+ declare function getServerEvent(event: TrackEvent<any>, data?: UserProvidedData,
114
+ /** Options, or — the 8.3.0 signature, still accepted — the bare consent value. */
115
+ options?: MicrosoftEventOptions | MicrosoftEventOptions['consent']): MicrosoftEvent;
109
116
  /**
110
117
  * Send events to `POST /v1/{tagId}/events`. Batches above the API's 1,000-event limit are split
111
118
  * into consecutive requests; one response is returned per request made. Never throws: an HTTP
@@ -113,5 +120,5 @@ declare function getServerEvent(event: TrackEvent<any>, data?: UserProvidedData,
113
120
  */
114
121
  declare function sendEvents(token: string, tagId: string | number, events: TrackEvent<any>[], data?: UserProvidedData, options?: MicrosoftConversionsOptions): Promise<MicrosoftConversionsResponse[]>;
115
122
  //#endregion
116
- export { MicrosoftConversionsOptions, MicrosoftConversionsResponse, MicrosoftCustomData, MicrosoftEvent, MicrosoftItem, MicrosoftUserData, MicrosoftValidationDetail, getServerEvent, normalizeEmail, sendEvents };
123
+ export { MicrosoftConversionsOptions, MicrosoftConversionsResponse, MicrosoftCustomData, MicrosoftEvent, MicrosoftEventOptions, MicrosoftItem, MicrosoftUserData, MicrosoftValidationDetail, getServerEvent, normalizeEmail, sendEvents };
117
124
  //# sourceMappingURL=microsoft-conversions-api.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-conversions-api.d.mts","names":[],"sources":["../../src/server/microsoft-conversions-api.ts"],"mappings":";;;;;;;;iBAwCgB,eAAe;UA2Bd;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;;;UAIe;EACf;EACA;;EAEA;;EAEA;EACA;EACA;EACA;;EAEA;EACA,UAAU;EACV,aAAa;;UAGE;;;;;EAKf;;;;;;;;;;EAUA;;EAEA;;;;;;EAMA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;IAAU;IAAe;IAAkB,UAAU;;;;;;;;iBA2DvC,eAEd,OAAO,iBACP,OAAM,kBACN,iCACC;;;;;;iBA4BmB,WACpB,eACA,wBAEA,QAAQ,mBACR,OAAM,kBACN,UAAS,8BACR,QAAQ"}
1
+ {"version":3,"file":"microsoft-conversions-api.d.mts","names":[],"sources":["../../src/server/microsoft-conversions-api.ts"],"mappings":";;;;;;;;iBAwCgB,eAAe;UA2Bd;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;;;UAIe;EACf;EACA;;EAEA;;EAEA;EACA;;EAEA;EACA;EACA;;EAEA;EACA,UAAU;EACV,aAAa;;UAGE;;;;;EAKf;;;;;;;;;;;;EAYA;;EAEA;;;;;;EAMA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;IAAU;IAAe;IAAkB,UAAU;;;;;;;;KA2D3C,wBAAwB,KAAK;iBAEzB,eAEd,OAAO,iBACP,OAAM,kBAEN;;AAAA,UAAS,wBAAwB,mCAChC;;;;;;iBA+BmB,WACpB,eACA,wBAEA,QAAQ,mBACR,OAAM,kBACN,UAAS,8BACR,QAAQ"}
@@ -95,12 +95,8 @@ function getCustomData(event) {
95
95
  };
96
96
  return Object.values(customData).some((value) => value !== void 0) ? customData : void 0;
97
97
  }
98
- /**
99
- * Build one CAPI event. `page_view` becomes a `pageLoad` event (the caller decides whether to
100
- * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event
101
- * named after the track event, which is also the UET action the browser sent.
102
- */
103
- function getServerEvent(event, data = {}, consent) {
98
+ function getServerEvent(event, data = {}, options = {}) {
99
+ const { consent, pageLoads = false } = typeof options === "string" ? { consent: options } : options;
104
100
  const { id, name, tags, created_at } = event;
105
101
  const isPageLoad = name === "page_view";
106
102
  return {
@@ -109,6 +105,7 @@ function getServerEvent(event, data = {}, consent) {
109
105
  eventName: isPageLoad ? void 0 : name,
110
106
  eventTime: Math.round(new Date(created_at).getTime() / 1e3),
111
107
  eventSourceUrl: pageLocation(tags),
108
+ pageLoadId: pageLoads ? tags.page_load_id : void 0,
112
109
  referrerUrl: tags.page_referrer,
113
110
  pageTitle: tags.page_title,
114
111
  adStorageConsent: consent === "granted" ? "G" : consent === "denied" ? "D" : void 0,
@@ -127,7 +124,10 @@ function compact(value) {
127
124
  */
128
125
  async function sendEvents(token, tagId, events, data = {}, options = {}) {
129
126
  const { consent, pageLoads = false, dataProvider, continueOnValidationError = true } = options;
130
- const capiEvents = events.filter((event) => pageLoads && event.name === "page_view" || !IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, consent)).filter((event) => event.eventType !== "pageLoad" || event.eventSourceUrl);
127
+ const capiEvents = events.filter((event) => pageLoads && event.name === "page_view" || !IGNORED_EVENTS.includes(event.name)).map((event) => getServerEvent(event, data, {
128
+ consent,
129
+ pageLoads
130
+ })).filter((event) => event.eventType !== "pageLoad" || event.eventSourceUrl);
131
131
  if (capiEvents.length === 0) return [];
132
132
  const responses = [];
133
133
  for (const batch of chunk(capiEvents, MAX_EVENTS_PER_REQUEST)) {
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-conversions-api.mjs","names":[],"sources":["../../src/server/microsoft-conversions-api.ts"],"sourcesContent":["/**\n * Microsoft Advertising Conversions API (CAPI) sender — the server-side counterpart of the UET\n * tag, in the same shape as the other senders here: a pure per-event builder, a never-throws\n * `sendEvents`, the token in a header and never in a URL or a log line.\n *\n * Microsoft's recommended deployment is hybrid: keep the UET JavaScript on the page and send the\n * same conversions here too, deduplicated by (`tagId`, `eventName`, `eventId`). Both channels in\n * this SDK are fed from the same `track()` call — the browser sends the internal name as the UET\n * action with the event id as `event_id`, and this builder sends the same name as `eventName`\n * with the same id — so the pair agrees by construction.\n *\n * The token comes from the UET tag's \"Use Conversions API\" setup step, or from the Campaign\n * Management API's `UetTagAuthKey/Query`. Access is provisioned per customer account: an account\n * whose tag setup has no \"Use Conversions API\" option has to ask Microsoft Advertising support.\n *\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration\n */\nimport { createHash } from 'node:crypto';\nimport { chunk, fetch } from '@shware/utils';\nimport { formatMsclkid } from '../click-id/index';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { mapUETEvent } from '../track/uetq';\nimport { pageLocation } from './page-location';\n\nconst ENDPOINT = 'https://capi.uet.microsoft.com/v1';\n\n/** The API's hard limit per request. */\nconst MAX_EVENTS_PER_REQUEST = 1000;\n\nconst SHA256 = /^[a-f0-9]{64}$/;\n\nconst sha256 = (value: string) => createHash('sha256').update(value).digest('hex');\n\n/**\n * Microsoft's email normalization: trim, drop the dots and any `+alias` from the user part —\n * for EVERY domain, unlike Google's gmail-only rule — lowercase, SHA-256. A value that is\n * already a SHA-256 hex digest passes through.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#hashed-identifiers\n */\nexport function normalizeEmail(input: string): string {\n const email = input.trim().toLowerCase();\n const at = email.lastIndexOf('@');\n if (at === -1) return email;\n const user = email.slice(0, at).replace(/\\./g, '').split('+', 1)[0] ?? '';\n return `${user}@${email.slice(at + 1)}`;\n}\n\nfunction hashEmail(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n return SHA256.test(value) ? value : sha256(normalizeEmail(value));\n}\n\n/**\n * Phones are E.164 by the schema (`+` then 11–15 digits, the range bat.js validates too); the\n * separators bat.js tolerates are stripped, and anything else is dropped rather than guessed —\n * a number with no country code hashed under an assumed one can never match.\n */\nfunction hashPhone(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n if (SHA256.test(value)) return value;\n const phone = value.replace(/[-() \\t]/g, '');\n return /^\\+\\d{11,15}$/.test(phone) ? sha256(phone) : undefined;\n}\n\nexport interface MicrosoftUserData {\n /** Microsoft click id, as a dashed UUID. */\n msclkid?: string;\n /** SHA-256 of the normalized email. */\n em?: string;\n /** SHA-256 of the E.164 phone number. */\n ph?: string;\n /** The SDK visitor id — what the client-side ID Sync pixel (`sendUETIdSync`) sends as `VID`. */\n anonymousId?: string;\n /** SHA-256 of the signed-in user id — what `sendUETIdSync` sends as `UID`. */\n externalId?: string;\n clientUserAgent?: string;\n clientIpAddress?: string;\n idfa?: string;\n gaid?: string;\n}\n\nexport interface MicrosoftItem {\n id?: string;\n name?: string;\n price?: number;\n quantity?: number;\n}\n\nexport interface MicrosoftCustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: MicrosoftItem[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n}\n\n/** One entry of the `data` array POSTed to `/v1/{tagId}/events`. */\nexport interface MicrosoftEvent {\n eventType: 'custom' | 'pageLoad';\n eventId: string;\n /** The UET action; absent on `pageLoad` events. */\n eventName?: string;\n /** UNIX seconds, UTC; the API rejects anything older than 7 days. */\n eventTime: number;\n eventSourceUrl?: string;\n referrerUrl?: string;\n pageTitle?: string;\n /** `G` granted, `D` denied. Absent means granted. */\n adStorageConsent?: 'G' | 'D';\n userData: MicrosoftUserData;\n customData?: MicrosoftCustomData;\n}\n\nexport interface MicrosoftConversionsOptions {\n /**\n * EEA/UK/CH consent for the whole batch. `denied` events are accepted but used for nothing —\n * no attribution, no audiences. Absent means granted, which is also the API's default.\n */\n consent?: 'granted' | 'denied';\n /**\n * Also send `page_view` events, as CAPI `pageLoad` events. Off by default because the UET\n * JavaScript already reports every page load and the two would double-count destination-URL\n * goals; turn it on for a CAPI-only site with no tag on the page.\n *\n * Not yet modelled: `pageLoadId`, which links custom events to the page load they happened on,\n * and the revenue-only custom event a `pageLoad` needs for a destination goal with variable\n * revenue. Both matter only in this CAPI-only mode.\n */\n pageLoads?: boolean;\n /** Identifies a third-party sender in Microsoft's monitoring; leave unset for a first-party build. */\n dataProvider?: string;\n /**\n * Process the valid events of a batch and report the invalid ones, instead of the API's default\n * all-or-nothing rejection. On by default — one malformed event should cost itself, not the\n * batch it travelled with.\n */\n continueOnValidationError?: boolean;\n}\n\nexport interface MicrosoftValidationDetail {\n index?: number;\n propertyName?: string;\n attemptedValue?: unknown;\n errorMessage?: string;\n errorCode?: string;\n isWarning?: boolean;\n}\n\nexport interface MicrosoftConversionsResponse {\n eventsReceived?: number;\n error?: { code?: string; message?: string; details?: MicrosoftValidationDetail[] };\n}\n\nfunction getUserData(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData\n): MicrosoftUserData {\n const { tags, platform, visitor_id } = event;\n const email = Array.isArray(data.email) ? data.email[0] : data.email;\n const phone = Array.isArray(data.phone_number) ? data.phone_number[0] : data.phone_number;\n\n return {\n msclkid: tags.msclkid ? formatMsclkid(tags.msclkid) : undefined,\n em: hashEmail(email),\n ph: hashPhone(phone),\n // Microsoft requires this to equal the `VID` of the client-side ID Sync pixel; both sides use\n // the SDK visitor id, which every event carries and the client can always read.\n anonymousId: visitor_id || undefined,\n // Microsoft asks for an anonymized id and never a real one. SHA-256, the same digest\n // `sendUETIdSync` applies client-side, so `externalId` and the pixel's `UID` agree too.\n externalId: data.user_id ? sha256(data.user_id) : undefined,\n clientUserAgent: data.user_agent,\n clientIpAddress:\n typeof tags.ip_address === 'string' && tags.ip_address ? tags.ip_address : data.ip_address,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n gaid: platform === 'android' ? tags.advertising_id : undefined,\n };\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData(event: TrackEvent<any>): MicrosoftCustomData | undefined {\n const [, params] = mapUETEvent(event.name, event.properties);\n const customData: MicrosoftCustomData = {\n eventCategory: params.event_category,\n eventLabel: params.event_label,\n eventValue: params.event_value,\n searchTerm: params.search_term,\n transactionId: params.transaction_id,\n value: params.revenue_value,\n currency: params.currency,\n items: params.items?.map(({ id, name, price, quantity }) => ({ id, name, price, quantity })),\n itemIds: Array.isArray(params.ecomm_prodid)\n ? params.ecomm_prodid\n : params.ecomm_prodid\n ? [params.ecomm_prodid]\n : undefined,\n pageType: params.ecomm_pagetype,\n ecommTotalValue: params.ecomm_totalvalue,\n ecommCategory: params.ecomm_category,\n };\n return Object.values(customData).some((value) => value !== undefined) ? customData : undefined;\n}\n\n/**\n * Build one CAPI event. `page_view` becomes a `pageLoad` event (the caller decides whether to\n * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event\n * named after the track event, which is also the UET action the browser sent.\n */\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n consent?: 'granted' | 'denied'\n): MicrosoftEvent {\n const { id, name, tags, created_at } = event;\n const isPageLoad = name === 'page_view';\n\n return {\n eventType: isPageLoad ? 'pageLoad' : 'custom',\n eventId: tags.idempotency_key ?? id,\n eventName: isPageLoad ? undefined : name,\n eventTime: Math.round(new Date(created_at).getTime() / 1000),\n eventSourceUrl: pageLocation(tags),\n referrerUrl: tags.page_referrer,\n pageTitle: tags.page_title,\n adStorageConsent: consent === 'granted' ? 'G' : consent === 'denied' ? 'D' : undefined,\n userData: getUserData(event, data),\n customData: isPageLoad ? undefined : getCustomData(event),\n };\n}\n\n/** Strip undefined fields so the wire payload carries only what was set. */\nfunction compact<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\n/**\n * Send events to `POST /v1/{tagId}/events`. Batches above the API's 1,000-event limit are split\n * into consecutive requests; one response is returned per request made. Never throws: an HTTP\n * or network failure is logged (without the token) and yields no response for that request.\n */\nexport async function sendEvents(\n token: string,\n tagId: string | number,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n options: MicrosoftConversionsOptions = {}\n): Promise<MicrosoftConversionsResponse[]> {\n const { consent, pageLoads = false, dataProvider, continueOnValidationError = true } = options;\n\n const capiEvents = events\n .filter(\n (event) => (pageLoads && event.name === 'page_view') || !IGNORED_EVENTS.includes(event.name)\n )\n .map((event) => getServerEvent(event, data, consent))\n // A pageLoad without a URL is rejected by the API, and a page-less event has nothing to say.\n .filter((event) => event.eventType !== 'pageLoad' || event.eventSourceUrl);\n if (capiEvents.length === 0) return [];\n\n const responses: MicrosoftConversionsResponse[] = [];\n for (const batch of chunk(capiEvents, MAX_EVENTS_PER_REQUEST)) {\n const body = compact({ data: batch, continueOnValidationError, dataProvider });\n try {\n const response = await fetch(`${ENDPOINT}/${tagId}/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(body),\n });\n if (response.ok) {\n const result = (await response.json()) as MicrosoftConversionsResponse;\n // A 200 can still carry field-level warnings (the field was removed) and, with\n // `continueOnValidationError`, skipped events. Surface them: silent partial data loss is\n // the one failure mode the caller cannot see from the status code.\n if (result.error?.details?.length) {\n console.warn(\n `Microsoft conversion accepted with validation issues: ${JSON.stringify(result.error.details)}`\n );\n }\n responses.push(result);\n continue;\n }\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Microsoft conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Failed to send Microsoft conversion, network error: ${message}`);\n }\n }\n return responses;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"microsoft-conversions-api.mjs","names":[],"sources":["../../src/server/microsoft-conversions-api.ts"],"sourcesContent":["/**\n * Microsoft Advertising Conversions API (CAPI) sender — the server-side counterpart of the UET\n * tag, in the same shape as the other senders here: a pure per-event builder, a never-throws\n * `sendEvents`, the token in a header and never in a URL or a log line.\n *\n * Microsoft's recommended deployment is hybrid: keep the UET JavaScript on the page and send the\n * same conversions here too, deduplicated by (`tagId`, `eventName`, `eventId`). Both channels in\n * this SDK are fed from the same `track()` call — the browser sends the internal name as the UET\n * action with the event id as `event_id`, and this builder sends the same name as `eventName`\n * with the same id — so the pair agrees by construction.\n *\n * The token comes from the UET tag's \"Use Conversions API\" setup step, or from the Campaign\n * Management API's `UetTagAuthKey/Query`. Access is provisioned per customer account: an account\n * whose tag setup has no \"Use Conversions API\" option has to ask Microsoft Advertising support.\n *\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration\n */\nimport { createHash } from 'node:crypto';\nimport { chunk, fetch } from '@shware/utils';\nimport { formatMsclkid } from '../click-id/index';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport type { TrackEvent, UserProvidedData } from '../track/types';\nimport { mapUETEvent } from '../track/uetq';\nimport { pageLocation } from './page-location';\n\nconst ENDPOINT = 'https://capi.uet.microsoft.com/v1';\n\n/** The API's hard limit per request. */\nconst MAX_EVENTS_PER_REQUEST = 1000;\n\nconst SHA256 = /^[a-f0-9]{64}$/;\n\nconst sha256 = (value: string) => createHash('sha256').update(value).digest('hex');\n\n/**\n * Microsoft's email normalization: trim, drop the dots and any `+alias` from the user part —\n * for EVERY domain, unlike Google's gmail-only rule — lowercase, SHA-256. A value that is\n * already a SHA-256 hex digest passes through.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#hashed-identifiers\n */\nexport function normalizeEmail(input: string): string {\n const email = input.trim().toLowerCase();\n const at = email.lastIndexOf('@');\n if (at === -1) return email;\n const user = email.slice(0, at).replace(/\\./g, '').split('+', 1)[0] ?? '';\n return `${user}@${email.slice(at + 1)}`;\n}\n\nfunction hashEmail(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n return SHA256.test(value) ? value : sha256(normalizeEmail(value));\n}\n\n/**\n * Phones are E.164 by the schema (`+` then 11–15 digits, the range bat.js validates too); the\n * separators bat.js tolerates are stripped, and anything else is dropped rather than guessed —\n * a number with no country code hashed under an assumed one can never match.\n */\nfunction hashPhone(input: string | undefined): string | undefined {\n const value = input?.trim().toLowerCase();\n if (!value) return undefined;\n if (SHA256.test(value)) return value;\n const phone = value.replace(/[-() \\t]/g, '');\n return /^\\+\\d{11,15}$/.test(phone) ? sha256(phone) : undefined;\n}\n\nexport interface MicrosoftUserData {\n /** Microsoft click id, as a dashed UUID. */\n msclkid?: string;\n /** SHA-256 of the normalized email. */\n em?: string;\n /** SHA-256 of the E.164 phone number. */\n ph?: string;\n /** The SDK visitor id — what the client-side ID Sync pixel (`sendUETIdSync`) sends as `VID`. */\n anonymousId?: string;\n /** SHA-256 of the signed-in user id — what `sendUETIdSync` sends as `UID`. */\n externalId?: string;\n clientUserAgent?: string;\n clientIpAddress?: string;\n idfa?: string;\n gaid?: string;\n}\n\nexport interface MicrosoftItem {\n id?: string;\n name?: string;\n price?: number;\n quantity?: number;\n}\n\nexport interface MicrosoftCustomData {\n eventCategory?: string;\n eventLabel?: string;\n eventValue?: number;\n searchTerm?: string;\n transactionId?: string;\n value?: number;\n currency?: string;\n items?: MicrosoftItem[];\n itemIds?: string[];\n pageType?: string;\n ecommTotalValue?: number;\n ecommCategory?: string;\n}\n\n/** One entry of the `data` array POSTed to `/v1/{tagId}/events`. */\nexport interface MicrosoftEvent {\n eventType: 'custom' | 'pageLoad';\n eventId: string;\n /** The UET action; absent on `pageLoad` events. */\n eventName?: string;\n /** UNIX seconds, UTC; the API rejects anything older than 7 days. */\n eventTime: number;\n eventSourceUrl?: string;\n /** Links a `custom` event to its `pageLoad` event; a v4 UUID (the SDK's `page_load_id` tag). */\n pageLoadId?: string;\n referrerUrl?: string;\n pageTitle?: string;\n /** `G` granted, `D` denied. Absent means granted. */\n adStorageConsent?: 'G' | 'D';\n userData: MicrosoftUserData;\n customData?: MicrosoftCustomData;\n}\n\nexport interface MicrosoftConversionsOptions {\n /**\n * EEA/UK/CH consent for the whole batch. `denied` events are accepted but used for nothing —\n * no attribution, no audiences. Absent means granted, which is also the API's default.\n */\n consent?: 'granted' | 'denied';\n /**\n * Also send `page_view` events, as CAPI `pageLoad` events, and link every custom event to the\n * page load it happened on (`pageLoadId`, from the `page_load_id` tag). Off by default because\n * the UET JavaScript already reports every page load and the two would double-count\n * destination-URL goals; turn it on for a CAPI-only site with no tag on the page. Off, no\n * `pageLoadId` is sent either: it would point at a page load Microsoft only saw from the tag,\n * under the tag's own id.\n *\n * Not yet modelled: the revenue-only custom event a `pageLoad` needs for a destination goal\n * with variable revenue.\n */\n pageLoads?: boolean;\n /** Identifies a third-party sender in Microsoft's monitoring; leave unset for a first-party build. */\n dataProvider?: string;\n /**\n * Process the valid events of a batch and report the invalid ones, instead of the API's default\n * all-or-nothing rejection. On by default — one malformed event should cost itself, not the\n * batch it travelled with.\n */\n continueOnValidationError?: boolean;\n}\n\nexport interface MicrosoftValidationDetail {\n index?: number;\n propertyName?: string;\n attemptedValue?: unknown;\n errorMessage?: string;\n errorCode?: string;\n isWarning?: boolean;\n}\n\nexport interface MicrosoftConversionsResponse {\n eventsReceived?: number;\n error?: { code?: string; message?: string; details?: MicrosoftValidationDetail[] };\n}\n\nfunction getUserData(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData\n): MicrosoftUserData {\n const { tags, platform, visitor_id } = event;\n const email = Array.isArray(data.email) ? data.email[0] : data.email;\n const phone = Array.isArray(data.phone_number) ? data.phone_number[0] : data.phone_number;\n\n return {\n msclkid: tags.msclkid ? formatMsclkid(tags.msclkid) : undefined,\n em: hashEmail(email),\n ph: hashPhone(phone),\n // Microsoft requires this to equal the `VID` of the client-side ID Sync pixel; both sides use\n // the SDK visitor id, which every event carries and the client can always read.\n anonymousId: visitor_id || undefined,\n // Microsoft asks for an anonymized id and never a real one. SHA-256, the same digest\n // `sendUETIdSync` applies client-side, so `externalId` and the pixel's `UID` agree too.\n externalId: data.user_id ? sha256(data.user_id) : undefined,\n clientUserAgent: data.user_agent,\n clientIpAddress:\n typeof tags.ip_address === 'string' && tags.ip_address ? tags.ip_address : data.ip_address,\n idfa: platform === 'ios' ? tags.advertising_id : undefined,\n gaid: platform === 'android' ? tags.advertising_id : undefined,\n };\n}\n\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction getCustomData(event: TrackEvent<any>): MicrosoftCustomData | undefined {\n const [, params] = mapUETEvent(event.name, event.properties);\n const customData: MicrosoftCustomData = {\n eventCategory: params.event_category,\n eventLabel: params.event_label,\n eventValue: params.event_value,\n searchTerm: params.search_term,\n transactionId: params.transaction_id,\n value: params.revenue_value,\n currency: params.currency,\n items: params.items?.map(({ id, name, price, quantity }) => ({ id, name, price, quantity })),\n itemIds: Array.isArray(params.ecomm_prodid)\n ? params.ecomm_prodid\n : params.ecomm_prodid\n ? [params.ecomm_prodid]\n : undefined,\n pageType: params.ecomm_pagetype,\n ecommTotalValue: params.ecomm_totalvalue,\n ecommCategory: params.ecomm_category,\n };\n return Object.values(customData).some((value) => value !== undefined) ? customData : undefined;\n}\n\n/**\n * Build one CAPI event. `page_view` becomes a `pageLoad` event (the caller decides whether to\n * send those, see `MicrosoftConversionsOptions.pageLoads`); everything else is a `custom` event\n * named after the track event, which is also the UET action the browser sent.\n */\nexport type MicrosoftEventOptions = Pick<MicrosoftConversionsOptions, 'consent' | 'pageLoads'>;\n\nexport function getServerEvent(\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n event: TrackEvent<any>,\n data: UserProvidedData = {},\n /** Options, or — the 8.3.0 signature, still accepted — the bare consent value. */\n options: MicrosoftEventOptions | MicrosoftEventOptions['consent'] = {}\n): MicrosoftEvent {\n const { consent, pageLoads = false } =\n typeof options === 'string' ? { consent: options } : options;\n const { id, name, tags, created_at } = event;\n const isPageLoad = name === 'page_view';\n\n return {\n eventType: isPageLoad ? 'pageLoad' : 'custom',\n eventId: tags.idempotency_key ?? id,\n eventName: isPageLoad ? undefined : name,\n eventTime: Math.round(new Date(created_at).getTime() / 1000),\n eventSourceUrl: pageLocation(tags),\n pageLoadId: pageLoads ? tags.page_load_id : undefined,\n referrerUrl: tags.page_referrer,\n pageTitle: tags.page_title,\n adStorageConsent: consent === 'granted' ? 'G' : consent === 'denied' ? 'D' : undefined,\n userData: getUserData(event, data),\n customData: isPageLoad ? undefined : getCustomData(event),\n };\n}\n\n/** Strip undefined fields so the wire payload carries only what was set. */\nfunction compact<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T;\n}\n\n/**\n * Send events to `POST /v1/{tagId}/events`. Batches above the API's 1,000-event limit are split\n * into consecutive requests; one response is returned per request made. Never throws: an HTTP\n * or network failure is logged (without the token) and yields no response for that request.\n */\nexport async function sendEvents(\n token: string,\n tagId: string | number,\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n events: TrackEvent<any>[],\n data: UserProvidedData = {},\n options: MicrosoftConversionsOptions = {}\n): Promise<MicrosoftConversionsResponse[]> {\n const { consent, pageLoads = false, dataProvider, continueOnValidationError = true } = options;\n\n const capiEvents = events\n .filter(\n (event) => (pageLoads && event.name === 'page_view') || !IGNORED_EVENTS.includes(event.name)\n )\n .map((event) => getServerEvent(event, data, { consent, pageLoads }))\n // A pageLoad without a URL is rejected by the API, and a page-less event has nothing to say.\n .filter((event) => event.eventType !== 'pageLoad' || event.eventSourceUrl);\n if (capiEvents.length === 0) return [];\n\n const responses: MicrosoftConversionsResponse[] = [];\n for (const batch of chunk(capiEvents, MAX_EVENTS_PER_REQUEST)) {\n const body = compact({ data: batch, continueOnValidationError, dataProvider });\n try {\n const response = await fetch(`${ENDPOINT}/${tagId}/events`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(body),\n });\n if (response.ok) {\n const result = (await response.json()) as MicrosoftConversionsResponse;\n // A 200 can still carry field-level warnings (the field was removed) and, with\n // `continueOnValidationError`, skipped events. Surface them: silent partial data loss is\n // the one failure mode the caller cannot see from the status code.\n if (result.error?.details?.length) {\n console.warn(\n `Microsoft conversion accepted with validation issues: ${JSON.stringify(result.error.details)}`\n );\n }\n responses.push(result);\n continue;\n }\n const { status } = response;\n const message = await response.text();\n console.error(`Failed to send Microsoft conversion, status: ${status}, body: ${message}`);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`Failed to send Microsoft conversion, network error: ${message}`);\n }\n }\n return responses;\n}\n"],"mappings":"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"}
@@ -6,9 +6,9 @@ const require_hooks_use_web_analytics = require("../hooks/use-web-analytics.cjs"
6
6
  let react_jsx_runtime = require("react/jsx-runtime");
7
7
  let _tanstack_react_router = require("@tanstack/react-router");
8
8
  //#region src/tanstack/analytics.tsx
9
- function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, hotjarId, facebookAppId, reportWebVitals = true }) {
10
- const { pathname } = (0, _tanstack_react_router.useLocation)();
11
- require_hooks_use_web_analytics.useWebAnalytics(pathname);
9
+ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, hotjarId, facebookAppId, reportWebVitals = true }) {
10
+ const { pathname, searchStr } = (0, _tanstack_react_router.useLocation)();
11
+ require_hooks_use_web_analytics.useWebAnalytics(pathname, searchStr);
12
12
  require_hooks_use_outbound_click_analytics.useOutboundClickAnalytics();
13
13
  require_hooks_use_report_web_vitals.useReportWebVitals((metric) => {
14
14
  if (!reportWebVitals) return;
@@ -130,6 +130,28 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId,
130
130
  b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js";
131
131
  s.parentNode.insertBefore(b, s);
132
132
  })(window.lintrk);
133
+ ` }
134
+ }),
135
+ uetTagId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("script", {
136
+ async: true,
137
+ id: "uet-tag",
138
+ dangerouslySetInnerHTML: { __html: `
139
+ (function (w, d, t, u, o) {
140
+ w[u] = w[u] || [], o.ts = (new Date).getTime();
141
+ var n = d.createElement(t);
142
+ n.src = "https://bat.bing.net/bat.js?ti=" + o.ti + ("uetq" != u ? "&q=" + u : ""),
143
+ n.async = 1, n.onload = n.onreadystatechange = function() {
144
+ var s = this.readyState;
145
+ s && "loaded" !== s && "complete" !== s ||
146
+ (o.q = w[u], w[u] = new UET(o), w[u].push("pageLoad"),
147
+ n.onload = n.onreadystatechange = null)
148
+ };
149
+ var i = d.getElementsByTagName(t)[0];
150
+ i.parentNode.insertBefore(n, i);
151
+ })(window, document, "script", "uetq", {
152
+ ti: "${uetTagId}",
153
+ enableAutoSpaTracking: true
154
+ });
133
155
  ` }
134
156
  }),
135
157
  hotjarId && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("script", {
@@ -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 { 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 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 hotjarId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const { pathname } = useLocation();\n useWebAnalytics(pathname);\n useOutboundClickAnalytics();\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 {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":";;;;;;;;AAwBA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,kBAAkB,QACV;CACR,MAAM,EAAE,cAAA,GAAaA,uBAAAA,YAAAA,CAAY;CACjC,gCAAA,gBAAgB,QAAQ;CACxB,2CAAA,0BAA0B;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;;;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 { 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 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 hotjarId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const { pathname, searchStr } = useLocation();\n useWebAnalytics(pathname, searchStr);\n useOutboundClickAnalytics();\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":";;;;;;;;AA0BA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,UACA,eACA,kBAAkB,QACV;CACR,MAAM,EAAE,UAAU,eAAA,GAAcA,uBAAAA,YAAAA,CAAY;CAC5C,gCAAA,gBAAgB,UAAU,SAAS;CACnC,2CAAA,0BAA0B;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"}
@@ -10,13 +10,15 @@ interface Props {
10
10
  openaiPixelId?: string;
11
11
  redditPixelId?: PixelId$1;
12
12
  linkedInPartnerId?: `${number}`;
13
+ /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */
14
+ uetTagId?: `${number}`;
13
15
  hotjarId?: `${number}`;
14
16
  facebookAppId?: string;
15
17
  nonce?: string;
16
18
  debugMode?: boolean;
17
19
  reportWebVitals?: boolean;
18
20
  }
19
- declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, hotjarId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
21
+ declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, hotjarId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
20
22
  //#endregion
21
23
  export { Analytics };
22
24
  //# sourceMappingURL=analytics.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.cts","names":[],"sources":["../../src/tanstack/analytics.tsx"],"mappings":";;;;UASU;EACR,OAAO;EACP;EACA,QAAQ;EACR,cAAc;EACd;EACA,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA;;iBAGc,YACd,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,mBACC,wBAAK,IAAA"}
1
+ {"version":3,"file":"analytics.d.cts","names":[],"sources":["../../src/tanstack/analytics.tsx"],"mappings":";;;;UASU;EACR,OAAO;EACP;EACA,QAAQ;EACR,cAAc;EACd;EACA,gBAAgB;EAChB;;EAEA;EACA;EACA;EACA;EACA;EACA;;iBAGc,YACd,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,UACA,eACA,mBACC,wBAAK,IAAA"}
@@ -10,13 +10,15 @@ interface Props {
10
10
  openaiPixelId?: string;
11
11
  redditPixelId?: PixelId$1;
12
12
  linkedInPartnerId?: `${number}`;
13
+ /** Microsoft Advertising UET tag id. The tag reports page loads itself (`enableAutoSpaTracking`). */
14
+ uetTagId?: `${number}`;
13
15
  hotjarId?: `${number}`;
14
16
  facebookAppId?: string;
15
17
  nonce?: string;
16
18
  debugMode?: boolean;
17
19
  reportWebVitals?: boolean;
18
20
  }
19
- declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, hotjarId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
21
+ declare function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, hotjarId, facebookAppId, reportWebVitals }: Props): import("react").JSX.Element;
20
22
  //#endregion
21
23
  export { Analytics };
22
24
  //# sourceMappingURL=analytics.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.mts","names":[],"sources":["../../src/tanstack/analytics.tsx"],"mappings":";;;;UASU;EACR,OAAO;EACP;EACA,QAAQ;EACR,cAAc;EACd;EACA,gBAAgB;EAChB;EACA;EACA;EACA;EACA;EACA;;iBAGc,YACd,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,mBACC,wBAAK,IAAA"}
1
+ {"version":3,"file":"analytics.d.mts","names":[],"sources":["../../src/tanstack/analytics.tsx"],"mappings":";;;;UASU;EACR,OAAO;EACP;EACA,QAAQ;EACR,cAAc;EACd;EACA,gBAAgB;EAChB;;EAEA;EACA;EACA;EACA;EACA;EACA;;iBAGc,YACd,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,UACA,eACA,mBACC,wBAAK,IAAA"}
@@ -5,9 +5,9 @@ import { useWebAnalytics } from "../hooks/use-web-analytics.mjs";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
6
  import { useLocation } from "@tanstack/react-router";
7
7
  //#region src/tanstack/analytics.tsx
8
- function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, hotjarId, facebookAppId, reportWebVitals = true }) {
9
- const { pathname } = useLocation();
10
- useWebAnalytics(pathname);
8
+ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId, redditPixelId, linkedInPartnerId, uetTagId, hotjarId, facebookAppId, reportWebVitals = true }) {
9
+ const { pathname, searchStr } = useLocation();
10
+ useWebAnalytics(pathname, searchStr);
11
11
  useOutboundClickAnalytics();
12
12
  useReportWebVitals((metric) => {
13
13
  if (!reportWebVitals) return;
@@ -129,6 +129,28 @@ function Analytics({ gaId, gaSrc, nonce, debugMode, metaPixelId, openaiPixelId,
129
129
  b.src = "https://snap.licdn.com/li.lms-analytics/insight.min.js";
130
130
  s.parentNode.insertBefore(b, s);
131
131
  })(window.lintrk);
132
+ ` }
133
+ }),
134
+ uetTagId && /* @__PURE__ */ jsx("script", {
135
+ async: true,
136
+ id: "uet-tag",
137
+ dangerouslySetInnerHTML: { __html: `
138
+ (function (w, d, t, u, o) {
139
+ w[u] = w[u] || [], o.ts = (new Date).getTime();
140
+ var n = d.createElement(t);
141
+ n.src = "https://bat.bing.net/bat.js?ti=" + o.ti + ("uetq" != u ? "&q=" + u : ""),
142
+ n.async = 1, n.onload = n.onreadystatechange = function() {
143
+ var s = this.readyState;
144
+ s && "loaded" !== s && "complete" !== s ||
145
+ (o.q = w[u], w[u] = new UET(o), w[u].push("pageLoad"),
146
+ n.onload = n.onreadystatechange = null)
147
+ };
148
+ var i = d.getElementsByTagName(t)[0];
149
+ i.parentNode.insertBefore(n, i);
150
+ })(window, document, "script", "uetq", {
151
+ ti: "${uetTagId}",
152
+ enableAutoSpaTracking: true
153
+ });
132
154
  ` }
133
155
  }),
134
156
  hotjarId && /* @__PURE__ */ jsx("script", {
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.mjs","names":[],"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 { 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 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 hotjarId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const { pathname } = useLocation();\n useWebAnalytics(pathname);\n useOutboundClickAnalytics();\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 {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":";;;;;;;AAwBA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,eACA,kBAAkB,QACV;CACR,MAAM,EAAE,aAAa,YAAY;CACjC,gBAAgB,QAAQ;CACxB,0BAA0B;CAE1B,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,MAAM,OAAO,MAAM,UAAU;CAC/B,CAAC;CAED,OACE,qBAAA,UAAA,EAAA,UAAA;EACG,iBAAiB,oBAAC,QAAD;GAAM,UAAS;GAAY,SAAS;EAAgB,CAAA;EACrE,QACC,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACI;GACP,KAAK,SAAS,+CAA+C;EAC9D,CAAA,GACD,oBAAC,UAAD;GACE,OAAA;GACO;GACP,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;gCAIU,KAAK,GAAG,YAAY,6BAA6B,GAAG;cAExE;EACD,CAAA,CACD,EAAA,CAAA;EAEH,eACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;;;2BAiBO,YAAY;uCAE7B;EACD,CAAA;EAEF,iBACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;2CAeuB,cAAc;;cAG/C;EACD,CAAA;EAEF,iBACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;2BAcO,cAAc;wCAE/B;EACD,CAAA;EAEF,qBACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;sCACkB,kBAAkB;;;;;;;;;;;;;;;;;cAkB9C;EACD,CAAA;EAEF,YACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;oCAGgB,SAAS;;;;;;cAOnC;EACD,CAAA;CAEH,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"analytics.mjs","names":[],"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 { 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 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 hotjarId,\n facebookAppId,\n reportWebVitals = true,\n}: Props) {\n const { pathname, searchStr } = useLocation();\n useWebAnalytics(pathname, searchStr);\n useOutboundClickAnalytics();\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":";;;;;;;AA0BA,SAAgB,UAAU,EACxB,MACA,OACA,OACA,WACA,aACA,eACA,eACA,mBACA,UACA,UACA,eACA,kBAAkB,QACV;CACR,MAAM,EAAE,UAAU,cAAc,YAAY;CAC5C,gBAAgB,UAAU,SAAS;CACnC,0BAA0B;CAE1B,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,MAAM,OAAO,MAAM,UAAU;CAC/B,CAAC;CAED,OACE,qBAAA,UAAA,EAAA,UAAA;EACG,iBAAiB,oBAAC,QAAD;GAAM,UAAS;GAAY,SAAS;EAAgB,CAAA;EACrE,QACC,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACI;GACP,KAAK,SAAS,+CAA+C;EAC9D,CAAA,GACD,oBAAC,UAAD;GACE,OAAA;GACO;GACP,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;gCAIU,KAAK,GAAG,YAAY,6BAA6B,GAAG;cAExE;EACD,CAAA,CACD,EAAA,CAAA;EAEH,eACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;;;2BAiBO,YAAY;uCAE7B;EACD,CAAA;EAEF,iBACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;;2CAeuB,cAAc;;cAG/C;EACD,CAAA;EAEF,iBACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;2BAcO,cAAc;wCAE/B;EACD,CAAA;EAEF,qBACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;sCACkB,kBAAkB;;;;;;;;;;;;;;;;;cAkB9C;EACD,CAAA;EAEF,YACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;;;;;;;;;;;;uBAcG,SAAS;;;cAItB;EACD,CAAA;EAEF,YACC,oBAAC,UAAD;GACE,OAAA;GACA,IAAG;GACH,yBAAyB,EACvB,QAAQ;;;oCAGgB,SAAS;;;;;;cAOnC;EACD,CAAA;CAEH,EAAA,CAAA;AAEN"}
@@ -108,6 +108,13 @@ interface PageInfo {
108
108
  page_location?: string;
109
109
  page_referrer?: string;
110
110
  page_title?: string;
111
+ /**
112
+ * Identifies one page load: every event of a page carries the same id, and a navigation — a
113
+ * reload, or a single-page-app route change, the same moments a `page_view` is sent — starts
114
+ * a new one. Lets a server-side sender tie a conversion to the page load it happened on
115
+ * (Microsoft's `pageLoadId`). Web only.
116
+ */
117
+ page_load_id?: string;
111
118
  /**
112
119
  * @deprecated Renamed to `page_location` in 7.0.0, and never set by this SDK any more.
113
120
  * Declared so events from clients still on an older version keep their page URL through
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.cts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+Ba;;;;;;;KAQZ,oBAAoB,KAAK,gBAAgB,wBAC1C,sBAAsB;KAGd,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KAAK,oBAAoB,KACxC,gBAAgB,wBACd,sBAAsB,KACtB,OAAO,mBAAmB;KAEpB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;;;;;EAMA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+Ba;;;;;;;KAQZ,oBAAoB,KAAK,gBAAgB,wBAC1C,sBAAsB;KAGd,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KAAK,oBAAoB,KACxC,gBAAgB,wBACd,sBAAsB,KACtB,OAAO,mBAAmB;KAEpB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;;;;;;EAOA;;;;;;EAMA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
@@ -108,6 +108,13 @@ interface PageInfo {
108
108
  page_location?: string;
109
109
  page_referrer?: string;
110
110
  page_title?: string;
111
+ /**
112
+ * Identifies one page load: every event of a page carries the same id, and a navigation — a
113
+ * reload, or a single-page-app route change, the same moments a `page_view` is sent — starts
114
+ * a new one. Lets a server-side sender tie a conversion to the page load it happened on
115
+ * (Microsoft's `pageLoadId`). Web only.
116
+ */
117
+ page_load_id?: string;
111
118
  /**
112
119
  * @deprecated Renamed to `page_location` in 7.0.0, and never set by this SDK any more.
113
120
  * Declared so events from clients still on an older version keep their page URL through
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+Ba;;;;;;;KAQZ,oBAAoB,KAAK,gBAAgB,wBAC1C,sBAAsB;KAGd,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KAAK,oBAAoB,KACxC,gBAAgB,wBACd,sBAAsB,KACtB,OAAO,mBAAmB;KAEpB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;;;;;EAMA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../src/track/types.ts"],"mappings":";;KAEY;KACA,YAAY;KAEZ,UAAU,UAAU,YAAY,aAAa,gBAAgB,iBACrE,IACA;;;;;;;;;;;;;;;;;;;;;;;;;;;UA+Ba;;;;;;;KAQZ,oBAAoB,KAAK,gBAAgB,wBAC1C,sBAAsB;KAGd,gBAAgB,UAAU,YAAY,aAAa,gBAAgB,iBAC3E,eAAe,KAAK,oBAAoB,KACxC,gBAAgB,wBACd,sBAAsB,KACtB,OAAO,mBAAmB;KAEpB;KACA;UAEK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe,yBAAyB;EACxC;EACA;EACA;EACA;EACA;IAAa;IAAc;IAAe;;EAE1C;EACA;;KAGU,qBAAqB,UAAU,WACzC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;UAGe;;EAEf;EACA;;UAGe;EACf;EACA;EACA;;;;;;;;;;UAWe;EACf;EACA;EACA;;;;;;;EAOA;;;;;;EAMA;;UAGe;;;;;;;EAOf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;EACA;EACA;EACA;;EAEA;EACA;;EAEA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;;EAEf;;;;;;;;;;;;EAqCA;;;;;;EAuDA;;EAMA;;EAEA;;EAEA;;EAEA;;EAUA;;EAEA;;UAGe,kBACP,cAAc,YAAY,SAAS,iBAAiB,UAAU,iBAAiB;EACvF;GACC;;UAGc,WAAW,UAAU,YAAY;EAChD;EACA,MAAM,UAAU;EAChB,MAAM;EACN;EACA;EACA,UAAU;EACV,aAAa;EACb,aAAa,gBAAgB;EAC7B;;KAGU;;EAEV"}