@shware/analytics 8.2.0 → 8.3.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 (76) hide show
  1. package/README.md +63 -2
  2. package/dist/click-id/index.cjs +57 -3
  3. package/dist/click-id/index.cjs.map +1 -1
  4. package/dist/click-id/index.d.cts +16 -1
  5. package/dist/click-id/index.d.cts.map +1 -1
  6. package/dist/click-id/index.d.mts +16 -1
  7. package/dist/click-id/index.d.mts.map +1 -1
  8. package/dist/click-id/index.mjs +54 -4
  9. package/dist/click-id/index.mjs.map +1 -1
  10. package/dist/server/google-data-manager.cjs +2 -0
  11. package/dist/server/google-data-manager.cjs.map +1 -1
  12. package/dist/server/google-data-manager.d.cts.map +1 -1
  13. package/dist/server/google-data-manager.d.mts.map +1 -1
  14. package/dist/server/google-data-manager.mjs +2 -0
  15. package/dist/server/google-data-manager.mjs.map +1 -1
  16. package/dist/server/index.cjs +8 -0
  17. package/dist/server/index.d.cts +5 -4
  18. package/dist/server/index.d.mts +5 -4
  19. package/dist/server/index.mjs +5 -4
  20. package/dist/server/microsoft-conversions-api.cjs +171 -0
  21. package/dist/server/microsoft-conversions-api.cjs.map +1 -0
  22. package/dist/server/microsoft-conversions-api.d.cts +117 -0
  23. package/dist/server/microsoft-conversions-api.d.cts.map +1 -0
  24. package/dist/server/microsoft-conversions-api.d.mts +117 -0
  25. package/dist/server/microsoft-conversions-api.d.mts.map +1 -0
  26. package/dist/server/microsoft-conversions-api.mjs +168 -0
  27. package/dist/server/microsoft-conversions-api.mjs.map +1 -0
  28. package/dist/tanstack/middleware.cjs +2 -1
  29. package/dist/tanstack/middleware.cjs.map +1 -1
  30. package/dist/tanstack/middleware.d.cts +2 -1
  31. package/dist/tanstack/middleware.d.cts.map +1 -1
  32. package/dist/tanstack/middleware.d.mts +2 -1
  33. package/dist/tanstack/middleware.d.mts.map +1 -1
  34. package/dist/tanstack/middleware.mjs +2 -1
  35. package/dist/tanstack/middleware.mjs.map +1 -1
  36. package/dist/third-parties/index.cjs +5 -0
  37. package/dist/third-parties/index.d.cts +3 -1
  38. package/dist/third-parties/index.d.mts +3 -1
  39. package/dist/third-parties/index.mjs +2 -1
  40. package/dist/third-parties/microsoft-uet.cjs +119 -0
  41. package/dist/third-parties/microsoft-uet.cjs.map +1 -0
  42. package/dist/third-parties/microsoft-uet.d.cts +70 -0
  43. package/dist/third-parties/microsoft-uet.d.cts.map +1 -0
  44. package/dist/third-parties/microsoft-uet.d.mts +70 -0
  45. package/dist/third-parties/microsoft-uet.d.mts.map +1 -0
  46. package/dist/third-parties/microsoft-uet.mjs +115 -0
  47. package/dist/third-parties/microsoft-uet.mjs.map +1 -0
  48. package/dist/third-parties/openai-pixel.cjs +2 -7
  49. package/dist/third-parties/openai-pixel.cjs.map +1 -1
  50. package/dist/third-parties/openai-pixel.d.cts.map +1 -1
  51. package/dist/third-parties/openai-pixel.d.mts.map +1 -1
  52. package/dist/third-parties/openai-pixel.mjs +1 -6
  53. package/dist/third-parties/openai-pixel.mjs.map +1 -1
  54. package/dist/track/uetq.cjs +80 -0
  55. package/dist/track/uetq.cjs.map +1 -0
  56. package/dist/track/uetq.d.cts +111 -0
  57. package/dist/track/uetq.d.cts.map +1 -0
  58. package/dist/track/uetq.d.mts +111 -0
  59. package/dist/track/uetq.d.mts.map +1 -0
  60. package/dist/track/uetq.mjs +78 -0
  61. package/dist/track/uetq.mjs.map +1 -0
  62. package/dist/utils/sha256.cjs +19 -0
  63. package/dist/utils/sha256.cjs.map +1 -0
  64. package/dist/utils/sha256.d.cts +13 -0
  65. package/dist/utils/sha256.d.cts.map +1 -0
  66. package/dist/utils/sha256.d.mts +13 -0
  67. package/dist/utils/sha256.d.mts.map +1 -0
  68. package/dist/utils/sha256.mjs +18 -0
  69. package/dist/utils/sha256.mjs.map +1 -0
  70. package/dist/web/index.cjs +1 -1
  71. package/dist/web/index.cjs.map +1 -1
  72. package/dist/web/index.d.cts.map +1 -1
  73. package/dist/web/index.d.mts.map +1 -1
  74. package/dist/web/index.mjs +2 -2
  75. package/dist/web/index.mjs.map +1 -1
  76. package/package.json +1 -1
@@ -0,0 +1,70 @@
1
+ import { UpdateVisitorDTO } from "../schema/index.cjs";
2
+ import { EventName, TrackName, TrackProperties } from "../track/types.cjs";
3
+ import { UETConsent, UETQ } from "../track/uetq.cjs";
4
+ //#region src/third-parties/microsoft-uet.d.ts
5
+ declare global {
6
+ interface Window {
7
+ /**
8
+ * Defined by the UET snippet before `bat.js` loads (as a plain array), so pushes made early
9
+ * are queued rather than lost. Undefined only where the snippet was never installed.
10
+ */
11
+ uetq?: UETQ;
12
+ }
13
+ }
14
+ /**
15
+ * Forward an internal track event to the Microsoft Advertising UET tag.
16
+ *
17
+ * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same
18
+ * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the
19
+ * internal name on both channels — see `mapUETEvent`.
20
+ *
21
+ * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the
22
+ * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in
23
+ * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views
24
+ * would report each page twice. The tag owns page loads; this tracker owns everything else.
25
+ */
26
+ declare function sendUETEvent<T extends EventName>(name: TrackName<T>, properties?: TrackProperties<T>, eventId?: string): void;
27
+ /**
28
+ * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a
29
+ * conversion to an ad click without a third-party cookie. A `set` applies to every event fired
30
+ * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own
31
+ * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
32
+ * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
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`.
36
+ */
37
+ declare function setUETUser(): ({ user_data }: UpdateVisitorDTO) => void;
38
+ /**
39
+ * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
40
+ * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the
41
+ * tag neither writes its cookies nor reads the `msclkid` from the URL.
42
+ * https://help.ads.microsoft.com/#apex/ads/en/60119/1
43
+ */
44
+ declare function setUETConsent(mode: 'default' | 'update', consent: UETConsent): void;
45
+ /**
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
61
+ */
62
+ declare function sendUETIdSync(options: {
63
+ customerId: string | number;
64
+ visitorId: string;
65
+ /** The signed-in user's id, raw; hashed before it leaves the page. */
66
+ userId?: string;
67
+ }): void;
68
+ //#endregion
69
+ export { sendUETEvent, sendUETIdSync, setUETConsent, setUETUser };
70
+ //# sourceMappingURL=microsoft-uet.d.cts.map
@@ -0,0 +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"}
@@ -0,0 +1,70 @@
1
+ import { UpdateVisitorDTO } from "../schema/index.mjs";
2
+ import { EventName, TrackName, TrackProperties } from "../track/types.mjs";
3
+ import { UETConsent, UETQ } from "../track/uetq.mjs";
4
+ //#region src/third-parties/microsoft-uet.d.ts
5
+ declare global {
6
+ interface Window {
7
+ /**
8
+ * Defined by the UET snippet before `bat.js` loads (as a plain array), so pushes made early
9
+ * are queued rather than lost. Undefined only where the snippet was never installed.
10
+ */
11
+ uetq?: UETQ;
12
+ }
13
+ }
14
+ /**
15
+ * Forward an internal track event to the Microsoft Advertising UET tag.
16
+ *
17
+ * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same
18
+ * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the
19
+ * internal name on both channels — see `mapUETEvent`.
20
+ *
21
+ * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the
22
+ * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in
23
+ * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views
24
+ * would report each page twice. The tag owns page loads; this tracker owns everything else.
25
+ */
26
+ declare function sendUETEvent<T extends EventName>(name: TrackName<T>, properties?: TrackProperties<T>, eventId?: string): void;
27
+ /**
28
+ * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a
29
+ * conversion to an ad click without a third-party cookie. A `set` applies to every event fired
30
+ * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own
31
+ * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
32
+ * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
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`.
36
+ */
37
+ declare function setUETUser(): ({ user_data }: UpdateVisitorDTO) => void;
38
+ /**
39
+ * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
40
+ * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the
41
+ * tag neither writes its cookies nor reads the `msclkid` from the URL.
42
+ * https://help.ads.microsoft.com/#apex/ads/en/60119/1
43
+ */
44
+ declare function setUETConsent(mode: 'default' | 'update', consent: UETConsent): void;
45
+ /**
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
61
+ */
62
+ declare function sendUETIdSync(options: {
63
+ customerId: string | number;
64
+ visitorId: string;
65
+ /** The signed-in user's id, raw; hashed before it leaves the page. */
66
+ userId?: string;
67
+ }): void;
68
+ //#endregion
69
+ export { sendUETEvent, sendUETIdSync, setUETConsent, setUETUser };
70
+ //# sourceMappingURL=microsoft-uet.d.mts.map
@@ -0,0 +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"}
@@ -0,0 +1,115 @@
1
+ import { getFirst } from "../utils/field.mjs";
2
+ import { mapUETEvent } from "../track/uetq.mjs";
3
+ import { sha256 } from "../utils/sha256.mjs";
4
+ //#region src/third-parties/microsoft-uet.ts
5
+ const metrics = [
6
+ "CLS",
7
+ "FCP",
8
+ "FID",
9
+ "INP",
10
+ "LCP",
11
+ "TTFB"
12
+ ];
13
+ /** Drop `undefined` fields: bat.js validates every key it receives, and `undefined` is a value. */
14
+ function clean(value) {
15
+ return JSON.parse(JSON.stringify(value));
16
+ }
17
+ function isLocal() {
18
+ return window.location.host.includes("127.0.0.1") || window.location.host.includes("localhost");
19
+ }
20
+ /**
21
+ * Forward an internal track event to the Microsoft Advertising UET tag.
22
+ *
23
+ * `eventId` is reused as UET's `event_id` so the browser event deduplicates against the same
24
+ * conversion sent through the Conversions API (`sendMicrosoftEvents`). The action name is the
25
+ * internal name on both channels — see `mapUETEvent`.
26
+ *
27
+ * `page_view` is not forwarded: bat.js turns that action into its page-load beacon, which the
28
+ * snippet already fires on load and, with `enableAutoSpaTracking: true` (the setting in
29
+ * Microsoft's generated snippet), on every history change — forwarding the SDK's own page views
30
+ * would report each page twice. The tag owns page loads; this tracker owns everything else.
31
+ */
32
+ function sendUETEvent(name, properties, eventId) {
33
+ if (typeof window === "undefined" || !window.uetq) {
34
+ console.warn("uetq has not been initialized");
35
+ return;
36
+ }
37
+ if (metrics.includes(name) || name === "page_view") return;
38
+ if (isLocal()) return;
39
+ const [action, params] = mapUETEvent(name, properties, eventId);
40
+ window.uetq.push("event", action, clean(params));
41
+ }
42
+ /**
43
+ * Enhanced conversions: hand the tag the visitor's email and phone so Microsoft can match a
44
+ * conversion to an ad click without a third-party cookie. A `set` applies to every event fired
45
+ * from the page afterwards, `pageLoad` included, and the tag fires a `pid` beacon on its own
46
+ * when the page load already went out. Values are sent raw — the tag normalizes and SHA-256
47
+ * hashes them in the browser (bat.js `validatePid`) before anything leaves the page.
48
+ *
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`.
51
+ */
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
+ };
66
+ }
67
+ /**
68
+ * UET consent mode. Push `default` before the tag loads (the tag processes `consent` early, ahead
69
+ * of the queued events) and `update` when the visitor decides. With `ad_storage: 'denied'` the
70
+ * tag neither writes its cookies nor reads the `msclkid` from the URL.
71
+ * https://help.ads.microsoft.com/#apex/ads/en/60119/1
72
+ */
73
+ function setUETConsent(mode, consent) {
74
+ if (typeof window === "undefined") return;
75
+ window.uetq ??= [];
76
+ window.uetq.push("consent", mode, consent);
77
+ }
78
+ /**
79
+ * The Conversions API's client-side ID Sync pixel: ties the visitor id sent server-side as
80
+ * `anonymousId` to Microsoft's own identifier, so server events can be attributed across
81
+ * contexts (view-through, audiences). Microsoft requires it for remarketing built from CAPI
82
+ * events and recommends it for measurement quality; fire it at least once per session, ideally
83
+ * on the first page view. A pixel rather than a fetch on purpose: Microsoft must observe the
84
+ * browser context of the sync, which a server-side call cannot carry.
85
+ *
86
+ * `customerId` is the Microsoft Advertising customer id (`cid` in the UI's URLs) — not the UET
87
+ * tag id. `visitorId` is the SDK visitor id (`getVisitor().id`), which is what
88
+ * `sendMicrosoftEvents` sends as `anonymousId`; Microsoft requires the two to be equal.
89
+ * `userId` is the raw signed-in user id: it is SHA-256 hashed here, exactly as the server sender
90
+ * hashes it into `externalId`, so the pair matches as well. Hashing is asynchronous, so the
91
+ * pixel fires once the digest resolves; where SubtleCrypto is unavailable (an insecure context)
92
+ * the pixel goes out without `UID` rather than with a raw id.
93
+ * https://learn.microsoft.com/en-us/advertising/guides/uet-conversion-api-integration#id-sync-and-why-it-matters
94
+ */
95
+ function sendUETIdSync(options) {
96
+ if (typeof window === "undefined" || typeof Image === "undefined") return;
97
+ if (isLocal()) return;
98
+ const fire = (uid) => {
99
+ const params = new URLSearchParams({
100
+ Red3: `BACID_${options.customerId}`,
101
+ VID: options.visitorId
102
+ });
103
+ if (uid) params.set("UID", uid);
104
+ new Image().src = `https://c.bing.com/c.gif?${params.toString()}`;
105
+ };
106
+ if (!options.userId) {
107
+ fire();
108
+ return;
109
+ }
110
+ sha256(options.userId).then(fire, () => fire());
111
+ }
112
+ //#endregion
113
+ export { sendUETEvent, sendUETIdSync, setUETConsent, setUETUser };
114
+
115
+ //# sourceMappingURL=microsoft-uet.mjs.map
@@ -0,0 +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,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_utils_field = require("../utils/field.cjs");
3
3
  const require_track_oaiq = require("../track/oaiq.cjs");
4
+ const require_utils_sha256 = require("../utils/sha256.cjs");
4
5
  //#region src/third-parties/openai-pixel.ts
5
6
  /** Drop `undefined` fields so the SDK only receives populated values. */
6
7
  function clean(value) {
@@ -26,12 +27,6 @@ function sendOpenAIEvent(name, properties, eventId) {
26
27
  });
27
28
  else window.oaiq("measure", type, clean(data), { event_id: eventId });
28
29
  }
29
- /** SHA-256 hex digest, lowercase — the format OpenAI expects for hashed identity fields. */
30
- async function sha256(value) {
31
- const bytes = new TextEncoder().encode(value);
32
- const digest = await crypto.subtle.digest("SHA-256", bytes);
33
- return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
34
- }
35
30
  /**
36
31
  * Re-initialize the pixel with hashed user identity for better conversion matching. Email and
37
32
  * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,
@@ -60,7 +55,7 @@ function setOpenAIUser(pixelId) {
60
55
  })
61
56
  });
62
57
  };
63
- Promise.all([email ? sha256(email) : void 0, user_id ? sha256(user_id) : void 0]).then(([email_sha256, external_id_sha256]) => init({
58
+ Promise.all([email ? require_utils_sha256.sha256(email) : void 0, user_id ? require_utils_sha256.sha256(user_id) : void 0]).then(([email_sha256, external_id_sha256]) => init({
64
59
  email_sha256,
65
60
  external_id_sha256
66
61
  })).catch(() => init({}));
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.cjs","names":["NON_AD_EVENTS","mapOAIEvent","getFirst"],"sources":["../../src/third-parties/openai-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { NON_AD_EVENTS, type OAIQ, type OAIQUser, mapOAIEvent } from '../track/oaiq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ndeclare global {\n interface Window {\n /** Undefined until the OpenAI pixel script has loaded. */\n oaiq?: OAIQ['oaiq'];\n }\n}\n\n/** Drop `undefined` fields so the SDK only receives populated values. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\n/**\n * Forward an internal track event to the OpenAI measurement pixel.\n * `eventId` is reused as the OpenAI `event_id` so browser events deduplicate against the\n * Conversions API. https://developers.openai.com/ads/measurement-pixel\n */\nexport function sendOpenAIEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n if (NON_AD_EVENTS.includes(name)) return;\n if (window.location.host.includes('127.0.0.1')) return;\n if (window.location.host.includes('localhost')) return;\n\n const { type, data } = mapOAIEvent(name, properties);\n if (type === 'custom') {\n window.oaiq('measure', 'custom', clean(data), { event_id: eventId, custom_event_name: name });\n } else {\n window.oaiq('measure', type, clean(data), { event_id: eventId });\n }\n}\n\n/** SHA-256 hex digest, lowercase — the format OpenAI expects for hashed identity fields. */\nasync function sha256(value: string): Promise<string> {\n const bytes = new TextEncoder().encode(value);\n const digest = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Re-initialize the pixel with hashed user identity for better conversion matching. Email and\n * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,\n * so the `init` call is deferred until the digests resolve.\n */\nexport function setOpenAIUser(pixelId: string) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n // Capture the narrowed reference so the deferred `init` closure keeps it non-optional.\n const oaiq = window.oaiq;\n\n const email = getFirst(user_data?.email)?.trim().toLowerCase();\n const address = getFirst(user_data?.address);\n\n const base: OAIQUser = {\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n };\n\n const init = (hashed: Partial<OAIQUser>) => {\n oaiq('init', { pixelId, user: clean({ ...base, ...hashed }) });\n };\n\n Promise.all([email ? sha256(email) : undefined, user_id ? sha256(user_id) : undefined])\n .then(([email_sha256, external_id_sha256]) => init({ email_sha256, external_id_sha256 }))\n .catch(() => init({}));\n };\n}\n"],"mappings":";;;;;AAcA,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;AAOA,SAAgB,gBACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAIA,mBAAAA,cAAc,SAAS,IAAI,GAAG;CAClC,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,EAAE,MAAM,SAASC,mBAAAA,YAAY,MAAM,UAAU;CACnD,IAAI,SAAS,UACX,OAAO,KAAK,WAAW,UAAU,MAAM,IAAI,GAAG;EAAE,UAAU;EAAS,mBAAmB;CAAK,CAAC;MAE5F,OAAO,KAAK,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE,UAAU,QAAQ,CAAC;AAEnE;;AAGA,eAAe,OAAO,OAAgC;CACpD,MAAM,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK;CAC5C,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;CAC1D,OAAO,MAAM,KAAK,IAAI,WAAW,MAAM,CAAC,CAAC,CACtC,KAAK,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CACjD,KAAK,EAAE;AACZ;;;;;;AAOA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQC,oBAAAA,SAAS,WAAW,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;EAC7D,MAAM,UAAUA,oBAAAA,SAAS,WAAW,OAAO;EAE3C,MAAM,OAAiB;GACrB,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;GAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;GACxC,UAAU,SAAS;EACrB;EAEA,MAAM,QAAQ,WAA8B;GAC1C,KAAK,QAAQ;IAAE;IAAS,MAAM,MAAM;KAAE,GAAG;KAAM,GAAG;IAAO,CAAC;GAAE,CAAC;EAC/D;EAEA,QAAQ,IAAI,CAAC,QAAQ,OAAO,KAAK,IAAI,KAAA,GAAW,UAAU,OAAO,OAAO,IAAI,KAAA,CAAS,CAAC,CAAC,CACpF,MAAM,CAAC,cAAc,wBAAwB,KAAK;GAAE;GAAc;EAAmB,CAAC,CAAC,CAAC,CACxF,YAAY,KAAK,CAAC,CAAC,CAAC;CACzB;AACF"}
1
+ {"version":3,"file":"openai-pixel.cjs","names":["NON_AD_EVENTS","mapOAIEvent","getFirst","sha256"],"sources":["../../src/third-parties/openai-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { NON_AD_EVENTS, type OAIQ, type OAIQUser, mapOAIEvent } from '../track/oaiq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\n\ndeclare global {\n interface Window {\n /** Undefined until the OpenAI pixel script has loaded. */\n oaiq?: OAIQ['oaiq'];\n }\n}\n\n/** Drop `undefined` fields so the SDK only receives populated values. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\n/**\n * Forward an internal track event to the OpenAI measurement pixel.\n * `eventId` is reused as the OpenAI `event_id` so browser events deduplicate against the\n * Conversions API. https://developers.openai.com/ads/measurement-pixel\n */\nexport function sendOpenAIEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n if (NON_AD_EVENTS.includes(name)) return;\n if (window.location.host.includes('127.0.0.1')) return;\n if (window.location.host.includes('localhost')) return;\n\n const { type, data } = mapOAIEvent(name, properties);\n if (type === 'custom') {\n window.oaiq('measure', 'custom', clean(data), { event_id: eventId, custom_event_name: name });\n } else {\n window.oaiq('measure', type, clean(data), { event_id: eventId });\n }\n}\n\n/**\n * Re-initialize the pixel with hashed user identity for better conversion matching. Email and\n * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,\n * so the `init` call is deferred until the digests resolve.\n */\nexport function setOpenAIUser(pixelId: string) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n // Capture the narrowed reference so the deferred `init` closure keeps it non-optional.\n const oaiq = window.oaiq;\n\n const email = getFirst(user_data?.email)?.trim().toLowerCase();\n const address = getFirst(user_data?.address);\n\n const base: OAIQUser = {\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n };\n\n const init = (hashed: Partial<OAIQUser>) => {\n oaiq('init', { pixelId, user: clean({ ...base, ...hashed }) });\n };\n\n Promise.all([email ? sha256(email) : undefined, user_id ? sha256(user_id) : undefined])\n .then(([email_sha256, external_id_sha256]) => init({ email_sha256, external_id_sha256 }))\n .catch(() => init({}));\n };\n}\n"],"mappings":";;;;;;AAeA,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;AAOA,SAAgB,gBACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAIA,mBAAAA,cAAc,SAAS,IAAI,GAAG;CAClC,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,EAAE,MAAM,SAASC,mBAAAA,YAAY,MAAM,UAAU;CACnD,IAAI,SAAS,UACX,OAAO,KAAK,WAAW,UAAU,MAAM,IAAI,GAAG;EAAE,UAAU;EAAS,mBAAmB;CAAK,CAAC;MAE5F,OAAO,KAAK,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE,UAAU,QAAQ,CAAC;AAEnE;;;;;;AAOA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQC,oBAAAA,SAAS,WAAW,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;EAC7D,MAAM,UAAUA,oBAAAA,SAAS,WAAW,OAAO;EAE3C,MAAM,OAAiB;GACrB,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;GAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;GACxC,UAAU,SAAS;EACrB;EAEA,MAAM,QAAQ,WAA8B;GAC1C,KAAK,QAAQ;IAAE;IAAS,MAAM,MAAM;KAAE,GAAG;KAAM,GAAG;IAAO,CAAC;GAAE,CAAC;EAC/D;EAEA,QAAQ,IAAI,CAAC,QAAQC,qBAAAA,OAAO,KAAK,IAAI,KAAA,GAAW,UAAUA,qBAAAA,OAAO,OAAO,IAAI,KAAA,CAAS,CAAC,CAAC,CACpF,MAAM,CAAC,cAAc,wBAAwB,KAAK;GAAE;GAAc;EAAmB,CAAC,CAAC,CAAC,CACxF,YAAY,KAAK,CAAC,CAAC,CAAC;CACzB;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.d.cts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAKQ;YACI;;IAER,OAAO;;;;;;;;iBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;iBAgCc,cAAc,qBAAe,SAAA,aACX"}
1
+ {"version":3,"file":"openai-pixel.d.cts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAMQ;YACI;;IAER,OAAO;;;;;;;;iBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;iBAuBc,cAAc,qBAAe,SAAA,aACX"}
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.d.mts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAKQ;YACI;;IAER,OAAO;;;;;;;;iBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;iBAgCc,cAAc,qBAAe,SAAA,aACX"}
1
+ {"version":3,"file":"openai-pixel.d.mts","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"mappings":";;;;QAMQ;YACI;;IAER,OAAO;;;;;;;;iBAeK,gBAAgB,UAAU,WACxC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B;;;;;;iBAuBc,cAAc,qBAAe,SAAA,aACX"}
@@ -1,5 +1,6 @@
1
1
  import { getFirst } from "../utils/field.mjs";
2
2
  import { NON_AD_EVENTS, mapOAIEvent } from "../track/oaiq.mjs";
3
+ import { sha256 } from "../utils/sha256.mjs";
3
4
  //#region src/third-parties/openai-pixel.ts
4
5
  /** Drop `undefined` fields so the SDK only receives populated values. */
5
6
  function clean(value) {
@@ -25,12 +26,6 @@ function sendOpenAIEvent(name, properties, eventId) {
25
26
  });
26
27
  else window.oaiq("measure", type, clean(data), { event_id: eventId });
27
28
  }
28
- /** SHA-256 hex digest, lowercase — the format OpenAI expects for hashed identity fields. */
29
- async function sha256(value) {
30
- const bytes = new TextEncoder().encode(value);
31
- const digest = await crypto.subtle.digest("SHA-256", bytes);
32
- return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
33
- }
34
29
  /**
35
30
  * Re-initialize the pixel with hashed user identity for better conversion matching. Email and
36
31
  * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,
@@ -1 +1 @@
1
- {"version":3,"file":"openai-pixel.mjs","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { NON_AD_EVENTS, type OAIQ, type OAIQUser, mapOAIEvent } from '../track/oaiq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ndeclare global {\n interface Window {\n /** Undefined until the OpenAI pixel script has loaded. */\n oaiq?: OAIQ['oaiq'];\n }\n}\n\n/** Drop `undefined` fields so the SDK only receives populated values. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\n/**\n * Forward an internal track event to the OpenAI measurement pixel.\n * `eventId` is reused as the OpenAI `event_id` so browser events deduplicate against the\n * Conversions API. https://developers.openai.com/ads/measurement-pixel\n */\nexport function sendOpenAIEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n if (NON_AD_EVENTS.includes(name)) return;\n if (window.location.host.includes('127.0.0.1')) return;\n if (window.location.host.includes('localhost')) return;\n\n const { type, data } = mapOAIEvent(name, properties);\n if (type === 'custom') {\n window.oaiq('measure', 'custom', clean(data), { event_id: eventId, custom_event_name: name });\n } else {\n window.oaiq('measure', type, clean(data), { event_id: eventId });\n }\n}\n\n/** SHA-256 hex digest, lowercase — the format OpenAI expects for hashed identity fields. */\nasync function sha256(value: string): Promise<string> {\n const bytes = new TextEncoder().encode(value);\n const digest = await crypto.subtle.digest('SHA-256', bytes);\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n}\n\n/**\n * Re-initialize the pixel with hashed user identity for better conversion matching. Email and\n * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,\n * so the `init` call is deferred until the digests resolve.\n */\nexport function setOpenAIUser(pixelId: string) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n // Capture the narrowed reference so the deferred `init` closure keeps it non-optional.\n const oaiq = window.oaiq;\n\n const email = getFirst(user_data?.email)?.trim().toLowerCase();\n const address = getFirst(user_data?.address);\n\n const base: OAIQUser = {\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n };\n\n const init = (hashed: Partial<OAIQUser>) => {\n oaiq('init', { pixelId, user: clean({ ...base, ...hashed }) });\n };\n\n Promise.all([email ? sha256(email) : undefined, user_id ? sha256(user_id) : undefined])\n .then(([email_sha256, external_id_sha256]) => init({ email_sha256, external_id_sha256 }))\n .catch(() => init({}));\n };\n}\n"],"mappings":";;;;AAcA,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;AAOA,SAAgB,gBACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAI,cAAc,SAAS,IAAI,GAAG;CAClC,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,EAAE,MAAM,SAAS,YAAY,MAAM,UAAU;CACnD,IAAI,SAAS,UACX,OAAO,KAAK,WAAW,UAAU,MAAM,IAAI,GAAG;EAAE,UAAU;EAAS,mBAAmB;CAAK,CAAC;MAE5F,OAAO,KAAK,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE,UAAU,QAAQ,CAAC;AAEnE;;AAGA,eAAe,OAAO,OAAgC;CACpD,MAAM,QAAQ,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK;CAC5C,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,KAAK;CAC1D,OAAO,MAAM,KAAK,IAAI,WAAW,MAAM,CAAC,CAAC,CACtC,KAAK,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CACjD,KAAK,EAAE;AACZ;;;;;;AAOA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQ,SAAS,WAAW,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;EAC7D,MAAM,UAAU,SAAS,WAAW,OAAO;EAE3C,MAAM,OAAiB;GACrB,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;GAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;GACxC,UAAU,SAAS;EACrB;EAEA,MAAM,QAAQ,WAA8B;GAC1C,KAAK,QAAQ;IAAE;IAAS,MAAM,MAAM;KAAE,GAAG;KAAM,GAAG;IAAO,CAAC;GAAE,CAAC;EAC/D;EAEA,QAAQ,IAAI,CAAC,QAAQ,OAAO,KAAK,IAAI,KAAA,GAAW,UAAU,OAAO,OAAO,IAAI,KAAA,CAAS,CAAC,CAAC,CACpF,MAAM,CAAC,cAAc,wBAAwB,KAAK;GAAE;GAAc;EAAmB,CAAC,CAAC,CAAC,CACxF,YAAY,KAAK,CAAC,CAAC,CAAC;CACzB;AACF"}
1
+ {"version":3,"file":"openai-pixel.mjs","names":[],"sources":["../../src/third-parties/openai-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { NON_AD_EVENTS, type OAIQ, type OAIQUser, mapOAIEvent } from '../track/oaiq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\nimport { sha256 } from '../utils/sha256';\n\ndeclare global {\n interface Window {\n /** Undefined until the OpenAI pixel script has loaded. */\n oaiq?: OAIQ['oaiq'];\n }\n}\n\n/** Drop `undefined` fields so the SDK only receives populated values. */\n// oxlint-disable-next-line @typescript-eslint/no-explicit-any\nfunction clean(value: unknown): any {\n return JSON.parse(JSON.stringify(value));\n}\n\n/**\n * Forward an internal track event to the OpenAI measurement pixel.\n * `eventId` is reused as the OpenAI `event_id` so browser events deduplicate against the\n * Conversions API. https://developers.openai.com/ads/measurement-pixel\n */\nexport function sendOpenAIEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n) {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n if (NON_AD_EVENTS.includes(name)) return;\n if (window.location.host.includes('127.0.0.1')) return;\n if (window.location.host.includes('localhost')) return;\n\n const { type, data } = mapOAIEvent(name, properties);\n if (type === 'custom') {\n window.oaiq('measure', 'custom', clean(data), { event_id: eventId, custom_event_name: name });\n } else {\n window.oaiq('measure', type, clean(data), { event_id: eventId });\n }\n}\n\n/**\n * Re-initialize the pixel with hashed user identity for better conversion matching. Email and\n * external id are hashed client-side; geographic fields are sent raw. Hashing is asynchronous,\n * so the `init` call is deferred until the digests resolve.\n */\nexport function setOpenAIUser(pixelId: string) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.oaiq) {\n console.warn('oaiq has not been initialized');\n return;\n }\n // Capture the narrowed reference so the deferred `init` closure keeps it non-optional.\n const oaiq = window.oaiq;\n\n const email = getFirst(user_data?.email)?.trim().toLowerCase();\n const address = getFirst(user_data?.address);\n\n const base: OAIQUser = {\n country: address?.country?.trim().toUpperCase(),\n city: address?.city?.trim().toLowerCase(),\n zip_code: address?.postal_code,\n };\n\n const init = (hashed: Partial<OAIQUser>) => {\n oaiq('init', { pixelId, user: clean({ ...base, ...hashed }) });\n };\n\n Promise.all([email ? sha256(email) : undefined, user_id ? sha256(user_id) : undefined])\n .then(([email_sha256, external_id_sha256]) => init({ email_sha256, external_id_sha256 }))\n .catch(() => init({}));\n };\n}\n"],"mappings":";;;;;AAeA,SAAS,MAAM,OAAqB;CAClC,OAAO,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;AACzC;;;;;;AAOA,SAAgB,gBACd,MACA,YACA,SACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;EACjD,QAAQ,KAAK,+BAA+B;EAC5C;CACF;CACA,IAAI,cAAc,SAAS,IAAI,GAAG;CAClC,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,EAAE,MAAM,SAAS,YAAY,MAAM,UAAU;CACnD,IAAI,SAAS,UACX,OAAO,KAAK,WAAW,UAAU,MAAM,IAAI,GAAG;EAAE,UAAU;EAAS,mBAAmB;CAAK,CAAC;MAE5F,OAAO,KAAK,WAAW,MAAM,MAAM,IAAI,GAAG,EAAE,UAAU,QAAQ,CAAC;AAEnE;;;;;;AAOA,SAAgB,cAAc,SAAiB;CAC7C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,MAAM;GACjD,QAAQ,KAAK,+BAA+B;GAC5C;EACF;EAEA,MAAM,OAAO,OAAO;EAEpB,MAAM,QAAQ,SAAS,WAAW,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,YAAY;EAC7D,MAAM,UAAU,SAAS,WAAW,OAAO;EAE3C,MAAM,OAAiB;GACrB,SAAS,SAAS,SAAS,KAAK,CAAC,CAAC,YAAY;GAC9C,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,YAAY;GACxC,UAAU,SAAS;EACrB;EAEA,MAAM,QAAQ,WAA8B;GAC1C,KAAK,QAAQ;IAAE;IAAS,MAAM,MAAM;KAAE,GAAG;KAAM,GAAG;IAAO,CAAC;GAAE,CAAC;EAC/D;EAEA,QAAQ,IAAI,CAAC,QAAQ,OAAO,KAAK,IAAI,KAAA,GAAW,UAAU,OAAO,OAAO,IAAI,KAAA,CAAS,CAAC,CAAC,CACpF,MAAM,CAAC,cAAc,wBAAwB,KAAK;GAAE;GAAc;EAAmB,CAAC,CAAC,CAAC,CACxF,YAAY,KAAK,CAAC,CAAC,CAAC;CACzB;AACF"}
@@ -0,0 +1,80 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ //#region src/track/uetq.ts
3
+ /** GA4 `Item` → UET `items[]` entry. Undefined fields are left for the caller to strip. */
4
+ function mapItems(items) {
5
+ if (!items || items.length === 0) return void 0;
6
+ return items.map((item) => ({
7
+ id: item.item_id,
8
+ name: item.item_name,
9
+ brand: item.item_brand,
10
+ category: item.item_category,
11
+ variant: item.item_variant,
12
+ price: item.price,
13
+ quantity: item.quantity,
14
+ list_name: item.item_list_name,
15
+ list_position: item.index,
16
+ location_id: item.location_id
17
+ }));
18
+ }
19
+ function number(value) {
20
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
21
+ }
22
+ function string(value) {
23
+ return typeof value === "string" && value !== "" ? value : void 0;
24
+ }
25
+ function currency(value) {
26
+ return string(value)?.toUpperCase();
27
+ }
28
+ const PAGE_TYPES = [
29
+ "home",
30
+ "searchresults",
31
+ "category",
32
+ "product",
33
+ "cart",
34
+ "purchase",
35
+ "other"
36
+ ];
37
+ function pageType(value) {
38
+ const lowered = string(value)?.toLowerCase();
39
+ return lowered && PAGE_TYPES.includes(lowered) ? lowered : void 0;
40
+ }
41
+ /**
42
+ * Map an internal track event onto a UET action and its parameters.
43
+ *
44
+ * The action is always the internal name: for the GA4-named standard events that is also the
45
+ * name UET knows, and for everything else it is the custom action a UET event goal is configured
46
+ * with. Keeping the name identical on both channels is what lets the Conversions API sender
47
+ * (`eventName`) deduplicate against the browser (`event_id`).
48
+ *
49
+ * `page_view` is special to the tag: bat.js routes that action to its page-load beacon (the same
50
+ * one the snippet fires on load and `enableAutoSpaTracking` fires on history changes), so the
51
+ * browser tracker does not forward it — see `sendUETEvent`.
52
+ */
53
+ function mapUETEvent(name, properties, eventId) {
54
+ const p = properties ?? {};
55
+ const params = { event_id: eventId };
56
+ params.revenue_value = number(p.value) ?? number(p.revenue_value);
57
+ params.currency = currency(p.currency);
58
+ params.transaction_id = string(p.transaction_id);
59
+ params.tax = number(p.tax);
60
+ params.shipping = number(p.shipping);
61
+ params.coupon = string(p.coupon);
62
+ params.items = Array.isArray(p.items) ? mapItems(p.items) : void 0;
63
+ params.search_term = string(p.search_term);
64
+ params.method = string(p.method);
65
+ params.content_type = string(p.content_type);
66
+ params.content_id = string(p.content_id);
67
+ params.event_category = string(p.event_category);
68
+ params.event_label = string(p.event_label);
69
+ params.event_value = number(p.event_value);
70
+ params.ecomm_pagetype = pageType(p.ecomm_pagetype);
71
+ params.ecomm_prodid = params.ecomm_pagetype ? Array.isArray(p.ecomm_prodid) ? p.ecomm_prodid : string(p.ecomm_prodid) : void 0;
72
+ params.ecomm_totalvalue = number(p.ecomm_totalvalue);
73
+ params.ecomm_category = string(p.ecomm_category);
74
+ return [name, params];
75
+ }
76
+ //#endregion
77
+ exports.mapItems = mapItems;
78
+ exports.mapUETEvent = mapUETEvent;
79
+
80
+ //# sourceMappingURL=uetq.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uetq.cjs","names":[],"sources":["../../src/track/uetq.ts"],"sourcesContent":["import type { Item } from './gtag';\nimport type { EventName, TrackName, TrackProperties } from './types';\n\n/**\n * Microsoft Advertising Universal Event Tracking (UET) — the `uetq` queue that `bat.js` drains.\n *\n * UET's modern event API is gtag-shaped: `uetq.push('event', '<action>', { ...params })`, and the\n * actions it knows (`purchase`, `add_to_cart`, `sign_up`, …) are GA4's recommended event names.\n * Since this SDK's own event names are GA4's too, a standard event maps onto UET under the same\n * name — only the parameter spellings differ (`value` → `revenue_value`, `Item` → `items[]`). An\n * unknown name goes out unchanged as a custom event, which is what a UET *event goal* matches on.\n *\n * Parameter names below are taken from `bat.js` itself (`knownParams`); the tag silently drops\n * any key it does not know, so an unmapped property is lost rather than an error. (bat.js's\n * `knownEvents` list is not an allow-list — the tag only uses it to inherit page-level `set`\n * parameters onto the events that carry them.)\n *\n * https://help.ads.microsoft.com/#apex/ads/en/56684/2 (custom events)\n * https://help.ads.microsoft.com/#apex/ads/en/56910/1 (retail / dynamic remarketing)\n * https://learn.microsoft.com/en-us/advertising/msa-help/hlp_ba_conc_uet_enhancedconversions\n */\n\n/** The retail `ecomm_pagetype` values UET validates against. */\nexport type UETPageType =\n | 'home'\n | 'searchresults'\n | 'category'\n | 'product'\n | 'cart'\n | 'purchase'\n | 'other';\n\n/** One entry of the `items` array (`items.*` in bat.js's `knownParams`). */\nexport interface UETItem {\n id?: string;\n name?: string;\n brand?: string;\n category?: string;\n variant?: string;\n price?: number;\n quantity?: number;\n list_name?: string;\n list_position?: number;\n location_id?: string;\n}\n\n/**\n * Enhanced conversions identifiers. Sent RAW: the tag normalizes and SHA-256 hashes them in the\n * browser before anything leaves the page (bat.js `validatePid`), and rejects a phone that is\n * not E.164 after stripping separators.\n */\nexport interface UETPid {\n em?: string;\n ph?: string;\n}\n\n/** The parameters of one `uetq.push('event', action, params)` call. */\nexport interface UETEventParams {\n /** Deduplicates against the same event sent through the Conversions API. */\n event_id?: string;\n event_category?: string;\n event_label?: string;\n event_value?: number;\n /** Variable revenue (`gv` on the wire). */\n revenue_value?: number;\n /** ISO 4217, uppercase (`gc` on the wire). */\n currency?: string;\n transaction_id?: string;\n tax?: number;\n shipping?: number;\n coupon?: string;\n items?: UETItem[];\n search_term?: string;\n method?: string;\n content_type?: string;\n content_id?: string;\n ecomm_prodid?: string | string[];\n ecomm_pagetype?: UETPageType;\n ecomm_totalvalue?: number;\n ecomm_category?: string;\n pid?: UETPid;\n}\n\nexport type UETConsent = {\n ad_storage: 'granted' | 'denied';\n /**\n * `default` only: milliseconds to hold events for an `update` before acting on the default,\n * so a CMP that answers quickly does not cost the landing page. bat.js reads exactly this\n * lowercase key (some community snippets spell it `Wait_for_update`, which it ignores) and\n * caps it at 10,000.\n */\n wait_for_update?: number;\n};\n\n/**\n * `window.uetq` is a plain array until `bat.js` loads (the snippet does `w[u] = w[u] || []`), then\n * a `UET` instance that drains the queue; both expose `push`, which is all this SDK uses.\n */\nexport interface UETQ {\n push(command: 'event', action: string, params?: UETEventParams): void;\n push(command: 'pageLoad'): void;\n push(command: 'set', params: { pid?: UETPid }): void;\n push(command: 'consent', mode: 'default' | 'update', consent: UETConsent): void;\n}\n\n/** GA4 `Item` → UET `items[]` entry. Undefined fields are left for the caller to strip. */\nexport function mapItems(items?: Item[]): undefined | UETItem[] {\n if (!items || items.length === 0) return undefined;\n return items.map((item) => ({\n id: item.item_id,\n name: item.item_name,\n brand: item.item_brand,\n category: item.item_category,\n variant: item.item_variant,\n price: item.price,\n quantity: item.quantity,\n list_name: item.item_list_name,\n list_position: item.index,\n location_id: item.location_id,\n }));\n}\n\nfunction number(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\nfunction string(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined;\n}\n\nfunction currency(value: unknown): string | undefined {\n return string(value)?.toUpperCase();\n}\n\nconst PAGE_TYPES: string[] = [\n 'home',\n 'searchresults',\n 'category',\n 'product',\n 'cart',\n 'purchase',\n 'other',\n];\n\nfunction pageType(value: unknown): UETPageType | undefined {\n const lowered = string(value)?.toLowerCase();\n return lowered && PAGE_TYPES.includes(lowered) ? (lowered as UETPageType) : undefined;\n}\n\n/**\n * Map an internal track event onto a UET action and its parameters.\n *\n * The action is always the internal name: for the GA4-named standard events that is also the\n * name UET knows, and for everything else it is the custom action a UET event goal is configured\n * with. Keeping the name identical on both channels is what lets the Conversions API sender\n * (`eventName`) deduplicate against the browser (`event_id`).\n *\n * `page_view` is special to the tag: bat.js routes that action to its page-load beacon (the same\n * one the snippet fires on load and `enableAutoSpaTracking` fires on history changes), so the\n * browser tracker does not forward it — see `sendUETEvent`.\n */\nexport function mapUETEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n eventId?: string\n): [string, UETEventParams] {\n // A loose view of whatever the caller passed: every branch below reads only the keys it\n // knows and validates their types, so an event with foreign properties cannot leak them.\n const p = (properties ?? {}) as Record<string, unknown>;\n const params: UETEventParams = { event_id: eventId };\n\n // GA4's commerce parameters, spelled the UET way. `value` is GA4's revenue field and becomes\n // UET's variable revenue; every key here is one bat.js lists for the events that carry it.\n params.revenue_value = number(p.value) ?? number(p.revenue_value);\n params.currency = currency(p.currency);\n params.transaction_id = string(p.transaction_id);\n params.tax = number(p.tax);\n params.shipping = number(p.shipping);\n params.coupon = string(p.coupon);\n params.items = Array.isArray(p.items) ? mapItems(p.items as Item[]) : undefined;\n params.search_term = string(p.search_term);\n params.method = string(p.method);\n params.content_type = string(p.content_type);\n params.content_id = string(p.content_id);\n\n // The custom-event goal fields and the retail (dynamic remarketing) fields pass through under\n // their UET names, so a host can attach them to any event it defines.\n params.event_category = string(p.event_category);\n params.event_label = string(p.event_label);\n params.event_value = number(p.event_value);\n // bat.js throws on a `prodid` without a `pagetype` (`missingPageTypeException`), which would\n // cost the whole event; a product id on its own is dropped instead.\n params.ecomm_pagetype = pageType(p.ecomm_pagetype);\n params.ecomm_prodid = params.ecomm_pagetype\n ? Array.isArray(p.ecomm_prodid)\n ? (p.ecomm_prodid as string[])\n : string(p.ecomm_prodid)\n : undefined;\n params.ecomm_totalvalue = number(p.ecomm_totalvalue);\n params.ecomm_category = string(p.ecomm_category);\n\n return [name, params];\n}\n"],"mappings":";;;AA0GA,SAAgB,SAAS,OAAuC;CAC9D,IAAI,CAAC,SAAS,MAAM,WAAW,GAAG,OAAO,KAAA;CACzC,OAAO,MAAM,KAAK,UAAU;EAC1B,IAAI,KAAK;EACT,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,UAAU,KAAK;EACf,SAAS,KAAK;EACd,OAAO,KAAK;EACZ,UAAU,KAAK;EACf,WAAW,KAAK;EAChB,eAAe,KAAK;EACpB,aAAa,KAAK;CACpB,EAAE;AACJ;AAEA,SAAS,OAAO,OAAoC;CAClD,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACvE;AAEA,SAAS,OAAO,OAAoC;CAClD,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,KAAA;AAC7D;AAEA,SAAS,SAAS,OAAoC;CACpD,OAAO,OAAO,KAAK,CAAC,EAAE,YAAY;AACpC;AAEA,MAAM,aAAuB;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,SAAS,SAAS,OAAyC;CACzD,MAAM,UAAU,OAAO,KAAK,CAAC,EAAE,YAAY;CAC3C,OAAO,WAAW,WAAW,SAAS,OAAO,IAAK,UAA0B,KAAA;AAC9E;;;;;;;;;;;;;AAcA,SAAgB,YACd,MACA,YACA,SAC0B;CAG1B,MAAM,IAAK,cAAc,CAAC;CAC1B,MAAM,SAAyB,EAAE,UAAU,QAAQ;CAInD,OAAO,gBAAgB,OAAO,EAAE,KAAK,KAAK,OAAO,EAAE,aAAa;CAChE,OAAO,WAAW,SAAS,EAAE,QAAQ;CACrC,OAAO,iBAAiB,OAAO,EAAE,cAAc;CAC/C,OAAO,MAAM,OAAO,EAAE,GAAG;CACzB,OAAO,WAAW,OAAO,EAAE,QAAQ;CACnC,OAAO,SAAS,OAAO,EAAE,MAAM;CAC/B,OAAO,QAAQ,MAAM,QAAQ,EAAE,KAAK,IAAI,SAAS,EAAE,KAAe,IAAI,KAAA;CACtE,OAAO,cAAc,OAAO,EAAE,WAAW;CACzC,OAAO,SAAS,OAAO,EAAE,MAAM;CAC/B,OAAO,eAAe,OAAO,EAAE,YAAY;CAC3C,OAAO,aAAa,OAAO,EAAE,UAAU;CAIvC,OAAO,iBAAiB,OAAO,EAAE,cAAc;CAC/C,OAAO,cAAc,OAAO,EAAE,WAAW;CACzC,OAAO,cAAc,OAAO,EAAE,WAAW;CAGzC,OAAO,iBAAiB,SAAS,EAAE,cAAc;CACjD,OAAO,eAAe,OAAO,iBACzB,MAAM,QAAQ,EAAE,YAAY,IACzB,EAAE,eACH,OAAO,EAAE,YAAY,IACvB,KAAA;CACJ,OAAO,mBAAmB,OAAO,EAAE,gBAAgB;CACnD,OAAO,iBAAiB,OAAO,EAAE,cAAc;CAE/C,OAAO,CAAC,MAAM,MAAM;AACtB"}
@@ -0,0 +1,111 @@
1
+ import { Item } from "./gtag.cjs";
2
+ import { EventName, TrackName, TrackProperties } from "./types.cjs";
3
+ //#region src/track/uetq.d.ts
4
+ /**
5
+ * Microsoft Advertising Universal Event Tracking (UET) — the `uetq` queue that `bat.js` drains.
6
+ *
7
+ * UET's modern event API is gtag-shaped: `uetq.push('event', '<action>', { ...params })`, and the
8
+ * actions it knows (`purchase`, `add_to_cart`, `sign_up`, …) are GA4's recommended event names.
9
+ * Since this SDK's own event names are GA4's too, a standard event maps onto UET under the same
10
+ * name — only the parameter spellings differ (`value` → `revenue_value`, `Item` → `items[]`). An
11
+ * unknown name goes out unchanged as a custom event, which is what a UET *event goal* matches on.
12
+ *
13
+ * Parameter names below are taken from `bat.js` itself (`knownParams`); the tag silently drops
14
+ * any key it does not know, so an unmapped property is lost rather than an error. (bat.js's
15
+ * `knownEvents` list is not an allow-list — the tag only uses it to inherit page-level `set`
16
+ * parameters onto the events that carry them.)
17
+ *
18
+ * https://help.ads.microsoft.com/#apex/ads/en/56684/2 (custom events)
19
+ * https://help.ads.microsoft.com/#apex/ads/en/56910/1 (retail / dynamic remarketing)
20
+ * https://learn.microsoft.com/en-us/advertising/msa-help/hlp_ba_conc_uet_enhancedconversions
21
+ */
22
+ /** The retail `ecomm_pagetype` values UET validates against. */
23
+ type UETPageType = 'home' | 'searchresults' | 'category' | 'product' | 'cart' | 'purchase' | 'other';
24
+ /** One entry of the `items` array (`items.*` in bat.js's `knownParams`). */
25
+ interface UETItem {
26
+ id?: string;
27
+ name?: string;
28
+ brand?: string;
29
+ category?: string;
30
+ variant?: string;
31
+ price?: number;
32
+ quantity?: number;
33
+ list_name?: string;
34
+ list_position?: number;
35
+ location_id?: string;
36
+ }
37
+ /**
38
+ * Enhanced conversions identifiers. Sent RAW: the tag normalizes and SHA-256 hashes them in the
39
+ * browser before anything leaves the page (bat.js `validatePid`), and rejects a phone that is
40
+ * not E.164 after stripping separators.
41
+ */
42
+ interface UETPid {
43
+ em?: string;
44
+ ph?: string;
45
+ }
46
+ /** The parameters of one `uetq.push('event', action, params)` call. */
47
+ interface UETEventParams {
48
+ /** Deduplicates against the same event sent through the Conversions API. */
49
+ event_id?: string;
50
+ event_category?: string;
51
+ event_label?: string;
52
+ event_value?: number;
53
+ /** Variable revenue (`gv` on the wire). */
54
+ revenue_value?: number;
55
+ /** ISO 4217, uppercase (`gc` on the wire). */
56
+ currency?: string;
57
+ transaction_id?: string;
58
+ tax?: number;
59
+ shipping?: number;
60
+ coupon?: string;
61
+ items?: UETItem[];
62
+ search_term?: string;
63
+ method?: string;
64
+ content_type?: string;
65
+ content_id?: string;
66
+ ecomm_prodid?: string | string[];
67
+ ecomm_pagetype?: UETPageType;
68
+ ecomm_totalvalue?: number;
69
+ ecomm_category?: string;
70
+ pid?: UETPid;
71
+ }
72
+ type UETConsent = {
73
+ ad_storage: 'granted' | 'denied';
74
+ /**
75
+ * `default` only: milliseconds to hold events for an `update` before acting on the default,
76
+ * so a CMP that answers quickly does not cost the landing page. bat.js reads exactly this
77
+ * lowercase key (some community snippets spell it `Wait_for_update`, which it ignores) and
78
+ * caps it at 10,000.
79
+ */
80
+ wait_for_update?: number;
81
+ };
82
+ /**
83
+ * `window.uetq` is a plain array until `bat.js` loads (the snippet does `w[u] = w[u] || []`), then
84
+ * a `UET` instance that drains the queue; both expose `push`, which is all this SDK uses.
85
+ */
86
+ interface UETQ {
87
+ push(command: 'event', action: string, params?: UETEventParams): void;
88
+ push(command: 'pageLoad'): void;
89
+ push(command: 'set', params: {
90
+ pid?: UETPid;
91
+ }): void;
92
+ push(command: 'consent', mode: 'default' | 'update', consent: UETConsent): void;
93
+ }
94
+ /** GA4 `Item` → UET `items[]` entry. Undefined fields are left for the caller to strip. */
95
+ declare function mapItems(items?: Item[]): undefined | UETItem[];
96
+ /**
97
+ * Map an internal track event onto a UET action and its parameters.
98
+ *
99
+ * The action is always the internal name: for the GA4-named standard events that is also the
100
+ * name UET knows, and for everything else it is the custom action a UET event goal is configured
101
+ * with. Keeping the name identical on both channels is what lets the Conversions API sender
102
+ * (`eventName`) deduplicate against the browser (`event_id`).
103
+ *
104
+ * `page_view` is special to the tag: bat.js routes that action to its page-load beacon (the same
105
+ * one the snippet fires on load and `enableAutoSpaTracking` fires on history changes), so the
106
+ * browser tracker does not forward it — see `sendUETEvent`.
107
+ */
108
+ declare function mapUETEvent<T extends EventName>(name: TrackName<T>, properties?: TrackProperties<T>, eventId?: string): [string, UETEventParams];
109
+ //#endregion
110
+ export { UETConsent, UETEventParams, UETItem, UETPageType, UETPid, UETQ, mapItems, mapUETEvent };
111
+ //# sourceMappingURL=uetq.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uetq.d.cts","names":[],"sources":["../../src/track/uetq.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;KAuBY;;UAUK;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;UAQe;EACf;EACA;;;UAIe;;EAEf;EACA;EACA;EACA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA,iBAAiB;EACjB;EACA;EACA,MAAM;;KAGI;EACV;;;;;;;EAOA;;;;;;UAOe;EACf,KAAK,kBAAkB,gBAAgB,SAAS;EAChD,KAAK;EACL,KAAK,gBAAgB;IAAU,MAAM;;EACrC,KAAK,oBAAoB,4BAA4B,SAAS;;;iBAIhD,SAAS,QAAQ,qBAAqB;;;;;;;;;;;;;iBAuDtC,YAAY,UAAU,WACpC,MAAM,UAAU,IAChB,aAAa,gBAAgB,IAC7B,4BACU"}