@shware/analytics 8.5.0 → 8.7.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 (66) hide show
  1. package/README.md +13 -16
  2. package/dist/hooks/use-uet-id-sync.cjs +19 -0
  3. package/dist/hooks/use-uet-id-sync.cjs.map +1 -0
  4. package/dist/hooks/use-uet-id-sync.d.cts +10 -0
  5. package/dist/hooks/use-uet-id-sync.d.cts.map +1 -0
  6. package/dist/hooks/use-uet-id-sync.d.mts +10 -0
  7. package/dist/hooks/use-uet-id-sync.d.mts.map +1 -0
  8. package/dist/hooks/use-uet-id-sync.mjs +18 -0
  9. package/dist/hooks/use-uet-id-sync.mjs.map +1 -0
  10. package/dist/next/index.cjs +3 -1
  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 +3 -1
  17. package/dist/next/index.mjs.map +1 -1
  18. package/dist/react-router/index.cjs +3 -1
  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 +3 -1
  25. package/dist/react-router/index.mjs.map +1 -1
  26. package/dist/server/linkedin-conversions-api.cjs +28 -6
  27. package/dist/server/linkedin-conversions-api.cjs.map +1 -1
  28. package/dist/server/linkedin-conversions-api.d.cts +13 -3
  29. package/dist/server/linkedin-conversions-api.d.cts.map +1 -1
  30. package/dist/server/linkedin-conversions-api.d.mts +13 -3
  31. package/dist/server/linkedin-conversions-api.d.mts.map +1 -1
  32. package/dist/server/linkedin-conversions-api.mjs +28 -6
  33. package/dist/server/linkedin-conversions-api.mjs.map +1 -1
  34. package/dist/server/microsoft-conversions-api.cjs +1 -1
  35. package/dist/server/microsoft-conversions-api.mjs +1 -1
  36. package/dist/server/openai-conversions-api.cjs +1 -1
  37. package/dist/server/openai-conversions-api.mjs +1 -1
  38. package/dist/server/reddit-conversions-api.cjs +1 -1
  39. package/dist/server/reddit-conversions-api.mjs +1 -1
  40. package/dist/tanstack/analytics.cjs +3 -1
  41. package/dist/tanstack/analytics.cjs.map +1 -1
  42. package/dist/tanstack/analytics.d.cts +3 -1
  43. package/dist/tanstack/analytics.d.cts.map +1 -1
  44. package/dist/tanstack/analytics.d.mts +3 -1
  45. package/dist/tanstack/analytics.d.mts.map +1 -1
  46. package/dist/tanstack/analytics.mjs +3 -1
  47. package/dist/tanstack/analytics.mjs.map +1 -1
  48. package/dist/third-parties/index.cjs +3 -1
  49. package/dist/third-parties/index.d.cts +2 -2
  50. package/dist/third-parties/index.d.mts +2 -2
  51. package/dist/third-parties/index.mjs +2 -2
  52. package/dist/third-parties/meta-pixel.cjs +1 -1
  53. package/dist/third-parties/meta-pixel.mjs +1 -1
  54. package/dist/third-parties/microsoft-uet.cjs +45 -17
  55. package/dist/third-parties/microsoft-uet.cjs.map +1 -1
  56. package/dist/third-parties/microsoft-uet.d.cts +13 -19
  57. package/dist/third-parties/microsoft-uet.d.cts.map +1 -1
  58. package/dist/third-parties/microsoft-uet.d.mts +13 -19
  59. package/dist/third-parties/microsoft-uet.d.mts.map +1 -1
  60. package/dist/third-parties/microsoft-uet.mjs +44 -18
  61. package/dist/third-parties/microsoft-uet.mjs.map +1 -1
  62. package/dist/third-parties/openai-pixel.cjs +1 -1
  63. package/dist/third-parties/openai-pixel.mjs +1 -1
  64. package/dist/third-parties/reddit-pixel.cjs +1 -1
  65. package/dist/third-parties/reddit-pixel.mjs +1 -1
  66. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_utils_field = require("../utils/field.cjs");
2
+ const require_visitor_index = require("../visitor/index.cjs");
3
3
  const require_track_uetq = require("../track/uetq.cjs");
4
+ const require_utils_field = require("../utils/field.cjs");
4
5
  const require_utils_sha256 = require("../utils/sha256.cjs");
5
6
  //#region src/third-parties/microsoft-uet.ts
6
7
  const metrics = [
@@ -47,23 +48,23 @@ function sendUETEvent(name, properties, eventId) {
47
48
  * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
48
49
  * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
49
50
  *
50
- * No tag id parameter: unlike the Meta/Reddit pixels, UET is bound to its tag by the snippet
51
- * and `set` addresses whichever tag drains `uetq`.
51
+ * A setter itself, like `setGAUser`, rather than a factory like `setFBUser(pixelId)`: UET is
52
+ * bound to its tag by the snippet and `set` addresses whichever tag drains `uetq`, so there is
53
+ * nothing to bind first.
52
54
  */
53
- function setUETUser() {
54
- return ({ user_data }) => {
55
- if (typeof window === "undefined" || !window.uetq) {
56
- console.warn("uetq has not been initialized");
57
- return;
58
- }
59
- const em = require_utils_field.getFirst(user_data?.email);
60
- const ph = require_utils_field.getFirst(user_data?.phone_number);
61
- if (!em && !ph) return;
62
- window.uetq.push("set", { pid: clean({
63
- em,
64
- ph
65
- }) });
66
- };
55
+ function setUETUser({ user_id, user_data }) {
56
+ if (typeof window === "undefined" || !window.uetq) {
57
+ console.warn("uetq has not been initialized");
58
+ return;
59
+ }
60
+ if (user_id) syncUETVisitor(user_id);
61
+ const em = require_utils_field.getFirst(user_data?.email);
62
+ const ph = require_utils_field.getFirst(user_data?.phone_number);
63
+ if (!em && !ph) return;
64
+ window.uetq.push("set", { pid: clean({
65
+ em,
66
+ ph
67
+ }) });
67
68
  }
68
69
  /**
69
70
  * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
@@ -93,6 +94,31 @@ function setUETConsent(mode, consent) {
93
94
  * the pixel goes out without `UID` rather than with a raw id.
94
95
  * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters
95
96
  */
97
+ /**
98
+ * The Microsoft Advertising customer id the ID Sync pixel reports to, set once by the framework
99
+ * `Analytics` component's `uetCustomerId` prop (or `configureUET`). Unset, nothing syncs.
100
+ */
101
+ let uetCustomerId;
102
+ function configureUET(options) {
103
+ uetCustomerId = options.customerId === void 0 ? void 0 : String(options.customerId);
104
+ }
105
+ /**
106
+ * Sync the SDK visitor — the `anonymousId` of the events the server sends to the Conversions
107
+ * API — with Microsoft, plus the user's `UID` when one is known. The `Analytics` components run
108
+ * it on mount so every visit syncs at least once, anonymous visitors included (they are who
109
+ * remarketing audiences are built from), and `setUETUser` runs it again on sign-in.
110
+ */
111
+ async function syncUETVisitor(userId) {
112
+ if (!uetCustomerId) return;
113
+ try {
114
+ const { id } = await require_visitor_index.getVisitor();
115
+ sendUETIdSync({
116
+ customerId: uetCustomerId,
117
+ visitorId: id,
118
+ userId
119
+ });
120
+ } catch {}
121
+ }
96
122
  function sendUETIdSync(options) {
97
123
  if (typeof window === "undefined" || typeof Image === "undefined") return;
98
124
  if (isLocal()) return;
@@ -111,9 +137,11 @@ function sendUETIdSync(options) {
111
137
  require_utils_sha256.sha256(options.userId).then(fire, () => fire());
112
138
  }
113
139
  //#endregion
140
+ exports.configureUET = configureUET;
114
141
  exports.sendUETEvent = sendUETEvent;
115
142
  exports.sendUETIdSync = sendUETIdSync;
116
143
  exports.setUETConsent = setUETConsent;
117
144
  exports.setUETUser = setUETUser;
145
+ exports.syncUETVisitor = syncUETVisitor;
118
146
 
119
147
  //# sourceMappingURL=microsoft-uet.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-uet.cjs","names":["mapUETEvent","getFirst"],"sources":["../../src/third-parties/microsoft-uet.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { type UETConsent, type UETQ, mapUETEvent } from '../track/uetq';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\n\ndeclare global {\n interface Window {\n /**\n * Defined by the UET snippet before `bat.js` loads (as a plain array), so pushes made early\n * are queued rather than lost. Undefined only where the snippet was never installed.\n */\n uetq?: UETQ;\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\n/** Drop `undefined` fields: bat.js validates every key it receives, and `undefined` is a value. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction isLocal(): boolean {\n return window.location.host.includes('127.0.0.1') || window.location.host.includes('localhost');\n}\n\n/**\n * Forward an internal track event to the Microsoft Advertising UET tag.\n *\n * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same\n * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the\n * internal name on both channels — see `mapUETEvent`.\n *\n * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the\n * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in\n * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views\n * would report each page twice. The tag owns page loads; this tracker owns everything else.\n */\nexport function sendUETEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n if (metrics.includes(name) || name === 'page_view') return;\n if (isLocal()) return;\n\n const [action, params] = mapUETEvent(name, properties, eventId);\n window.uetq.push('event', action, clean(params));\n}\n\n/**\n * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a\n * conversion to an ad click without a third-party cookie. A `set` applies to every event fired\n * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own\n * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256\n * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.\n *\n * No tag id parameter: unlike the Meta/Reddit pixels, UET is bound to its tag by the snippet\n * and `set` addresses whichever tag drains `uetq`.\n */\nexport function setUETUser() {\n return ({ user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n\n const em = getFirst(user_data?.email);\n const ph = getFirst(user_data?.phone_number);\n if (!em && !ph) return;\n\n window.uetq.push('set', { pid: clean({ em, ph }) });\n };\n}\n\n/**\n * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead\n * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the\n * tag neither writes its cookies nor reads the `msclkid` from the URL.\n * https://help.ads.microsoft.com/#apex/ads/en/60119/1\n */\nexport function setUETConsent(mode: 'default' | 'update', consent: UETConsent) {\n if (typeof window === 'undefined') return;\n window.uetq ??= [] as unknown as UETQ;\n window.uetq.push('consent', mode, consent);\n}\n\n/**\n * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as\n * `anonymousId` to Microsoft's own identifier, so server events can be attributed across\n * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI\n * events and recommends it for measurement quality; fire it at least once per session, ideally\n * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the\n * browser context of the sync, which a server-side call cannot carry.\n *\n * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET\n * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what\n * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.\n * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender\n * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the\n * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)\n * the pixel goes out without `UID` rather than with a raw id.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters\n */\nexport function sendUETIdSync(options: {\n customerId: string | number;\n visitorId: string;\n /** The signed-in user's id, raw; hashed before it leaves the page. */\n userId?: string;\n}) {\n if (typeof window === 'undefined' || typeof Image === 'undefined') return;\n if (isLocal()) return;\n\n const fire = (uid?: string) => {\n const params = new URLSearchParams({\n Red3: `BACID_${options.customerId}`,\n VID: options.visitorId,\n });\n if (uid) params.set('UID', uid);\n new Image().src = `https://c.bing.com/c.gif?${params.toString()}`;\n };\n\n if (!options.userId) {\n fire();\n return;\n }\n sha256(options.userId).then(fire, () => fire());\n}\n"],"mappings":";;;;;AAgBA,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;AAAM;;AAI1D,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;AAEA,SAAS,UAAmB;CAC1B,OAAO,OAAO,SAAS,KAAK,SAAS,WAAW,KAAK,OAAO,SAAS,KAAK,SAAS,WAAW;AAChG;;;;;;;;;;;;;AAcA,SAAgB,aACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAI,QAAQ,SAAS,IAAI,KAAK,SAAS,aAAa;CACpD,IAAI,QAAQ,GAAG;CAEf,MAAM,CAAC,QAAQ,UAAUA,mBAAAA,YAAY,MAAM,YAAY,OAAO;CAC9D,OAAO,KAAK,KAAK,SAAS,QAAQ,MAAM,MAAM,CAAC;AACjD;;;;;;;;;;;AAYA,SAAgB,aAAa;CAC3B,QAAQ,EAAE,gBAAkC;EAC1C,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,KAAKC,oBAAAA,SAAS,WAAW,KAAK;EACpC,MAAM,KAAKA,oBAAAA,SAAS,WAAW,YAAY;EAC3C,IAAI,CAAC,MAAM,CAAC,IAAI;EAEhB,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM;GAAE;GAAI;EAAG,CAAC,EAAE,CAAC;CACpD;AACF;;;;;;;AAQA,SAAgB,cAAc,MAA4B,SAAqB;CAC7E,IAAI,OAAO,WAAW,aAAa;CACnC,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,KAAK,WAAW,MAAM,OAAO;AAC3C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,SAK3B;CACD,IAAI,OAAO,WAAW,eAAe,OAAO,UAAU,aAAa;CACnE,IAAI,QAAQ,GAAG;CAEf,MAAM,QAAQ,QAAiB;EAC7B,MAAM,SAAS,IAAI,gBAAgB;GACjC,MAAM,SAAS,QAAQ;GACvB,KAAK,QAAQ;EACf,CAAC;EACD,IAAI,KAAK,OAAO,IAAI,OAAO,GAAG;EAC9B,IAAI,MAAM,CAAC,CAAC,MAAM,4BAA4B,OAAO,SAAS;CAChE;CAEA,IAAI,CAAC,QAAQ,QAAQ;EACnB,KAAK;EACL;CACF;CACA,qBAAA,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;AAChD"}
1
+ {"version":3,"file":"microsoft-uet.cjs","names":["mapUETEvent","getFirst","getVisitor"],"sources":["../../src/third-parties/microsoft-uet.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { type UETConsent, type UETQ, mapUETEvent } from '../track/uetq';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\nimport { getVisitor } from '../visitor/index';\n\ndeclare global {\n interface Window {\n /**\n * Defined by the UET snippet before `bat.js` loads (as a plain array), so pushes made early\n * are queued rather than lost. Undefined only where the snippet was never installed.\n */\n uetq?: UETQ;\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\n/** Drop `undefined` fields: bat.js validates every key it receives, and `undefined` is a value. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction isLocal(): boolean {\n return window.location.host.includes('127.0.0.1') || window.location.host.includes('localhost');\n}\n\n/**\n * Forward an internal track event to the Microsoft Advertising UET tag.\n *\n * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same\n * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the\n * internal name on both channels — see `mapUETEvent`.\n *\n * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the\n * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in\n * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views\n * would report each page twice. The tag owns page loads; this tracker owns everything else.\n */\nexport function sendUETEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n if (metrics.includes(name) || name === 'page_view') return;\n if (isLocal()) return;\n\n const [action, params] = mapUETEvent(name, properties, eventId);\n window.uetq.push('event', action, clean(params));\n}\n\n/**\n * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a\n * conversion to an ad click without a third-party cookie. A `set` applies to every event fired\n * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own\n * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256\n * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.\n *\n * A setter itself, like `setGAUser`, rather than a factory like `setFBUser(pixelId)`: UET is\n * bound to its tag by the snippet and `set` addresses whichever tag drains `uetq`, so there is\n * nothing to bind first.\n */\nexport function setUETUser({ user_id, user_data }: UpdateVisitorDTO) {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n\n // A signed-in user is also the moment the ID Sync pixel can carry a `UID` — see `syncUETVisitor`.\n if (user_id) void syncUETVisitor(user_id);\n\n const em = getFirst(user_data?.email);\n const ph = getFirst(user_data?.phone_number);\n if (!em && !ph) return;\n\n window.uetq.push('set', { pid: clean({ em, ph }) });\n}\n\n/**\n * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead\n * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the\n * tag neither writes its cookies nor reads the `msclkid` from the URL.\n * https://help.ads.microsoft.com/#apex/ads/en/60119/1\n */\nexport function setUETConsent(mode: 'default' | 'update', consent: UETConsent) {\n if (typeof window === 'undefined') return;\n window.uetq ??= [] as unknown as UETQ;\n window.uetq.push('consent', mode, consent);\n}\n\n/**\n * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as\n * `anonymousId` to Microsoft's own identifier, so server events can be attributed across\n * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI\n * events and recommends it for measurement quality; fire it at least once per session, ideally\n * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the\n * browser context of the sync, which a server-side call cannot carry.\n *\n * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET\n * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what\n * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.\n * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender\n * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the\n * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)\n * the pixel goes out without `UID` rather than with a raw id.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters\n */\n/**\n * The Microsoft Advertising customer id the ID Sync pixel reports to, set once by the framework\n * `Analytics` component's `uetCustomerId` prop (or `configureUET`). Unset, nothing syncs.\n */\nlet uetCustomerId: string | undefined;\n\nexport function configureUET(options: { customerId?: string | number }) {\n uetCustomerId = options.customerId === undefined ? undefined : String(options.customerId);\n}\n\n/**\n * Sync the SDK visitor — the `anonymousId` of the events the server sends to the Conversions\n * API — with Microsoft, plus the user's `UID` when one is known. The `Analytics` components run\n * it on mount so every visit syncs at least once, anonymous visitors included (they are who\n * remarketing audiences are built from), and `setUETUser` runs it again on sign-in.\n */\nexport async function syncUETVisitor(userId?: string): Promise<void> {\n if (!uetCustomerId) return;\n try {\n const { id } = await getVisitor();\n sendUETIdSync({ customerId: uetCustomerId, visitorId: id, userId });\n } catch {\n // The visitor request failed; the next page load tries again.\n }\n}\n\nexport function sendUETIdSync(options: {\n customerId: string | number;\n visitorId: string;\n /** The signed-in user's id, raw; hashed before it leaves the page. */\n userId?: string;\n}) {\n if (typeof window === 'undefined' || typeof Image === 'undefined') return;\n if (isLocal()) return;\n\n const fire = (uid?: string) => {\n const params = new URLSearchParams({\n Red3: `BACID_${options.customerId}`,\n VID: options.visitorId,\n });\n if (uid) params.set('UID', uid);\n new Image().src = `https://c.bing.com/c.gif?${params.toString()}`;\n };\n\n if (!options.userId) {\n fire();\n return;\n }\n sha256(options.userId).then(fire, () => fire());\n}\n"],"mappings":";;;;;;AAiBA,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;AAAM;;AAI1D,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;AAEA,SAAS,UAAmB;CAC1B,OAAO,OAAO,SAAS,KAAK,SAAS,WAAW,KAAK,OAAO,SAAS,KAAK,SAAS,WAAW;AAChG;;;;;;;;;;;;;AAcA,SAAgB,aACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAI,QAAQ,SAAS,IAAI,KAAK,SAAS,aAAa;CACpD,IAAI,QAAQ,GAAG;CAEf,MAAM,CAAC,QAAQ,UAAUA,mBAAAA,YAAY,MAAM,YAAY,OAAO;CAC9D,OAAO,KAAK,KAAK,SAAS,QAAQ,MAAM,MAAM,CAAC;AACjD;;;;;;;;;;;;AAaA,SAAgB,WAAW,EAAE,SAAS,aAA+B;CACnE,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CAGA,IAAI,SAAS,eAAoB,OAAO;CAExC,MAAM,KAAKC,oBAAAA,SAAS,WAAW,KAAK;CACpC,MAAM,KAAKA,oBAAAA,SAAS,WAAW,YAAY;CAC3C,IAAI,CAAC,MAAM,CAAC,IAAI;CAEhB,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM;EAAE;EAAI;CAAG,CAAC,EAAE,CAAC;AACpD;;;;;;;AAQA,SAAgB,cAAc,MAA4B,SAAqB;CAC7E,IAAI,OAAO,WAAW,aAAa;CACnC,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,KAAK,WAAW,MAAM,OAAO;AAC3C;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAI;AAEJ,SAAgB,aAAa,SAA2C;CACtE,gBAAgB,QAAQ,eAAe,KAAA,IAAY,KAAA,IAAY,OAAO,QAAQ,UAAU;AAC1F;;;;;;;AAQA,eAAsB,eAAe,QAAgC;CACnE,IAAI,CAAC,eAAe;CACpB,IAAI;EACF,MAAM,EAAE,OAAO,MAAMC,sBAAAA,WAAW;EAChC,cAAc;GAAE,YAAY;GAAe,WAAW;GAAI;EAAO,CAAC;CACpE,QAAQ,CAER;AACF;AAEA,SAAgB,cAAc,SAK3B;CACD,IAAI,OAAO,WAAW,eAAe,OAAO,UAAU,aAAa;CACnE,IAAI,QAAQ,GAAG;CAEf,MAAM,QAAQ,QAAiB;EAC7B,MAAM,SAAS,IAAI,gBAAgB;GACjC,MAAM,SAAS,QAAQ;GACvB,KAAK,QAAQ;EACf,CAAC;EACD,IAAI,KAAK,OAAO,IAAI,OAAO,GAAG;EAC9B,IAAI,MAAM,CAAC,CAAC,MAAM,4BAA4B,OAAO,SAAS;CAChE;CAEA,IAAI,CAAC,QAAQ,QAAQ;EACnB,KAAK;EACL;CACF;CACA,qBAAA,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;AAChD"}
@@ -31,10 +31,11 @@ declare function sendUETEvent<T extends EventName>(name: TrackName<T>, propertie
31
31
  * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
32
32
  * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
33
33
  *
34
- * No tag id parameter: unlike the Meta/Reddit pixels, UET is bound to its tag by the snippet
35
- * and `set` addresses whichever tag drains `uetq`.
34
+ * A setter itself, like `setGAUser`, rather than a factory like `setFBUser(pixelId)`: UET is
35
+ * bound to its tag by the snippet and `set` addresses whichever tag drains `uetq`, so there is
36
+ * nothing to bind first.
36
37
  */
37
- declare function setUETUser(): ({ user_data }: UpdateVisitorDTO) => void;
38
+ declare function setUETUser({ user_id, user_data }: UpdateVisitorDTO): void;
38
39
  /**
39
40
  * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
40
41
  * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the
@@ -42,23 +43,16 @@ declare function setUETUser(): ({ user_data }: UpdateVisitorDTO) => void;
42
43
  * https://help.ads.microsoft.com/#apex/ads/en/60119/1
43
44
  */
44
45
  declare function setUETConsent(mode: 'default' | 'update', consent: UETConsent): void;
46
+ declare function configureUET(options: {
47
+ customerId?: string | number;
48
+ }): void;
45
49
  /**
46
- * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as
47
- * `anonymousId` to Microsoft's own identifier, so server events can be attributed across
48
- * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI
49
- * events and recommends it for measurement quality; fire it at least once per session, ideally
50
- * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the
51
- * browser context of the sync, which a server-side call cannot carry.
52
- *
53
- * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET
54
- * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what
55
- * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.
56
- * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender
57
- * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the
58
- * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)
59
- * the pixel goes out without `UID` rather than with a raw id.
60
- * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters
50
+ * Sync the SDK visitor — the `anonymousId` of the events the server sends to the Conversions
51
+ * API — with Microsoft, plus the user's `UID` when one is known. The `Analytics` components run
52
+ * it on mount so every visit syncs at least once, anonymous visitors included (they are who
53
+ * remarketing audiences are built from), and `setUETUser` runs it again on sign-in.
61
54
  */
55
+ declare function syncUETVisitor(userId?: string): Promise<void>;
62
56
  declare function sendUETIdSync(options: {
63
57
  customerId: string | number;
64
58
  visitorId: string;
@@ -66,5 +60,5 @@ declare function sendUETIdSync(options: {
66
60
  userId?: string;
67
61
  }): void;
68
62
  //#endregion
69
- export { sendUETEvent, sendUETIdSync, setUETConsent, setUETUser };
63
+ export { configureUET, sendUETEvent, sendUETIdSync, setUETConsent, setUETUser, syncUETVisitor };
70
64
  //# sourceMappingURL=microsoft-uet.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-uet.d.cts","names":[],"sources":["../../src/third-parties/microsoft-uet.ts"],"mappings":";;;;QAMQ;YACI;;;;;IAKR,OAAO;;;;;;;;;;;;;;;iBA4BK,aAAa,UAAU,WACrC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;;;;;;iBAuBc,iBAAU,aACD;;;;;;;iBAoBT,cAAc,4BAA4B,SAAS;;;;;;;;;;;;;;;;;;iBAuBnD,cAAc;EAC5B;EACA;;EAEA"}
1
+ {"version":3,"file":"microsoft-uet.d.cts","names":[],"sources":["../../src/third-parties/microsoft-uet.ts"],"mappings":";;;;QAOQ;YACI;;;;;IAKR,OAAO;;;;;;;;;;;;;;;iBA4BK,aAAa,UAAU,WACrC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;;;;;;;iBAwBc,aAAa,SAAS,aAAa;;;;;;;iBAsBnC,cAAc,4BAA4B,SAAS;iBA6BnD,aAAa;EAAW;;;;;;;;iBAUlB,eAAe,kBAAkB;iBAUvC,cAAc;EAC5B;EACA;;EAEA"}
@@ -31,10 +31,11 @@ declare function sendUETEvent<T extends EventName>(name: TrackName<T>, propertie
31
31
  * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
32
32
  * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
33
33
  *
34
- * No tag id parameter: unlike the Meta/Reddit pixels, UET is bound to its tag by the snippet
35
- * and `set` addresses whichever tag drains `uetq`.
34
+ * A setter itself, like `setGAUser`, rather than a factory like `setFBUser(pixelId)`: UET is
35
+ * bound to its tag by the snippet and `set` addresses whichever tag drains `uetq`, so there is
36
+ * nothing to bind first.
36
37
  */
37
- declare function setUETUser(): ({ user_data }: UpdateVisitorDTO) => void;
38
+ declare function setUETUser({ user_id, user_data }: UpdateVisitorDTO): void;
38
39
  /**
39
40
  * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
40
41
  * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the
@@ -42,23 +43,16 @@ declare function setUETUser(): ({ user_data }: UpdateVisitorDTO) => void;
42
43
  * https://help.ads.microsoft.com/#apex/ads/en/60119/1
43
44
  */
44
45
  declare function setUETConsent(mode: 'default' | 'update', consent: UETConsent): void;
46
+ declare function configureUET(options: {
47
+ customerId?: string | number;
48
+ }): void;
45
49
  /**
46
- * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as
47
- * `anonymousId` to Microsoft's own identifier, so server events can be attributed across
48
- * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI
49
- * events and recommends it for measurement quality; fire it at least once per session, ideally
50
- * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the
51
- * browser context of the sync, which a server-side call cannot carry.
52
- *
53
- * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET
54
- * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what
55
- * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.
56
- * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender
57
- * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the
58
- * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)
59
- * the pixel goes out without `UID` rather than with a raw id.
60
- * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters
50
+ * Sync the SDK visitor — the `anonymousId` of the events the server sends to the Conversions
51
+ * API — with Microsoft, plus the user's `UID` when one is known. The `Analytics` components run
52
+ * it on mount so every visit syncs at least once, anonymous visitors included (they are who
53
+ * remarketing audiences are built from), and `setUETUser` runs it again on sign-in.
61
54
  */
55
+ declare function syncUETVisitor(userId?: string): Promise<void>;
62
56
  declare function sendUETIdSync(options: {
63
57
  customerId: string | number;
64
58
  visitorId: string;
@@ -66,5 +60,5 @@ declare function sendUETIdSync(options: {
66
60
  userId?: string;
67
61
  }): void;
68
62
  //#endregion
69
- export { sendUETEvent, sendUETIdSync, setUETConsent, setUETUser };
63
+ export { configureUET, sendUETEvent, sendUETIdSync, setUETConsent, setUETUser, syncUETVisitor };
70
64
  //# sourceMappingURL=microsoft-uet.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-uet.d.mts","names":[],"sources":["../../src/third-parties/microsoft-uet.ts"],"mappings":";;;;QAMQ;YACI;;;;;IAKR,OAAO;;;;;;;;;;;;;;;iBA4BK,aAAa,UAAU,WACrC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;;;;;;iBAuBc,iBAAU,aACD;;;;;;;iBAoBT,cAAc,4BAA4B,SAAS;;;;;;;;;;;;;;;;;;iBAuBnD,cAAc;EAC5B;EACA;;EAEA"}
1
+ {"version":3,"file":"microsoft-uet.d.mts","names":[],"sources":["../../src/third-parties/microsoft-uet.ts"],"mappings":";;;;QAOQ;YACI;;;;;IAKR,OAAO;;;;;;;;;;;;;;;iBA4BK,aAAa,UAAU,WACrC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;;;;;;;iBAwBc,aAAa,SAAS,aAAa;;;;;;;iBAsBnC,cAAc,4BAA4B,SAAS;iBA6BnD,aAAa;EAAW;;;;;;;;iBAUlB,eAAe,kBAAkB;iBAUvC,cAAc;EAC5B;EACA;;EAEA"}
@@ -1,5 +1,6 @@
1
- import { getFirst } from "../utils/field.mjs";
1
+ import { getVisitor } from "../visitor/index.mjs";
2
2
  import { mapUETEvent } from "../track/uetq.mjs";
3
+ import { getFirst } from "../utils/field.mjs";
3
4
  import { sha256 } from "../utils/sha256.mjs";
4
5
  //#region src/third-parties/microsoft-uet.ts
5
6
  const metrics = [
@@ -46,23 +47,23 @@ function sendUETEvent(name, properties, eventId) {
46
47
  * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
47
48
  * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
48
49
  *
49
- * No tag id parameter: unlike the Meta/Reddit pixels, UET is bound to its tag by the snippet
50
- * and `set` addresses whichever tag drains `uetq`.
50
+ * A setter itself, like `setGAUser`, rather than a factory like `setFBUser(pixelId)`: UET is
51
+ * bound to its tag by the snippet and `set` addresses whichever tag drains `uetq`, so there is
52
+ * nothing to bind first.
51
53
  */
52
- function setUETUser() {
53
- return ({ user_data }) => {
54
- if (typeof window === "undefined" || !window.uetq) {
55
- console.warn("uetq has not been initialized");
56
- return;
57
- }
58
- const em = getFirst(user_data?.email);
59
- const ph = getFirst(user_data?.phone_number);
60
- if (!em && !ph) return;
61
- window.uetq.push("set", { pid: clean({
62
- em,
63
- ph
64
- }) });
65
- };
54
+ function setUETUser({ user_id, user_data }) {
55
+ if (typeof window === "undefined" || !window.uetq) {
56
+ console.warn("uetq has not been initialized");
57
+ return;
58
+ }
59
+ if (user_id) syncUETVisitor(user_id);
60
+ const em = getFirst(user_data?.email);
61
+ const ph = getFirst(user_data?.phone_number);
62
+ if (!em && !ph) return;
63
+ window.uetq.push("set", { pid: clean({
64
+ em,
65
+ ph
66
+ }) });
66
67
  }
67
68
  /**
68
69
  * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
@@ -92,6 +93,31 @@ function setUETConsent(mode, consent) {
92
93
  * the pixel goes out without `UID` rather than with a raw id.
93
94
  * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters
94
95
  */
96
+ /**
97
+ * The Microsoft Advertising customer id the ID Sync pixel reports to, set once by the framework
98
+ * `Analytics` component's `uetCustomerId` prop (or `configureUET`). Unset, nothing syncs.
99
+ */
100
+ let uetCustomerId;
101
+ function configureUET(options) {
102
+ uetCustomerId = options.customerId === void 0 ? void 0 : String(options.customerId);
103
+ }
104
+ /**
105
+ * Sync the SDK visitor — the `anonymousId` of the events the server sends to the Conversions
106
+ * API — with Microsoft, plus the user's `UID` when one is known. The `Analytics` components run
107
+ * it on mount so every visit syncs at least once, anonymous visitors included (they are who
108
+ * remarketing audiences are built from), and `setUETUser` runs it again on sign-in.
109
+ */
110
+ async function syncUETVisitor(userId) {
111
+ if (!uetCustomerId) return;
112
+ try {
113
+ const { id } = await getVisitor();
114
+ sendUETIdSync({
115
+ customerId: uetCustomerId,
116
+ visitorId: id,
117
+ userId
118
+ });
119
+ } catch {}
120
+ }
95
121
  function sendUETIdSync(options) {
96
122
  if (typeof window === "undefined" || typeof Image === "undefined") return;
97
123
  if (isLocal()) return;
@@ -110,6 +136,6 @@ function sendUETIdSync(options) {
110
136
  sha256(options.userId).then(fire, () => fire());
111
137
  }
112
138
  //#endregion
113
- export { sendUETEvent, sendUETIdSync, setUETConsent, setUETUser };
139
+ export { configureUET, sendUETEvent, sendUETIdSync, setUETConsent, setUETUser, syncUETVisitor };
114
140
 
115
141
  //# sourceMappingURL=microsoft-uet.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"microsoft-uet.mjs","names":[],"sources":["../../src/third-parties/microsoft-uet.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { type UETConsent, type UETQ, mapUETEvent } from '../track/uetq';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\n\ndeclare global {\n interface Window {\n /**\n * Defined by the UET snippet before `bat.js` loads (as a plain array), so pushes made early\n * are queued rather than lost. Undefined only where the snippet was never installed.\n */\n uetq?: UETQ;\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\n/** Drop `undefined` fields: bat.js validates every key it receives, and `undefined` is a value. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction isLocal(): boolean {\n return window.location.host.includes('127.0.0.1') || window.location.host.includes('localhost');\n}\n\n/**\n * Forward an internal track event to the Microsoft Advertising UET tag.\n *\n * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same\n * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the\n * internal name on both channels — see `mapUETEvent`.\n *\n * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the\n * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in\n * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views\n * would report each page twice. The tag owns page loads; this tracker owns everything else.\n */\nexport function sendUETEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n if (metrics.includes(name) || name === 'page_view') return;\n if (isLocal()) return;\n\n const [action, params] = mapUETEvent(name, properties, eventId);\n window.uetq.push('event', action, clean(params));\n}\n\n/**\n * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a\n * conversion to an ad click without a third-party cookie. A `set` applies to every event fired\n * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own\n * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256\n * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.\n *\n * No tag id parameter: unlike the Meta/Reddit pixels, UET is bound to its tag by the snippet\n * and `set` addresses whichever tag drains `uetq`.\n */\nexport function setUETUser() {\n return ({ user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n\n const em = getFirst(user_data?.email);\n const ph = getFirst(user_data?.phone_number);\n if (!em && !ph) return;\n\n window.uetq.push('set', { pid: clean({ em, ph }) });\n };\n}\n\n/**\n * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead\n * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the\n * tag neither writes its cookies nor reads the `msclkid` from the URL.\n * https://help.ads.microsoft.com/#apex/ads/en/60119/1\n */\nexport function setUETConsent(mode: 'default' | 'update', consent: UETConsent) {\n if (typeof window === 'undefined') return;\n window.uetq ??= [] as unknown as UETQ;\n window.uetq.push('consent', mode, consent);\n}\n\n/**\n * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as\n * `anonymousId` to Microsoft's own identifier, so server events can be attributed across\n * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI\n * events and recommends it for measurement quality; fire it at least once per session, ideally\n * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the\n * browser context of the sync, which a server-side call cannot carry.\n *\n * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET\n * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what\n * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.\n * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender\n * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the\n * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)\n * the pixel goes out without `UID` rather than with a raw id.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters\n */\nexport function sendUETIdSync(options: {\n customerId: string | number;\n visitorId: string;\n /** The signed-in user's id, raw; hashed before it leaves the page. */\n userId?: string;\n}) {\n if (typeof window === 'undefined' || typeof Image === 'undefined') return;\n if (isLocal()) return;\n\n const fire = (uid?: string) => {\n const params = new URLSearchParams({\n Red3: `BACID_${options.customerId}`,\n VID: options.visitorId,\n });\n if (uid) params.set('UID', uid);\n new Image().src = `https://c.bing.com/c.gif?${params.toString()}`;\n };\n\n if (!options.userId) {\n fire();\n return;\n }\n sha256(options.userId).then(fire, () => fire());\n}\n"],"mappings":";;;;AAgBA,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;AAAM;;AAI1D,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;AAEA,SAAS,UAAmB;CAC1B,OAAO,OAAO,SAAS,KAAK,SAAS,WAAW,KAAK,OAAO,SAAS,KAAK,SAAS,WAAW;AAChG;;;;;;;;;;;;;AAcA,SAAgB,aACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAI,QAAQ,SAAS,IAAI,KAAK,SAAS,aAAa;CACpD,IAAI,QAAQ,GAAG;CAEf,MAAM,CAAC,QAAQ,UAAU,YAAY,MAAM,YAAY,OAAO;CAC9D,OAAO,KAAK,KAAK,SAAS,QAAQ,MAAM,MAAM,CAAC;AACjD;;;;;;;;;;;AAYA,SAAgB,aAAa;CAC3B,QAAQ,EAAE,gBAAkC;EAC1C,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,KAAK,SAAS,WAAW,KAAK;EACpC,MAAM,KAAK,SAAS,WAAW,YAAY;EAC3C,IAAI,CAAC,MAAM,CAAC,IAAI;EAEhB,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM;GAAE;GAAI;EAAG,CAAC,EAAE,CAAC;CACpD;AACF;;;;;;;AAQA,SAAgB,cAAc,MAA4B,SAAqB;CAC7E,IAAI,OAAO,WAAW,aAAa;CACnC,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,KAAK,WAAW,MAAM,OAAO;AAC3C;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,cAAc,SAK3B;CACD,IAAI,OAAO,WAAW,eAAe,OAAO,UAAU,aAAa;CACnE,IAAI,QAAQ,GAAG;CAEf,MAAM,QAAQ,QAAiB;EAC7B,MAAM,SAAS,IAAI,gBAAgB;GACjC,MAAM,SAAS,QAAQ;GACvB,KAAK,QAAQ;EACf,CAAC;EACD,IAAI,KAAK,OAAO,IAAI,OAAO,GAAG;EAC9B,IAAI,MAAM,CAAC,CAAC,MAAM,4BAA4B,OAAO,SAAS;CAChE;CAEA,IAAI,CAAC,QAAQ,QAAQ;EACnB,KAAK;EACL;CACF;CACA,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;AAChD"}
1
+ {"version":3,"file":"microsoft-uet.mjs","names":[],"sources":["../../src/third-parties/microsoft-uet.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { type UETConsent, type UETQ, mapUETEvent } from '../track/uetq';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\nimport { getVisitor } from '../visitor/index';\n\ndeclare global {\n interface Window {\n /**\n * Defined by the UET snippet before `bat.js` loads (as a plain array), so pushes made early\n * are queued rather than lost. Undefined only where the snippet was never installed.\n */\n uetq?: UETQ;\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\n/** Drop `undefined` fields: bat.js validates every key it receives, and `undefined` is a value. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\nfunction isLocal(): boolean {\n return window.location.host.includes('127.0.0.1') || window.location.host.includes('localhost');\n}\n\n/**\n * Forward an internal track event to the Microsoft Advertising UET tag.\n *\n * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same\n * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the\n * internal name on both channels — see `mapUETEvent`.\n *\n * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the\n * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in\n * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views\n * would report each page twice. The tag owns page loads; this tracker owns everything else.\n */\nexport function sendUETEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n if (metrics.includes(name) || name === 'page_view') return;\n if (isLocal()) return;\n\n const [action, params] = mapUETEvent(name, properties, eventId);\n window.uetq.push('event', action, clean(params));\n}\n\n/**\n * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a\n * conversion to an ad click without a third-party cookie. A `set` applies to every event fired\n * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own\n * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256\n * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.\n *\n * A setter itself, like `setGAUser`, rather than a factory like `setFBUser(pixelId)`: UET is\n * bound to its tag by the snippet and `set` addresses whichever tag drains `uetq`, so there is\n * nothing to bind first.\n */\nexport function setUETUser({ user_id, user_data }: UpdateVisitorDTO) {\n if (typeof window === 'undefined' || !window.uetq) {\n console.warn('uetq has not been initialized');\n return;\n }\n\n // A signed-in user is also the moment the ID Sync pixel can carry a `UID` — see `syncUETVisitor`.\n if (user_id) void syncUETVisitor(user_id);\n\n const em = getFirst(user_data?.email);\n const ph = getFirst(user_data?.phone_number);\n if (!em && !ph) return;\n\n window.uetq.push('set', { pid: clean({ em, ph }) });\n}\n\n/**\n * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead\n * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the\n * tag neither writes its cookies nor reads the `msclkid` from the URL.\n * https://help.ads.microsoft.com/#apex/ads/en/60119/1\n */\nexport function setUETConsent(mode: 'default' | 'update', consent: UETConsent) {\n if (typeof window === 'undefined') return;\n window.uetq ??= [] as unknown as UETQ;\n window.uetq.push('consent', mode, consent);\n}\n\n/**\n * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as\n * `anonymousId` to Microsoft's own identifier, so server events can be attributed across\n * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI\n * events and recommends it for measurement quality; fire it at least once per session, ideally\n * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the\n * browser context of the sync, which a server-side call cannot carry.\n *\n * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET\n * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what\n * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.\n * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender\n * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the\n * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)\n * the pixel goes out without `UID` rather than with a raw id.\n * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters\n */\n/**\n * The Microsoft Advertising customer id the ID Sync pixel reports to, set once by the framework\n * `Analytics` component's `uetCustomerId` prop (or `configureUET`). Unset, nothing syncs.\n */\nlet uetCustomerId: string | undefined;\n\nexport function configureUET(options: { customerId?: string | number }) {\n uetCustomerId = options.customerId === undefined ? undefined : String(options.customerId);\n}\n\n/**\n * Sync the SDK visitor — the `anonymousId` of the events the server sends to the Conversions\n * API — with Microsoft, plus the user's `UID` when one is known. The `Analytics` components run\n * it on mount so every visit syncs at least once, anonymous visitors included (they are who\n * remarketing audiences are built from), and `setUETUser` runs it again on sign-in.\n */\nexport async function syncUETVisitor(userId?: string): Promise<void> {\n if (!uetCustomerId) return;\n try {\n const { id } = await getVisitor();\n sendUETIdSync({ customerId: uetCustomerId, visitorId: id, userId });\n } catch {\n // The visitor request failed; the next page load tries again.\n }\n}\n\nexport function sendUETIdSync(options: {\n customerId: string | number;\n visitorId: string;\n /** The signed-in user's id, raw; hashed before it leaves the page. */\n userId?: string;\n}) {\n if (typeof window === 'undefined' || typeof Image === 'undefined') return;\n if (isLocal()) return;\n\n const fire = (uid?: string) => {\n const params = new URLSearchParams({\n Red3: `BACID_${options.customerId}`,\n VID: options.visitorId,\n });\n if (uid) params.set('UID', uid);\n new Image().src = `https://c.bing.com/c.gif?${params.toString()}`;\n };\n\n if (!options.userId) {\n fire();\n return;\n }\n sha256(options.userId).then(fire, () => fire());\n}\n"],"mappings":";;;;;AAiBA,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;AAAM;;AAI1D,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;AAEA,SAAS,UAAmB;CAC1B,OAAO,OAAO,SAAS,KAAK,SAAS,WAAW,KAAK,OAAO,SAAS,KAAK,SAAS,WAAW;AAChG;;;;;;;;;;;;;AAcA,SAAgB,aACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAI,QAAQ,SAAS,IAAI,KAAK,SAAS,aAAa;CACpD,IAAI,QAAQ,GAAG;CAEf,MAAM,CAAC,QAAQ,UAAU,YAAY,MAAM,YAAY,OAAO;CAC9D,OAAO,KAAK,KAAK,SAAS,QAAQ,MAAM,MAAM,CAAC;AACjD;;;;;;;;;;;;AAaA,SAAgB,WAAW,EAAE,SAAS,aAA+B;CACnE,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CAGA,IAAI,SAAS,eAAoB,OAAO;CAExC,MAAM,KAAK,SAAS,WAAW,KAAK;CACpC,MAAM,KAAK,SAAS,WAAW,YAAY;CAC3C,IAAI,CAAC,MAAM,CAAC,IAAI;CAEhB,OAAO,KAAK,KAAK,OAAO,EAAE,KAAK,MAAM;EAAE;EAAI;CAAG,CAAC,EAAE,CAAC;AACpD;;;;;;;AAQA,SAAgB,cAAc,MAA4B,SAAqB;CAC7E,IAAI,OAAO,WAAW,aAAa;CACnC,OAAO,SAAS,CAAC;CACjB,OAAO,KAAK,KAAK,WAAW,MAAM,OAAO;AAC3C;;;;;;;;;;;;;;;;;;;;;;AAuBA,IAAI;AAEJ,SAAgB,aAAa,SAA2C;CACtE,gBAAgB,QAAQ,eAAe,KAAA,IAAY,KAAA,IAAY,OAAO,QAAQ,UAAU;AAC1F;;;;;;;AAQA,eAAsB,eAAe,QAAgC;CACnE,IAAI,CAAC,eAAe;CACpB,IAAI;EACF,MAAM,EAAE,OAAO,MAAM,WAAW;EAChC,cAAc;GAAE,YAAY;GAAe,WAAW;GAAI;EAAO,CAAC;CACpE,QAAQ,CAER;AACF;AAEA,SAAgB,cAAc,SAK3B;CACD,IAAI,OAAO,WAAW,eAAe,OAAO,UAAU,aAAa;CACnE,IAAI,QAAQ,GAAG;CAEf,MAAM,QAAQ,QAAiB;EAC7B,MAAM,SAAS,IAAI,gBAAgB;GACjC,MAAM,SAAS,QAAQ;GACvB,KAAK,QAAQ;EACf,CAAC;EACD,IAAI,KAAK,OAAO,IAAI,OAAO,GAAG;EAC9B,IAAI,MAAM,CAAC,CAAC,MAAM,4BAA4B,OAAO,SAAS;CAChE;CAEA,IAAI,CAAC,QAAQ,QAAQ;EACnB,KAAK;EACL;CACF;CACA,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC;AAChD"}
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_utils_field = require("../utils/field.cjs");
3
- const require_track_oaiq = require("../track/oaiq.cjs");
4
3
  const require_utils_sha256 = require("../utils/sha256.cjs");
4
+ const require_track_oaiq = require("../track/oaiq.cjs");
5
5
  //#region src/third-parties/openai-pixel.ts
6
6
  /** Drop `undefined` fields so the SDK only receives populated values. */
7
7
  function clean(value) {
@@ -1,6 +1,6 @@
1
1
  import { getFirst } from "../utils/field.mjs";
2
- import { NON_AD_EVENTS, mapOAIEvent } from "../track/oaiq.mjs";
3
2
  import { sha256 } from "../utils/sha256.mjs";
3
+ import { NON_AD_EVENTS, mapOAIEvent } from "../track/oaiq.mjs";
4
4
  //#region src/third-parties/openai-pixel.ts
5
5
  /** Drop `undefined` fields so the SDK only receives populated values. */
6
6
  function clean(value) {
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_track_rdt = require("../track/rdt.cjs");
3
2
  const require_utils_field = require("../utils/field.cjs");
3
+ const require_track_rdt = require("../track/rdt.cjs");
4
4
  //#region src/third-parties/reddit-pixel.ts
5
5
  const metrics = [
6
6
  "CLS",
@@ -1,5 +1,5 @@
1
- import { mapRDTEvent } from "../track/rdt.mjs";
2
1
  import { getFirst } from "../utils/field.mjs";
2
+ import { mapRDTEvent } from "../track/rdt.mjs";
3
3
  //#region src/third-parties/reddit-pixel.ts
4
4
  const metrics = [
5
5
  "CLS",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/analytics",
3
- "version": "8.5.0",
3
+ "version": "8.7.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",