@shware/analytics 8.1.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.
- package/README.md +63 -2
- package/dist/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/_virtual/_rolldown/runtime.mjs +1 -1
- package/dist/click-id/index.cjs +119 -6
- package/dist/click-id/index.cjs.map +1 -1
- package/dist/click-id/index.d.cts +36 -1
- package/dist/click-id/index.d.cts.map +1 -1
- package/dist/click-id/index.d.mts +36 -1
- package/dist/click-id/index.d.mts.map +1 -1
- package/dist/click-id/index.mjs +111 -6
- package/dist/click-id/index.mjs.map +1 -1
- package/dist/feedback/index.cjs.map +1 -1
- package/dist/hooks/use-previous.cjs.map +1 -1
- package/dist/hooks/use-track-impression.cjs.map +1 -1
- package/dist/hooks/use-web-analytics.cjs.map +1 -1
- package/dist/link/index.cjs.map +1 -1
- package/dist/native/fingerprint.cjs.map +1 -1
- package/dist/native/firebase.cjs.map +1 -1
- package/dist/native/setup.cjs.map +1 -1
- package/dist/next/index.cjs +2 -1
- package/dist/next/index.cjs.map +1 -1
- package/dist/next/index.mjs +2 -1
- package/dist/next/index.mjs.map +1 -1
- package/dist/react-router/index.cjs +1 -1
- package/dist/react-router/index.cjs.map +1 -1
- package/dist/react-router/index.mjs +1 -1
- package/dist/schema/index.cjs.map +1 -1
- package/dist/server/google-data-manager.cjs +2 -0
- package/dist/server/google-data-manager.cjs.map +1 -1
- package/dist/server/google-data-manager.d.cts.map +1 -1
- package/dist/server/google-data-manager.d.mts.map +1 -1
- package/dist/server/google-data-manager.mjs +2 -0
- package/dist/server/google-data-manager.mjs.map +1 -1
- package/dist/server/index.cjs +12 -0
- package/dist/server/index.d.cts +5 -4
- package/dist/server/index.d.mts +5 -4
- package/dist/server/index.mjs +5 -4
- package/dist/server/linkedin-conversions-api.cjs.map +1 -1
- package/dist/server/meta-capi.cjs +19 -17
- package/dist/server/meta-capi.cjs.map +1 -1
- package/dist/server/meta-capi.mjs +19 -17
- package/dist/server/meta-capi.mjs.map +1 -1
- package/dist/server/meta-conversions-api.cjs +40 -36
- package/dist/server/meta-conversions-api.cjs.map +1 -1
- package/dist/server/meta-conversions-api.mjs +40 -36
- package/dist/server/meta-conversions-api.mjs.map +1 -1
- package/dist/server/microsoft-conversions-api.cjs +171 -0
- package/dist/server/microsoft-conversions-api.cjs.map +1 -0
- package/dist/server/microsoft-conversions-api.d.cts +117 -0
- package/dist/server/microsoft-conversions-api.d.cts.map +1 -0
- package/dist/server/microsoft-conversions-api.d.mts +117 -0
- package/dist/server/microsoft-conversions-api.d.mts.map +1 -0
- package/dist/server/microsoft-conversions-api.mjs +168 -0
- package/dist/server/microsoft-conversions-api.mjs.map +1 -0
- package/dist/server/openai-conversions-api.cjs.map +1 -1
- package/dist/server/reddit-conversions-api.cjs.map +1 -1
- package/dist/setup/session.cjs +1 -1
- package/dist/setup/session.cjs.map +1 -1
- package/dist/setup/session.mjs +1 -1
- package/dist/setup/session.mjs.map +1 -1
- package/dist/tanstack/analytics.cjs +1 -1
- package/dist/tanstack/analytics.cjs.map +1 -1
- package/dist/tanstack/analytics.mjs +1 -1
- package/dist/tanstack/middleware.cjs +2 -1
- package/dist/tanstack/middleware.cjs.map +1 -1
- package/dist/tanstack/middleware.d.cts +2 -1
- package/dist/tanstack/middleware.d.cts.map +1 -1
- package/dist/tanstack/middleware.d.mts +2 -1
- package/dist/tanstack/middleware.d.mts.map +1 -1
- package/dist/tanstack/middleware.mjs +2 -1
- package/dist/tanstack/middleware.mjs.map +1 -1
- package/dist/third-parties/index.cjs +5 -0
- package/dist/third-parties/index.d.cts +3 -1
- package/dist/third-parties/index.d.mts +3 -1
- package/dist/third-parties/index.mjs +2 -1
- package/dist/third-parties/microsoft-uet.cjs +119 -0
- package/dist/third-parties/microsoft-uet.cjs.map +1 -0
- package/dist/third-parties/microsoft-uet.d.cts +70 -0
- package/dist/third-parties/microsoft-uet.d.cts.map +1 -0
- package/dist/third-parties/microsoft-uet.d.mts +70 -0
- package/dist/third-parties/microsoft-uet.d.mts.map +1 -0
- package/dist/third-parties/microsoft-uet.mjs +115 -0
- package/dist/third-parties/microsoft-uet.mjs.map +1 -0
- package/dist/third-parties/openai-pixel.cjs +2 -7
- package/dist/third-parties/openai-pixel.cjs.map +1 -1
- package/dist/third-parties/openai-pixel.d.cts.map +1 -1
- package/dist/third-parties/openai-pixel.d.mts.map +1 -1
- package/dist/third-parties/openai-pixel.mjs +1 -6
- package/dist/third-parties/openai-pixel.mjs.map +1 -1
- package/dist/track/index.cjs.map +1 -1
- package/dist/track/uetq.cjs +80 -0
- package/dist/track/uetq.cjs.map +1 -0
- package/dist/track/uetq.d.cts +111 -0
- package/dist/track/uetq.d.cts.map +1 -0
- package/dist/track/uetq.d.mts +111 -0
- package/dist/track/uetq.d.mts.map +1 -0
- package/dist/track/uetq.mjs +78 -0
- package/dist/track/uetq.mjs.map +1 -0
- package/dist/utils/sha256.cjs +19 -0
- package/dist/utils/sha256.cjs.map +1 -0
- package/dist/utils/sha256.d.cts +13 -0
- package/dist/utils/sha256.d.cts.map +1 -0
- package/dist/utils/sha256.d.mts +13 -0
- package/dist/utils/sha256.d.mts.map +1 -0
- package/dist/utils/sha256.mjs +18 -0
- package/dist/utils/sha256.mjs.map +1 -0
- package/dist/visitor/index.cjs.map +1 -1
- package/dist/web/index.cjs +4 -3
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts.map +1 -1
- package/dist/web/index.d.mts.map +1 -1
- package/dist/web/index.mjs +4 -3
- package/dist/web/index.mjs.map +1 -1
- 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
|
|
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":";;;;
|
|
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":";;;;
|
|
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
|
|
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"}
|
package/dist/track/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["TokenBucket","config","cache","getSession","getVisitor","IGNORED_EVENTS","keys"],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { keys } from '../constants/storage';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties, TrackTags } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = {};\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n tags: Promise<TrackTags>;\n timestamp: string;\n options: TrackOptions;\n};\n\n/**\n * Tags belong to the moment the event happened, not to the moment its batch goes out: a queued\n * event waits up to `delay` ms, and a single page app can navigate in that window, which would\n * stamp every pending event with the URL of the page the user has already left.\n *\n * The promise then sits in the queue with nothing awaiting it, so a failure has to be absorbed\n * here — an unhandled rejection would surface as a global error long before `sendEvents` could\n * catch it. Falling back to the last built tags keeps the event, minus whatever changed since.\n */\nasync function captureTags(): Promise<TrackTags> {\n try {\n return await config.getTags();\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n return cache.tags ?? {};\n }\n}\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n // One read-modify-write of the stored session for the whole batch: it answers which session\n // these events belong to and whether this batch is the one that started it. Timed by the\n // events themselves rather than by this moment — a tab frozen in the background can hold a\n // batch for far longer than `delay`, and those events belong to the session they happened in.\n const firstTimestamp = events[0].timestamp;\n const { id: session_id, started } = getSession().touch(\n Date.parse(firstTimestamp),\n Date.parse(events[events.length - 1].timestamp)\n );\n if (started) {\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n tags: captureTags(),\n // The session began with the event that opened it, not at this moment: a batch held in a\n // frozen tab would otherwise announce its session later than the events inside it.\n timestamp: firstTimestamp,\n });\n }\n\n await getTokenBucket().removeTokens();\n\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = await Promise.all(\n events.map(async (event) => ({\n name: event.name,\n properties: event.properties,\n tags: await event.tags,\n visitor_id,\n session_id,\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }))\n );\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n // Survive the page being unloaded mid-flight: a batch waits up to `delay` ms, so closing\n // the tab inside that window would otherwise abort the request and lose every event in it.\n // The body stays far under keepalive's 64KB in-flight budget at 100 events per batch.\n keepalive: true,\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data.at(index)?.id;\n options.onSucceed?.(eventId ? { id: eventId } : undefined);\n index++;\n // An explicit false, not falsiness: a caller passing `{ onSucceed }` replaces the options\n // object wholesale, and leaving the flag out must not silently switch forwarding off.\n if (options.enableThirdPartyTracking === false || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => {\n try {\n tracker(name, properties, eventId);\n } catch (e: unknown) {\n // A third-party script does not get to take the rest of the batch with it. This loop is\n // still draining `events` with `shift`, so a throw would escape to the catch below and\n // report failure to whatever is left in the queue — for events the server has already\n // accepted, and after the ones ahead of them were told they succeeded.\n if (e instanceof Error) console.log(e.message);\n }\n });\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Both paths into a send go through here, so a batch that fills up cancels the timer the\n * previous push armed rather than leaving it to wake up on its own with nothing to send.\n */\nfunction flush() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n if (list.length === 0) return;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n}\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({\n name,\n properties,\n options,\n tags: captureTags(),\n timestamp: new Date().toISOString(),\n });\n if (list.length >= batch) {\n flush();\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([\n { name, properties, options, tags: captureTags(), timestamp: new Date().toISOString() },\n ]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // The visitor id is persisted, so a returning visitor already has one before `getVisitor` has\n // finished its round trip for this page. Requiring the in-memory copy threw away exactly the\n // events this function exists for: everything a visit accrues before its first batch comes\n // back, which for a short visit is the whole of it.\n const stored = config.storage.getItem(keys.visitor_id);\n const visitor_id = cache.visitor?.id ?? (stored && stored !== 'undefined' ? stored : undefined);\n if (!visitor_id) return;\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n // Tags are worth less than the event carrying them: an empty set still reports the\n // engagement, and every field in `tagsSchema` is optional.\n tags: cache.tags ?? {},\n visitor_id,\n session_id: getSession().extend(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["TokenBucket","config","cache","getSession","getVisitor","fetch","IGNORED_EVENTS","keys"],"sources":["../../src/track/index.ts"],"sourcesContent":["import { TokenBucket, fetch } from '@shware/utils';\nimport { keys } from '../constants/storage';\nimport type { CreateTrackEventDTO } from '../schema/index';\nimport { cache, config } from '../setup/index';\nimport { getSession } from '../setup/session';\nimport { IGNORED_EVENTS } from '../third-parties/ignored-events';\nimport { getVisitor } from '../visitor/index';\nimport type { EventName, TrackEventResponse, TrackName, TrackProperties, TrackTags } from './types';\n\nexport interface TrackOptions {\n enableThirdPartyTracking?: boolean;\n onSucceed?: (response?: TrackEventResponse[number]) => void;\n onError?: (error: unknown) => void;\n}\n\nconst defaultOptions: TrackOptions = {};\n\nlet tokenBucket: TokenBucket | undefined;\n\n/**\n * The rate limiter, built on the first send rather than at module scope: its\n * constructor starts a refill `setInterval`, and Cloudflare Workers refuse to\n * set a timer outside a request handler for the same reason they refuse to\n * generate random values there — see `setup/session.ts`.\n */\nfunction getTokenBucket() {\n return (tokenBucket ??= new TokenBucket({ rate: 1, capacity: 20, requested: 2 }));\n}\n\ntype Item = {\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n name: TrackName<any>;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n properties: TrackProperties<any>;\n tags: Promise<TrackTags>;\n timestamp: string;\n options: TrackOptions;\n};\n\n/**\n * Tags belong to the moment the event happened, not to the moment its batch goes out: a queued\n * event waits up to `delay` ms, and a single page app can navigate in that window, which would\n * stamp every pending event with the URL of the page the user has already left.\n *\n * The promise then sits in the queue with nothing awaiting it, so a failure has to be absorbed\n * here — an unhandled rejection would surface as a global error long before `sendEvents` could\n * catch it. Falling back to the last built tags keeps the event, minus whatever changed since.\n */\nasync function captureTags(): Promise<TrackTags> {\n try {\n return await config.getTags();\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n return cache.tags ?? {};\n }\n}\n\nasync function sendEvents(events: Item[]) {\n try {\n if (events.length === 0) return;\n\n // One read-modify-write of the stored session for the whole batch: it answers which session\n // these events belong to and whether this batch is the one that started it. Timed by the\n // events themselves rather than by this moment — a tab frozen in the background can hold a\n // batch for far longer than `delay`, and those events belong to the session they happened in.\n const firstTimestamp = events[0].timestamp;\n const { id: session_id, started } = getSession().touch(\n Date.parse(firstTimestamp),\n Date.parse(events[events.length - 1].timestamp)\n );\n if (started) {\n events.unshift({\n name: 'session_start',\n properties: {},\n options: { enableThirdPartyTracking: false },\n tags: captureTags(),\n // The session began with the event that opened it, not at this moment: a batch held in a\n // frozen tab would otherwise announce its session later than the events inside it.\n timestamp: firstTimestamp,\n });\n }\n\n await getTokenBucket().removeTokens();\n\n const visitor_id = (await getVisitor()).id;\n\n const dto: CreateTrackEventDTO = await Promise.all(\n events.map(async (event) => ({\n name: event.name,\n properties: event.properties,\n tags: await event.tags,\n visitor_id,\n session_id,\n platform: config.platform,\n environment: config.environment,\n timestamp: event.timestamp,\n }))\n );\n\n const response = await fetch(`${config.endpoint}/events`, {\n method: 'POST',\n credentials: 'include',\n // Survive the page being unloaded mid-flight: a batch waits up to `delay` ms, so closing\n // the tab inside that window would otherwise abort the request and lose every event in it.\n // The body stays far under keepalive's 64KB in-flight budget at 100 events per batch.\n keepalive: true,\n headers: await config.getHeaders(),\n body: JSON.stringify(dto),\n });\n\n if (!response.ok) {\n throw new Error(`Failed to send track event: ${response.status} ${await response.text()}`);\n }\n\n const data = (await response.json()) as TrackEventResponse;\n\n let index = 0;\n while (events.length > 0) {\n const event = events.shift();\n if (!event) {\n index++;\n continue;\n }\n const { options, name, properties } = event;\n const eventId = data.at(index)?.id;\n options.onSucceed?.(eventId ? { id: eventId } : undefined);\n index++;\n // An explicit false, not falsiness: a caller passing `{ onSucceed }` replaces the options\n // object wholesale, and leaving the flag out must not silently switch forwarding off.\n if (options.enableThirdPartyTracking === false || IGNORED_EVENTS.includes(name)) {\n continue;\n }\n config.thirdPartyTrackers.forEach((tracker) => {\n try {\n tracker(name, properties, eventId);\n } catch (e: unknown) {\n // A third-party script does not get to take the rest of the batch with it. This loop is\n // still draining `events` with `shift`, so a throw would escape to the catch below and\n // report failure to whatever is left in the queue — for events the server has already\n // accepted, and after the ones ahead of them were told they succeeded.\n if (e instanceof Error) console.log(e.message);\n }\n });\n }\n } catch (e: unknown) {\n if (e instanceof Error) console.log(e.message);\n events.forEach((event) => event.options.onError?.(e));\n }\n}\n\nconst batch = 10;\nconst delay = 2000;\nconst list: Item[] = [];\nlet timer: ReturnType<typeof setTimeout> | null = null;\n\n/**\n * Both paths into a send go through here, so a batch that fills up cancels the timer the\n * previous push armed rather than leaving it to wake up on its own with nothing to send.\n */\nfunction flush() {\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n if (list.length === 0) return;\n const copy = [...list];\n list.length = 0;\n void sendEvents(copy);\n}\n\nexport function track<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n list.push({\n name,\n properties,\n options,\n tags: captureTags(),\n timestamp: new Date().toISOString(),\n });\n if (list.length >= batch) {\n flush();\n return;\n }\n if (timer) clearTimeout(timer);\n timer = setTimeout(flush, delay);\n}\n\nexport async function trackAsync<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n options: TrackOptions = defaultOptions\n) {\n await sendEvents([\n { name, properties, options, tags: captureTags(), timestamp: new Date().toISOString() },\n ]);\n}\n\nexport function sendBeacon<T extends EventName = EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n) {\n // The visitor id is persisted, so a returning visitor already has one before `getVisitor` has\n // finished its round trip for this page. Requiring the in-memory copy threw away exactly the\n // events this function exists for: everything a visit accrues before its first batch comes\n // back, which for a short visit is the whole of it.\n const stored = config.storage.getItem(keys.visitor_id);\n const visitor_id = cache.visitor?.id ?? (stored && stored !== 'undefined' ? stored : undefined);\n if (!visitor_id) return;\n\n const dto: CreateTrackEventDTO = [\n {\n name,\n properties,\n // Tags are worth less than the event carrying them: an empty set still reports the\n // engagement, and every field in `tagsSchema` is optional.\n tags: cache.tags ?? {},\n visitor_id,\n session_id: getSession().extend(),\n platform: config.platform,\n environment: config.environment,\n timestamp: new Date().toISOString(),\n },\n ];\n const blob = new Blob([JSON.stringify(dto)], { type: 'application/json' });\n const success = navigator.sendBeacon(`${config.endpoint}/events`, blob);\n if (success) return;\n console.warn('Failed to send beacon', name, properties);\n}\n"],"mappings":"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"}
|
|
@@ -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"}
|