@shware/analytics 7.3.0 → 7.4.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 +12 -0
- package/dist/hooks/use-web-analytics.cjs +4 -3
- package/dist/hooks/use-web-analytics.cjs.map +1 -1
- package/dist/hooks/use-web-analytics.d.cts.map +1 -1
- package/dist/hooks/use-web-analytics.d.mts.map +1 -1
- package/dist/hooks/use-web-analytics.mjs +4 -3
- package/dist/hooks/use-web-analytics.mjs.map +1 -1
- package/dist/next/index.cjs +1 -1
- package/dist/next/index.mjs +1 -1
- package/dist/react-router/index.cjs +1 -1
- package/dist/react-router/index.mjs +1 -1
- package/dist/schema/index.cjs +9 -2
- package/dist/schema/index.cjs.map +1 -1
- package/dist/schema/index.d.cts +6 -2
- package/dist/schema/index.d.cts.map +1 -1
- package/dist/schema/index.d.mts +6 -2
- package/dist/schema/index.d.mts.map +1 -1
- package/dist/schema/index.mjs +9 -2
- package/dist/schema/index.mjs.map +1 -1
- package/dist/setup/session.cjs +8 -9
- package/dist/setup/session.cjs.map +1 -1
- package/dist/setup/session.d.cts +8 -6
- package/dist/setup/session.d.cts.map +1 -1
- package/dist/setup/session.d.mts +8 -6
- package/dist/setup/session.d.mts.map +1 -1
- package/dist/setup/session.mjs +8 -9
- package/dist/setup/session.mjs.map +1 -1
- package/dist/test/setup.cjs +43 -0
- package/dist/test/setup.cjs.map +1 -0
- package/dist/test/setup.d.cts +19 -0
- package/dist/test/setup.d.cts.map +1 -0
- package/dist/test/setup.d.mts +19 -0
- package/dist/test/setup.d.mts.map +1 -0
- package/dist/test/setup.mjs +40 -0
- package/dist/test/setup.mjs.map +1 -0
- package/dist/third-parties/meta-pixel.cjs +1 -1
- package/dist/third-parties/meta-pixel.cjs.map +1 -1
- package/dist/third-parties/meta-pixel.mjs +1 -1
- package/dist/third-parties/meta-pixel.mjs.map +1 -1
- package/dist/track/fbq.cjs +3 -3
- package/dist/track/fbq.cjs.map +1 -1
- package/dist/track/fbq.d.cts.map +1 -1
- package/dist/track/fbq.d.mts.map +1 -1
- package/dist/track/fbq.mjs +3 -3
- package/dist/track/fbq.mjs.map +1 -1
- package/dist/track/index.cjs +3 -2
- package/dist/track/index.cjs.map +1 -1
- package/dist/track/index.d.cts.map +1 -1
- package/dist/track/index.d.mts.map +1 -1
- package/dist/track/index.mjs +3 -2
- package/dist/track/index.mjs.map +1 -1
- package/dist/track/types.d.cts +1 -0
- package/dist/track/types.d.cts.map +1 -1
- package/dist/track/types.d.mts +1 -0
- package/dist/track/types.d.mts.map +1 -1
- package/dist/web/index.cjs +1 -0
- 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 +1 -0
- package/dist/web/index.mjs.map +1 -1
- package/package.json +5 -1
package/dist/setup/session.cjs
CHANGED
|
@@ -44,7 +44,7 @@ var Session = class {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
this.accumulatedTime = 0;
|
|
47
|
-
this.
|
|
47
|
+
this.lastTickTime = Date.now();
|
|
48
48
|
const session = {
|
|
49
49
|
id: (0, uuid.v7)(),
|
|
50
50
|
lastEventTime
|
|
@@ -66,18 +66,16 @@ var Session = class {
|
|
|
66
66
|
return stored.id;
|
|
67
67
|
};
|
|
68
68
|
this.isActive = () => this.active;
|
|
69
|
-
this.isVisible = () => this.visible;
|
|
70
|
-
this.isFocused = () => this.focused;
|
|
71
69
|
this.updateActive = (active) => {
|
|
72
70
|
this.active = active;
|
|
73
71
|
};
|
|
74
72
|
this.updateAccumulator = () => {
|
|
75
73
|
const now = Date.now();
|
|
76
74
|
if (this.focused && this.visible && this.active) {
|
|
77
|
-
const delta = now - this.
|
|
75
|
+
const delta = now - this.lastTickTime;
|
|
78
76
|
if (delta > 0 && delta < 18e5) this.accumulatedTime += delta;
|
|
79
77
|
}
|
|
80
|
-
this.
|
|
78
|
+
this.lastTickTime = now;
|
|
81
79
|
};
|
|
82
80
|
this.focus = () => {
|
|
83
81
|
this.updateAccumulator();
|
|
@@ -105,7 +103,7 @@ var Session = class {
|
|
|
105
103
|
this.accumulatedTime = 0;
|
|
106
104
|
return engagementTime;
|
|
107
105
|
};
|
|
108
|
-
this.
|
|
106
|
+
this.lastTickTime = Date.now();
|
|
109
107
|
this.accumulatedTime = 0;
|
|
110
108
|
this.active = true;
|
|
111
109
|
this.visible = typeof document !== "undefined" ? document.visibilityState === "visible" : true;
|
|
@@ -132,9 +130,10 @@ let session;
|
|
|
132
130
|
* isolate booted, taking down every route before a component rendered.
|
|
133
131
|
*
|
|
134
132
|
* Everything here is per-visitor browser or app state, so deferring the
|
|
135
|
-
* construction costs nothing and
|
|
136
|
-
*
|
|
137
|
-
*
|
|
133
|
+
* construction costs nothing and lets the server bundle be evaluated. It does
|
|
134
|
+
* not make any of it safe to use there: this instance, `cache` and `config` are
|
|
135
|
+
* module singletons, so calling `track()` on a server shares one session and one
|
|
136
|
+
* visitor across every request the isolate serves. Import it there; do not track.
|
|
138
137
|
*/
|
|
139
138
|
function getSession() {
|
|
140
139
|
return session ??= new Session();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.cjs","names":["config","keys"],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { config } from './index';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\n/**\n * The part of a session that outlives the page: its identity, and the clock the timeout is\n * measured against. GA4 keeps these in its `_ga_<container>` cookie — alongside the engaged flag,\n * which arrives with `session_engaged` later — and rereads them for every event, which is what\n * makes a session survive a reload, span a whole multipage visit, and stay shared between two\n * tabs of the same site. Holding them in memory instead, as this did, starts a new session on\n * every full navigation and gives every tab one of its own.\n */\ninterface StoredSession {\n id: string;\n /** When the last event was sent. The timeout is measured from here, and only from here. */\n lastEventTime: number;\n}\n\n/** The session an event belongs to, and whether that event is the one that started it. */\ninterface SessionForEvent {\n id: string;\n started: boolean;\n}\n\n/**\n * `<version>.<id>.<lastEventTime>`, compact and cookie-safe rather than JSON: `{`, `\"`\n * and `,` all have to be percent-encoded in a cookie, and a host that wants one session across\n * its subdomains will hand `setupAnalytics` a cookie-backed `storage`, where this has to survive\n * unchanged. A uuidv7 contains no dots, so the record splits cleanly.\n *\n * The version guards a change the parser could not otherwise survive. A field appended to the end\n * does not need one — a short record simply leaves it undefined.\n */\nconst VERSION = '1';\n\nfunction readSession(): StoredSession | undefined {\n const raw = config.storage.getItem(keys.session);\n if (!raw) return undefined;\n\n const [version, id, lastEventTime] = raw.split('.');\n if (version !== VERSION || !id) return undefined;\n\n const parsed = { id, lastEventTime: Number(lastEventTime) };\n if (!Number.isFinite(parsed.lastEventTime)) return undefined;\n return parsed;\n}\n\nfunction writeSession({ id, lastEventTime }: StoredSession) {\n config.storage.setItem(keys.session, `${VERSION}.${id}.${lastEventTime}`);\n}\n\nclass Session {\n /**\n * Engagement is deliberately not stored: it is the time this page has accrued and not yet\n * reported, so it belongs to the page, not to the session. GA4 draws the same line — its cookie\n * carries the session, while the engagement timer lives and dies with the document.\n */\n private
|
|
1
|
+
{"version":3,"file":"session.cjs","names":["config","keys"],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { config } from './index';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\n/**\n * The part of a session that outlives the page: its identity, and the clock the timeout is\n * measured against. GA4 keeps these in its `_ga_<container>` cookie — alongside the engaged flag,\n * which arrives with `session_engaged` later — and rereads them for every event, which is what\n * makes a session survive a reload, span a whole multipage visit, and stay shared between two\n * tabs of the same site. Holding them in memory instead, as this did, starts a new session on\n * every full navigation and gives every tab one of its own.\n */\ninterface StoredSession {\n id: string;\n /** When the last event was sent. The timeout is measured from here, and only from here. */\n lastEventTime: number;\n}\n\n/** The session an event belongs to, and whether that event is the one that started it. */\ninterface SessionForEvent {\n id: string;\n started: boolean;\n}\n\n/**\n * `<version>.<id>.<lastEventTime>`, compact and cookie-safe rather than JSON: `{`, `\"`\n * and `,` all have to be percent-encoded in a cookie, and a host that wants one session across\n * its subdomains will hand `setupAnalytics` a cookie-backed `storage`, where this has to survive\n * unchanged. A uuidv7 contains no dots, so the record splits cleanly.\n *\n * The version guards a change the parser could not otherwise survive. A field appended to the end\n * does not need one — a short record simply leaves it undefined.\n */\nconst VERSION = '1';\n\nfunction readSession(): StoredSession | undefined {\n const raw = config.storage.getItem(keys.session);\n if (!raw) return undefined;\n\n const [version, id, lastEventTime] = raw.split('.');\n if (version !== VERSION || !id) return undefined;\n\n const parsed = { id, lastEventTime: Number(lastEventTime) };\n if (!Number.isFinite(parsed.lastEventTime)) return undefined;\n return parsed;\n}\n\nfunction writeSession({ id, lastEventTime }: StoredSession) {\n config.storage.setItem(keys.session, `${VERSION}.${id}.${lastEventTime}`);\n}\n\nclass Session {\n /**\n * Engagement is deliberately not stored: it is the time this page has accrued and not yet\n * reported, so it belongs to the page, not to the session. GA4 draws the same line — its cookie\n * carries the session, while the engagement timer lives and dies with the document.\n *\n * `lastTickTime` is where the accumulator last settled up, rewritten on every tick — it is not\n * when the session began, and nothing here needs to know that.\n */\n private lastTickTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.lastTickTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n /**\n * The session a batch of events belongs to: read the stored one, start a new one if it has\n * timed out or there is none, stamp it with when those events happened and write it back. Every\n * event goes through here, exactly as GA4 rereads and rewrites its cookie per event — caching\n * the session in memory would put the tabs back out of step with each other.\n */\n touch = (eventTime: number, lastEventTime = eventTime): SessionForEvent => {\n const stored = readSession();\n\n if (stored && eventTime - stored.lastEventTime <= SESSION_TIMEOUT) {\n // `Math.max`, because a batch that waited in a frozen tab can be older than what another\n // tab has since written, and a session must never be shortened by a late arrival.\n writeSession({ ...stored, lastEventTime: Math.max(stored.lastEventTime, lastEventTime) });\n return { id: stored.id, started: false };\n }\n\n // Engagement the previous session accrued but never reported dies with it rather than being\n // handed to its successor. GA4 does the same on `session_start`.\n this.accumulatedTime = 0;\n // Wall clock, not `eventTime`: this anchors the engagement timer for the page in front of the\n // visitor now, which a batch describing something that happened an hour ago says nothing about.\n this.lastTickTime = Date.now();\n\n const session: StoredSession = { id: uuidv7(), lastEventTime };\n writeSession(session);\n return { id: session.id, started: true };\n };\n\n /**\n * The id for an event that must not start a session — the `pagehide` beacon, which reports what\n * the session now ending accrued. A live session is extended, as any event extends it; one\n * already past its timeout still owns that engagement, so its id comes back without being\n * revived into a session no `session_start` ever announced.\n */\n extend = (): string => {\n const stored = readSession();\n if (!stored) return this.touch(Date.now()).id;\n\n const now = Date.now();\n if (now - stored.lastEventTime <= SESSION_TIMEOUT) {\n writeSession({ ...stored, lastEventTime: now });\n }\n return stored.id;\n };\n\n isActive = () => this.active;\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.lastTickTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.lastTickTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nlet session: Session | undefined;\n\n/**\n * The session, built the first time something asks for it.\n *\n * Deliberately not a module-scope `new Session()`. The constructor calls\n * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and\n * Cloudflare Workers reject that outside a request handler:\n *\n * Disallowed operation called within global scope. Asynchronous I/O\n * (ex: fetch() or connect()), setting a timeout, and generating random\n * values are not allowed within global scope.\n *\n * Module scope in a Worker is evaluated once when the isolate boots and is\n * then shared by every request that isolate serves, so a random value drawn\n * there would be the same for all of them — which is why the runtime refuses\n * to produce one. A host that server-renders on Workers reaches this module\n * through `track()` on the server as well, and the throw happened as the\n * isolate booted, taking down every route before a component rendered.\n *\n * Everything here is per-visitor browser or app state, so deferring the\n * construction costs nothing and lets the server bundle be evaluated. It does\n * not make any of it safe to use there: this instance, `cache` and `config` are\n * module singletons, so calling `track()` on a server shares one session and one\n * visitor across every request the isolate serves. Import it there; do not track.\n */\nexport function getSession() {\n return (session ??= new Session());\n}\n"],"mappings":";;;;;AAIA,MAAa,kBAAkB,OAAU;;;;;;;;;;AA+BzC,MAAM,UAAU;AAEhB,SAAS,cAAyC;CAChD,MAAM,MAAMA,oBAAAA,OAAO,QAAQ,QAAQC,0BAAAA,KAAK,OAAO;CAC/C,IAAI,CAAC,KAAK,OAAO,KAAA;CAEjB,MAAM,CAAC,SAAS,IAAI,iBAAiB,IAAI,MAAM,GAAG;CAClD,IAAI,YAAY,WAAW,CAAC,IAAI,OAAO,KAAA;CAEvC,MAAM,SAAS;EAAE;EAAI,eAAe,OAAO,aAAa;CAAE;CAC1D,IAAI,CAAC,OAAO,SAAS,OAAO,aAAa,GAAG,OAAO,KAAA;CACnD,OAAO;AACT;AAEA,SAAS,aAAa,EAAE,IAAI,iBAAgC;CAC1D,oBAAA,OAAO,QAAQ,QAAQA,0BAAAA,KAAK,SAAS,GAAG,QAAQ,GAAG,GAAG,GAAG,eAAe;AAC1E;AAEA,IAAM,UAAN,MAAc;CAgBZ,cAAc;EAeL,KAAA,SAAA,WAAmB,gBAAgB,cAA+B;GACzE,MAAM,SAAS,YAAY;GAE3B,IAAI,UAAU,YAAY,OAAO,iBAAA,MAAkC;IAGjE,aAAa;KAAE,GAAG;KAAQ,eAAe,KAAK,IAAI,OAAO,eAAe,aAAa;IAAE,CAAC;IACxF,OAAO;KAAE,IAAI,OAAO;KAAI,SAAS;IAAM;GACzC;GAIA,KAAK,kBAAkB;GAGvB,KAAK,eAAe,KAAK,IAAI;GAE7B,MAAM,UAAyB;IAAE,KAAA,GAAA,KAAA,GAAA,CAAW;IAAG;GAAc;GAC7D,aAAa,OAAO;GACpB,OAAO;IAAE,IAAI,QAAQ;IAAI,SAAS;GAAK;EACzC;EAQuB,KAAA,eAAA;GACrB,MAAM,SAAS,YAAY;GAC3B,IAAI,CAAC,QAAQ,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC;GAE3C,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,MAAM,OAAO,iBAAA,MACf,aAAa;IAAE,GAAG;IAAQ,eAAe;GAAI,CAAC;GAEhD,OAAO,OAAO;EAChB;EAEiB,KAAA,iBAAA,KAAK;EAEN,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,eAAe;EACtB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EApGE,KAAK,eAAe,KAAK,IAAI;EAC7B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AA+FF;AAEA,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;AA0BJ,SAAgB,aAAa;CAC3B,OAAQ,YAAY,IAAI,QAAQ;AAClC"}
|
package/dist/setup/session.d.cts
CHANGED
|
@@ -10,8 +10,11 @@ declare class Session {
|
|
|
10
10
|
* Engagement is deliberately not stored: it is the time this page has accrued and not yet
|
|
11
11
|
* reported, so it belongs to the page, not to the session. GA4 draws the same line — its cookie
|
|
12
12
|
* carries the session, while the engagement timer lives and dies with the document.
|
|
13
|
+
*
|
|
14
|
+
* `lastTickTime` is where the accumulator last settled up, rewritten on every tick — it is not
|
|
15
|
+
* when the session began, and nothing here needs to know that.
|
|
13
16
|
*/
|
|
14
|
-
private
|
|
17
|
+
private lastTickTime;
|
|
15
18
|
private accumulatedTime;
|
|
16
19
|
private active;
|
|
17
20
|
private visible;
|
|
@@ -32,8 +35,6 @@ declare class Session {
|
|
|
32
35
|
*/
|
|
33
36
|
extend: () => string;
|
|
34
37
|
isActive: () => boolean;
|
|
35
|
-
isVisible: () => boolean;
|
|
36
|
-
isFocused: () => boolean;
|
|
37
38
|
updateActive: (active: boolean) => void;
|
|
38
39
|
updateAccumulator: () => void;
|
|
39
40
|
focus: () => void;
|
|
@@ -62,9 +63,10 @@ declare class Session {
|
|
|
62
63
|
* isolate booted, taking down every route before a component rendered.
|
|
63
64
|
*
|
|
64
65
|
* Everything here is per-visitor browser or app state, so deferring the
|
|
65
|
-
* construction costs nothing and
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* construction costs nothing and lets the server bundle be evaluated. It does
|
|
67
|
+
* not make any of it safe to use there: this instance, `cache` and `config` are
|
|
68
|
+
* module singletons, so calling `track()` on a server shares one session and one
|
|
69
|
+
* visitor across every request the isolate serves. Import it there; do not track.
|
|
68
70
|
*/
|
|
69
71
|
declare function getSession(): Session;
|
|
70
72
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.cts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAIa;;UAiBH;EACR;EACA;;cA8BI
|
|
1
|
+
{"version":3,"file":"session.d.cts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAIa;;UAiBH;EACR;EACA;;cA8BI;;;;;;;;;UASI;UACA;UAEA;UACA;UACA;EAER;;;;;;;EAeA,QAAK,mBAAqB,2BAA8B;;;;;;;EA4BxD;EAWA;EAEA,eAAY;EAIZ;EAWA;EAKA;EAKA;EAKA;EAKA,mBAAgB,OAAW;EAK3B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkCc,cAAU"}
|
package/dist/setup/session.d.mts
CHANGED
|
@@ -10,8 +10,11 @@ declare class Session {
|
|
|
10
10
|
* Engagement is deliberately not stored: it is the time this page has accrued and not yet
|
|
11
11
|
* reported, so it belongs to the page, not to the session. GA4 draws the same line — its cookie
|
|
12
12
|
* carries the session, while the engagement timer lives and dies with the document.
|
|
13
|
+
*
|
|
14
|
+
* `lastTickTime` is where the accumulator last settled up, rewritten on every tick — it is not
|
|
15
|
+
* when the session began, and nothing here needs to know that.
|
|
13
16
|
*/
|
|
14
|
-
private
|
|
17
|
+
private lastTickTime;
|
|
15
18
|
private accumulatedTime;
|
|
16
19
|
private active;
|
|
17
20
|
private visible;
|
|
@@ -32,8 +35,6 @@ declare class Session {
|
|
|
32
35
|
*/
|
|
33
36
|
extend: () => string;
|
|
34
37
|
isActive: () => boolean;
|
|
35
|
-
isVisible: () => boolean;
|
|
36
|
-
isFocused: () => boolean;
|
|
37
38
|
updateActive: (active: boolean) => void;
|
|
38
39
|
updateAccumulator: () => void;
|
|
39
40
|
focus: () => void;
|
|
@@ -62,9 +63,10 @@ declare class Session {
|
|
|
62
63
|
* isolate booted, taking down every route before a component rendered.
|
|
63
64
|
*
|
|
64
65
|
* Everything here is per-visitor browser or app state, so deferring the
|
|
65
|
-
* construction costs nothing and
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* construction costs nothing and lets the server bundle be evaluated. It does
|
|
67
|
+
* not make any of it safe to use there: this instance, `cache` and `config` are
|
|
68
|
+
* module singletons, so calling `track()` on a server shares one session and one
|
|
69
|
+
* visitor across every request the isolate serves. Import it there; do not track.
|
|
68
70
|
*/
|
|
69
71
|
declare function getSession(): Session;
|
|
70
72
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAIa;;UAiBH;EACR;EACA;;cA8BI
|
|
1
|
+
{"version":3,"file":"session.d.mts","names":[],"sources":["../../src/setup/session.ts"],"mappings":";cAIa;;UAiBH;EACR;EACA;;cA8BI;;;;;;;;;UASI;UACA;UAEA;UACA;UACA;EAER;;;;;;;EAeA,QAAK,mBAAqB,2BAA8B;;;;;;;EA4BxD;EAWA;EAEA,eAAY;EAIZ;EAWA;EAKA;EAKA;EAKA;EAKA,mBAAgB,OAAW;EAK3B;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkCc,cAAU"}
|
package/dist/setup/session.mjs
CHANGED
|
@@ -43,7 +43,7 @@ var Session = class {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
this.accumulatedTime = 0;
|
|
46
|
-
this.
|
|
46
|
+
this.lastTickTime = Date.now();
|
|
47
47
|
const session = {
|
|
48
48
|
id: v7(),
|
|
49
49
|
lastEventTime
|
|
@@ -65,18 +65,16 @@ var Session = class {
|
|
|
65
65
|
return stored.id;
|
|
66
66
|
};
|
|
67
67
|
this.isActive = () => this.active;
|
|
68
|
-
this.isVisible = () => this.visible;
|
|
69
|
-
this.isFocused = () => this.focused;
|
|
70
68
|
this.updateActive = (active) => {
|
|
71
69
|
this.active = active;
|
|
72
70
|
};
|
|
73
71
|
this.updateAccumulator = () => {
|
|
74
72
|
const now = Date.now();
|
|
75
73
|
if (this.focused && this.visible && this.active) {
|
|
76
|
-
const delta = now - this.
|
|
74
|
+
const delta = now - this.lastTickTime;
|
|
77
75
|
if (delta > 0 && delta < 18e5) this.accumulatedTime += delta;
|
|
78
76
|
}
|
|
79
|
-
this.
|
|
77
|
+
this.lastTickTime = now;
|
|
80
78
|
};
|
|
81
79
|
this.focus = () => {
|
|
82
80
|
this.updateAccumulator();
|
|
@@ -104,7 +102,7 @@ var Session = class {
|
|
|
104
102
|
this.accumulatedTime = 0;
|
|
105
103
|
return engagementTime;
|
|
106
104
|
};
|
|
107
|
-
this.
|
|
105
|
+
this.lastTickTime = Date.now();
|
|
108
106
|
this.accumulatedTime = 0;
|
|
109
107
|
this.active = true;
|
|
110
108
|
this.visible = typeof document !== "undefined" ? document.visibilityState === "visible" : true;
|
|
@@ -131,9 +129,10 @@ let session;
|
|
|
131
129
|
* isolate booted, taking down every route before a component rendered.
|
|
132
130
|
*
|
|
133
131
|
* Everything here is per-visitor browser or app state, so deferring the
|
|
134
|
-
* construction costs nothing and
|
|
135
|
-
*
|
|
136
|
-
*
|
|
132
|
+
* construction costs nothing and lets the server bundle be evaluated. It does
|
|
133
|
+
* not make any of it safe to use there: this instance, `cache` and `config` are
|
|
134
|
+
* module singletons, so calling `track()` on a server shares one session and one
|
|
135
|
+
* visitor across every request the isolate serves. Import it there; do not track.
|
|
137
136
|
*/
|
|
138
137
|
function getSession() {
|
|
139
138
|
return session ??= new Session();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { config } from './index';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\n/**\n * The part of a session that outlives the page: its identity, and the clock the timeout is\n * measured against. GA4 keeps these in its `_ga_<container>` cookie — alongside the engaged flag,\n * which arrives with `session_engaged` later — and rereads them for every event, which is what\n * makes a session survive a reload, span a whole multipage visit, and stay shared between two\n * tabs of the same site. Holding them in memory instead, as this did, starts a new session on\n * every full navigation and gives every tab one of its own.\n */\ninterface StoredSession {\n id: string;\n /** When the last event was sent. The timeout is measured from here, and only from here. */\n lastEventTime: number;\n}\n\n/** The session an event belongs to, and whether that event is the one that started it. */\ninterface SessionForEvent {\n id: string;\n started: boolean;\n}\n\n/**\n * `<version>.<id>.<lastEventTime>`, compact and cookie-safe rather than JSON: `{`, `\"`\n * and `,` all have to be percent-encoded in a cookie, and a host that wants one session across\n * its subdomains will hand `setupAnalytics` a cookie-backed `storage`, where this has to survive\n * unchanged. A uuidv7 contains no dots, so the record splits cleanly.\n *\n * The version guards a change the parser could not otherwise survive. A field appended to the end\n * does not need one — a short record simply leaves it undefined.\n */\nconst VERSION = '1';\n\nfunction readSession(): StoredSession | undefined {\n const raw = config.storage.getItem(keys.session);\n if (!raw) return undefined;\n\n const [version, id, lastEventTime] = raw.split('.');\n if (version !== VERSION || !id) return undefined;\n\n const parsed = { id, lastEventTime: Number(lastEventTime) };\n if (!Number.isFinite(parsed.lastEventTime)) return undefined;\n return parsed;\n}\n\nfunction writeSession({ id, lastEventTime }: StoredSession) {\n config.storage.setItem(keys.session, `${VERSION}.${id}.${lastEventTime}`);\n}\n\nclass Session {\n /**\n * Engagement is deliberately not stored: it is the time this page has accrued and not yet\n * reported, so it belongs to the page, not to the session. GA4 draws the same line — its cookie\n * carries the session, while the engagement timer lives and dies with the document.\n */\n private
|
|
1
|
+
{"version":3,"file":"session.mjs","names":["uuidv7"],"sources":["../../src/setup/session.ts"],"sourcesContent":["import { v7 as uuidv7 } from 'uuid';\nimport { keys } from '../constants/storage';\nimport { config } from './index';\n\nexport const SESSION_TIMEOUT = 30 * 60 * 1000;\n\n/**\n * The part of a session that outlives the page: its identity, and the clock the timeout is\n * measured against. GA4 keeps these in its `_ga_<container>` cookie — alongside the engaged flag,\n * which arrives with `session_engaged` later — and rereads them for every event, which is what\n * makes a session survive a reload, span a whole multipage visit, and stay shared between two\n * tabs of the same site. Holding them in memory instead, as this did, starts a new session on\n * every full navigation and gives every tab one of its own.\n */\ninterface StoredSession {\n id: string;\n /** When the last event was sent. The timeout is measured from here, and only from here. */\n lastEventTime: number;\n}\n\n/** The session an event belongs to, and whether that event is the one that started it. */\ninterface SessionForEvent {\n id: string;\n started: boolean;\n}\n\n/**\n * `<version>.<id>.<lastEventTime>`, compact and cookie-safe rather than JSON: `{`, `\"`\n * and `,` all have to be percent-encoded in a cookie, and a host that wants one session across\n * its subdomains will hand `setupAnalytics` a cookie-backed `storage`, where this has to survive\n * unchanged. A uuidv7 contains no dots, so the record splits cleanly.\n *\n * The version guards a change the parser could not otherwise survive. A field appended to the end\n * does not need one — a short record simply leaves it undefined.\n */\nconst VERSION = '1';\n\nfunction readSession(): StoredSession | undefined {\n const raw = config.storage.getItem(keys.session);\n if (!raw) return undefined;\n\n const [version, id, lastEventTime] = raw.split('.');\n if (version !== VERSION || !id) return undefined;\n\n const parsed = { id, lastEventTime: Number(lastEventTime) };\n if (!Number.isFinite(parsed.lastEventTime)) return undefined;\n return parsed;\n}\n\nfunction writeSession({ id, lastEventTime }: StoredSession) {\n config.storage.setItem(keys.session, `${VERSION}.${id}.${lastEventTime}`);\n}\n\nclass Session {\n /**\n * Engagement is deliberately not stored: it is the time this page has accrued and not yet\n * reported, so it belongs to the page, not to the session. GA4 draws the same line — its cookie\n * carries the session, while the engagement timer lives and dies with the document.\n *\n * `lastTickTime` is where the accumulator last settled up, rewritten on every tick — it is not\n * when the session began, and nothing here needs to know that.\n */\n private lastTickTime: number;\n private accumulatedTime: number;\n\n private active: boolean;\n private visible: boolean;\n private focused: boolean;\n\n constructor() {\n this.lastTickTime = Date.now();\n this.accumulatedTime = 0;\n\n this.active = true;\n this.visible = typeof document !== 'undefined' ? document.visibilityState === 'visible' : true;\n this.focused = typeof document !== 'undefined' ? document.hasFocus() : true;\n }\n\n /**\n * The session a batch of events belongs to: read the stored one, start a new one if it has\n * timed out or there is none, stamp it with when those events happened and write it back. Every\n * event goes through here, exactly as GA4 rereads and rewrites its cookie per event — caching\n * the session in memory would put the tabs back out of step with each other.\n */\n touch = (eventTime: number, lastEventTime = eventTime): SessionForEvent => {\n const stored = readSession();\n\n if (stored && eventTime - stored.lastEventTime <= SESSION_TIMEOUT) {\n // `Math.max`, because a batch that waited in a frozen tab can be older than what another\n // tab has since written, and a session must never be shortened by a late arrival.\n writeSession({ ...stored, lastEventTime: Math.max(stored.lastEventTime, lastEventTime) });\n return { id: stored.id, started: false };\n }\n\n // Engagement the previous session accrued but never reported dies with it rather than being\n // handed to its successor. GA4 does the same on `session_start`.\n this.accumulatedTime = 0;\n // Wall clock, not `eventTime`: this anchors the engagement timer for the page in front of the\n // visitor now, which a batch describing something that happened an hour ago says nothing about.\n this.lastTickTime = Date.now();\n\n const session: StoredSession = { id: uuidv7(), lastEventTime };\n writeSession(session);\n return { id: session.id, started: true };\n };\n\n /**\n * The id for an event that must not start a session — the `pagehide` beacon, which reports what\n * the session now ending accrued. A live session is extended, as any event extends it; one\n * already past its timeout still owns that engagement, so its id comes back without being\n * revived into a session no `session_start` ever announced.\n */\n extend = (): string => {\n const stored = readSession();\n if (!stored) return this.touch(Date.now()).id;\n\n const now = Date.now();\n if (now - stored.lastEventTime <= SESSION_TIMEOUT) {\n writeSession({ ...stored, lastEventTime: now });\n }\n return stored.id;\n };\n\n isActive = () => this.active;\n\n updateActive = (active: boolean) => {\n this.active = active;\n };\n\n updateAccumulator = () => {\n const now = Date.now();\n if (this.focused && this.visible && this.active) {\n const delta = now - this.lastTickTime;\n if (delta > 0 && delta < SESSION_TIMEOUT) {\n this.accumulatedTime += delta;\n }\n }\n this.lastTickTime = now;\n };\n\n focus = () => {\n this.updateAccumulator();\n this.focused = true;\n };\n\n blur = () => {\n this.updateAccumulator();\n this.focused = false;\n };\n\n pageshow = () => {\n this.updateAccumulator();\n this.active = true;\n };\n\n pagehide = () => {\n this.updateAccumulator();\n this.active = false;\n };\n\n visibilitychange = (state: DocumentVisibilityState) => {\n this.updateAccumulator();\n this.visible = state === 'visible';\n };\n\n flush = () => {\n this.updateAccumulator();\n const engagementTime = this.accumulatedTime;\n this.accumulatedTime = 0;\n return engagementTime;\n };\n}\n\nlet session: Session | undefined;\n\n/**\n * The session, built the first time something asks for it.\n *\n * Deliberately not a module-scope `new Session()`. The constructor calls\n * `uuidv7()`, `uuid` draws its bytes from `crypto.getRandomValues`, and\n * Cloudflare Workers reject that outside a request handler:\n *\n * Disallowed operation called within global scope. Asynchronous I/O\n * (ex: fetch() or connect()), setting a timeout, and generating random\n * values are not allowed within global scope.\n *\n * Module scope in a Worker is evaluated once when the isolate boots and is\n * then shared by every request that isolate serves, so a random value drawn\n * there would be the same for all of them — which is why the runtime refuses\n * to produce one. A host that server-renders on Workers reaches this module\n * through `track()` on the server as well, and the throw happened as the\n * isolate booted, taking down every route before a component rendered.\n *\n * Everything here is per-visitor browser or app state, so deferring the\n * construction costs nothing and lets the server bundle be evaluated. It does\n * not make any of it safe to use there: this instance, `cache` and `config` are\n * module singletons, so calling `track()` on a server shares one session and one\n * visitor across every request the isolate serves. Import it there; do not track.\n */\nexport function getSession() {\n return (session ??= new Session());\n}\n"],"mappings":";;;;AAIA,MAAa,kBAAkB,OAAU;;;;;;;;;;AA+BzC,MAAM,UAAU;AAEhB,SAAS,cAAyC;CAChD,MAAM,MAAM,OAAO,QAAQ,QAAQ,KAAK,OAAO;CAC/C,IAAI,CAAC,KAAK,OAAO,KAAA;CAEjB,MAAM,CAAC,SAAS,IAAI,iBAAiB,IAAI,MAAM,GAAG;CAClD,IAAI,YAAY,WAAW,CAAC,IAAI,OAAO,KAAA;CAEvC,MAAM,SAAS;EAAE;EAAI,eAAe,OAAO,aAAa;CAAE;CAC1D,IAAI,CAAC,OAAO,SAAS,OAAO,aAAa,GAAG,OAAO,KAAA;CACnD,OAAO;AACT;AAEA,SAAS,aAAa,EAAE,IAAI,iBAAgC;CAC1D,OAAO,QAAQ,QAAQ,KAAK,SAAS,GAAG,QAAQ,GAAG,GAAG,GAAG,eAAe;AAC1E;AAEA,IAAM,UAAN,MAAc;CAgBZ,cAAc;EAeL,KAAA,SAAA,WAAmB,gBAAgB,cAA+B;GACzE,MAAM,SAAS,YAAY;GAE3B,IAAI,UAAU,YAAY,OAAO,iBAAA,MAAkC;IAGjE,aAAa;KAAE,GAAG;KAAQ,eAAe,KAAK,IAAI,OAAO,eAAe,aAAa;IAAE,CAAC;IACxF,OAAO;KAAE,IAAI,OAAO;KAAI,SAAS;IAAM;GACzC;GAIA,KAAK,kBAAkB;GAGvB,KAAK,eAAe,KAAK,IAAI;GAE7B,MAAM,UAAyB;IAAE,IAAIA,GAAO;IAAG;GAAc;GAC7D,aAAa,OAAO;GACpB,OAAO;IAAE,IAAI,QAAQ;IAAI,SAAS;GAAK;EACzC;EAQuB,KAAA,eAAA;GACrB,MAAM,SAAS,YAAY;GAC3B,IAAI,CAAC,QAAQ,OAAO,KAAK,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC;GAE3C,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,MAAM,OAAO,iBAAA,MACf,aAAa;IAAE,GAAG;IAAQ,eAAe;GAAI,CAAC;GAEhD,OAAO,OAAO;EAChB;EAEiB,KAAA,iBAAA,KAAK;EAEN,KAAA,gBAAA,WAAoB;GAClC,KAAK,SAAS;EAChB;EAE0B,KAAA,0BAAA;GACxB,MAAM,MAAM,KAAK,IAAI;GACrB,IAAI,KAAK,WAAW,KAAK,WAAW,KAAK,QAAQ;IAC/C,MAAM,QAAQ,MAAM,KAAK;IACzB,IAAI,QAAQ,KAAK,QAAA,MACf,KAAK,mBAAmB;GAE5B;GACA,KAAK,eAAe;EACtB;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEa,KAAA,aAAA;GACX,KAAK,kBAAkB;GACvB,KAAK,UAAU;EACjB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEiB,KAAA,iBAAA;GACf,KAAK,kBAAkB;GACvB,KAAK,SAAS;EAChB;EAEoB,KAAA,oBAAA,UAAmC;GACrD,KAAK,kBAAkB;GACvB,KAAK,UAAU,UAAU;EAC3B;EAEc,KAAA,cAAA;GACZ,KAAK,kBAAkB;GACvB,MAAM,iBAAiB,KAAK;GAC5B,KAAK,kBAAkB;GACvB,OAAO;EACT;EApGE,KAAK,eAAe,KAAK,IAAI;EAC7B,KAAK,kBAAkB;EAEvB,KAAK,SAAS;EACd,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,oBAAoB,YAAY;EAC1F,KAAK,UAAU,OAAO,aAAa,cAAc,SAAS,SAAS,IAAI;CACzE;AA+FF;AAEA,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;AA0BJ,SAAgB,aAAa;CAC3B,OAAQ,YAAY,IAAI,QAAQ;AAClC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/test/setup.ts
|
|
3
|
+
/**
|
|
4
|
+
* The SDK's state — `config`, `cache`, the session singleton, the track queue — lives in module
|
|
5
|
+
* scope, exactly as it does in a browser. Tests therefore load the modules fresh per test with
|
|
6
|
+
* `vi.resetModules()` and dynamic imports; this file only provides the pieces every test hands to
|
|
7
|
+
* `setupAnalytics`, and is itself imported dynamically so it joins the same module graph.
|
|
8
|
+
*/
|
|
9
|
+
function memoryStorage(seed = {}) {
|
|
10
|
+
const map = new Map(Object.entries(seed));
|
|
11
|
+
return {
|
|
12
|
+
map,
|
|
13
|
+
getItem: (key) => map.get(key) ?? null,
|
|
14
|
+
setItem: (key, value) => {
|
|
15
|
+
map.set(key, value);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function baseOptions(overrides = {}) {
|
|
20
|
+
return {
|
|
21
|
+
release: "1.0.0",
|
|
22
|
+
storage: memoryStorage(),
|
|
23
|
+
endpoint: "https://api.test",
|
|
24
|
+
platform: "web",
|
|
25
|
+
environment: "production",
|
|
26
|
+
getTags: () => ({}),
|
|
27
|
+
getDeviceId: () => "device-1",
|
|
28
|
+
...overrides
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** A minimal ok Response whose json body is `data`. */
|
|
32
|
+
function jsonResponse(data, status = 200) {
|
|
33
|
+
return new Response(JSON.stringify(data), {
|
|
34
|
+
status,
|
|
35
|
+
headers: { "Content-Type": "application/json" }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
exports.baseOptions = baseOptions;
|
|
40
|
+
exports.jsonResponse = jsonResponse;
|
|
41
|
+
exports.memoryStorage = memoryStorage;
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=setup.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.cjs","names":[],"sources":["../../src/test/setup.ts"],"sourcesContent":["import type { Options } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\n/**\n * The SDK's state — `config`, `cache`, the session singleton, the track queue — lives in module\n * scope, exactly as it does in a browser. Tests therefore load the modules fresh per test with\n * `vi.resetModules()` and dynamic imports; this file only provides the pieces every test hands to\n * `setupAnalytics`, and is itself imported dynamically so it joins the same module graph.\n */\nexport function memoryStorage(seed: Record<string, string> = {}) {\n const map = new Map(Object.entries(seed));\n return {\n map,\n getItem: (key: string) => map.get(key) ?? null,\n setItem: (key: string, value: string) => {\n map.set(key, value);\n },\n };\n}\n\nexport function baseOptions(overrides: Partial<Options> = {}): Options {\n return {\n release: '1.0.0',\n storage: memoryStorage(),\n endpoint: 'https://api.test',\n platform: 'web',\n environment: 'production',\n getTags: (): TrackTags => ({}),\n getDeviceId: () => 'device-1',\n ...overrides,\n };\n}\n\n/** A minimal ok Response whose json body is `data`. */\nexport function jsonResponse(data: unknown, status = 200) {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n"],"mappings":";;;;;;;;AASA,SAAgB,cAAc,OAA+B,CAAC,GAAG;CAC/D,MAAM,MAAM,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC;CACxC,OAAO;EACL;EACA,UAAU,QAAgB,IAAI,IAAI,GAAG,KAAK;EAC1C,UAAU,KAAa,UAAkB;GACvC,IAAI,IAAI,KAAK,KAAK;EACpB;CACF;AACF;AAEA,SAAgB,YAAY,YAA8B,CAAC,GAAY;CACrE,OAAO;EACL,SAAS;EACT,SAAS,cAAc;EACvB,UAAU;EACV,UAAU;EACV,aAAa;EACb,gBAA2B,CAAC;EAC5B,mBAAmB;EACnB,GAAG;CACL;AACF;;AAGA,SAAgB,aAAa,MAAe,SAAS,KAAK;CACxD,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC;EACA,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Options } from "../setup/index.cjs";
|
|
2
|
+
//#region src/test/setup.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The SDK's state — `config`, `cache`, the session singleton, the track queue — lives in module
|
|
5
|
+
* scope, exactly as it does in a browser. Tests therefore load the modules fresh per test with
|
|
6
|
+
* `vi.resetModules()` and dynamic imports; this file only provides the pieces every test hands to
|
|
7
|
+
* `setupAnalytics`, and is itself imported dynamically so it joins the same module graph.
|
|
8
|
+
*/
|
|
9
|
+
declare function memoryStorage(seed?: Record<string, string>): {
|
|
10
|
+
map: Map<string, string>;
|
|
11
|
+
getItem: (key: string) => string | null;
|
|
12
|
+
setItem: (key: string, value: string) => void;
|
|
13
|
+
};
|
|
14
|
+
declare function baseOptions(overrides?: Partial<Options>): Options;
|
|
15
|
+
/** A minimal ok Response whose json body is `data`. */
|
|
16
|
+
declare function jsonResponse(data: unknown, status?: number): Response;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { baseOptions, jsonResponse, memoryStorage };
|
|
19
|
+
//# sourceMappingURL=setup.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.cts","names":[],"sources":["../../src/test/setup.ts"],"mappings":";;;;;;;;iBASgB,cAAc,OAAM;OAAA;EAIjB,UAAA;EACA,UAAA,aAAM;;iBAMT,YAAY,YAAW,QAAQ,WAAgB;;iBAc/C,aAAa,eAAe,kBAAY"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Options } from "../setup/index.mjs";
|
|
2
|
+
//#region src/test/setup.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* The SDK's state — `config`, `cache`, the session singleton, the track queue — lives in module
|
|
5
|
+
* scope, exactly as it does in a browser. Tests therefore load the modules fresh per test with
|
|
6
|
+
* `vi.resetModules()` and dynamic imports; this file only provides the pieces every test hands to
|
|
7
|
+
* `setupAnalytics`, and is itself imported dynamically so it joins the same module graph.
|
|
8
|
+
*/
|
|
9
|
+
declare function memoryStorage(seed?: Record<string, string>): {
|
|
10
|
+
map: Map<string, string>;
|
|
11
|
+
getItem: (key: string) => string | null;
|
|
12
|
+
setItem: (key: string, value: string) => void;
|
|
13
|
+
};
|
|
14
|
+
declare function baseOptions(overrides?: Partial<Options>): Options;
|
|
15
|
+
/** A minimal ok Response whose json body is `data`. */
|
|
16
|
+
declare function jsonResponse(data: unknown, status?: number): Response;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { baseOptions, jsonResponse, memoryStorage };
|
|
19
|
+
//# sourceMappingURL=setup.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.mts","names":[],"sources":["../../src/test/setup.ts"],"mappings":";;;;;;;;iBASgB,cAAc,OAAM;OAAA;EAIjB,UAAA;EACA,UAAA,aAAM;;iBAMT,YAAY,YAAW,QAAQ,WAAgB;;iBAc/C,aAAa,eAAe,kBAAY"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/test/setup.ts
|
|
2
|
+
/**
|
|
3
|
+
* The SDK's state — `config`, `cache`, the session singleton, the track queue — lives in module
|
|
4
|
+
* scope, exactly as it does in a browser. Tests therefore load the modules fresh per test with
|
|
5
|
+
* `vi.resetModules()` and dynamic imports; this file only provides the pieces every test hands to
|
|
6
|
+
* `setupAnalytics`, and is itself imported dynamically so it joins the same module graph.
|
|
7
|
+
*/
|
|
8
|
+
function memoryStorage(seed = {}) {
|
|
9
|
+
const map = new Map(Object.entries(seed));
|
|
10
|
+
return {
|
|
11
|
+
map,
|
|
12
|
+
getItem: (key) => map.get(key) ?? null,
|
|
13
|
+
setItem: (key, value) => {
|
|
14
|
+
map.set(key, value);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function baseOptions(overrides = {}) {
|
|
19
|
+
return {
|
|
20
|
+
release: "1.0.0",
|
|
21
|
+
storage: memoryStorage(),
|
|
22
|
+
endpoint: "https://api.test",
|
|
23
|
+
platform: "web",
|
|
24
|
+
environment: "production",
|
|
25
|
+
getTags: () => ({}),
|
|
26
|
+
getDeviceId: () => "device-1",
|
|
27
|
+
...overrides
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** A minimal ok Response whose json body is `data`. */
|
|
31
|
+
function jsonResponse(data, status = 200) {
|
|
32
|
+
return new Response(JSON.stringify(data), {
|
|
33
|
+
status,
|
|
34
|
+
headers: { "Content-Type": "application/json" }
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { baseOptions, jsonResponse, memoryStorage };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=setup.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.mjs","names":[],"sources":["../../src/test/setup.ts"],"sourcesContent":["import type { Options } from '../setup/index';\nimport type { TrackTags } from '../track/types';\n\n/**\n * The SDK's state — `config`, `cache`, the session singleton, the track queue — lives in module\n * scope, exactly as it does in a browser. Tests therefore load the modules fresh per test with\n * `vi.resetModules()` and dynamic imports; this file only provides the pieces every test hands to\n * `setupAnalytics`, and is itself imported dynamically so it joins the same module graph.\n */\nexport function memoryStorage(seed: Record<string, string> = {}) {\n const map = new Map(Object.entries(seed));\n return {\n map,\n getItem: (key: string) => map.get(key) ?? null,\n setItem: (key: string, value: string) => {\n map.set(key, value);\n },\n };\n}\n\nexport function baseOptions(overrides: Partial<Options> = {}): Options {\n return {\n release: '1.0.0',\n storage: memoryStorage(),\n endpoint: 'https://api.test',\n platform: 'web',\n environment: 'production',\n getTags: (): TrackTags => ({}),\n getDeviceId: () => 'device-1',\n ...overrides,\n };\n}\n\n/** A minimal ok Response whose json body is `data`. */\nexport function jsonResponse(data: unknown, status = 200) {\n return new Response(JSON.stringify(data), {\n status,\n headers: { 'Content-Type': 'application/json' },\n });\n}\n"],"mappings":";;;;;;;AASA,SAAgB,cAAc,OAA+B,CAAC,GAAG;CAC/D,MAAM,MAAM,IAAI,IAAI,OAAO,QAAQ,IAAI,CAAC;CACxC,OAAO;EACL;EACA,UAAU,QAAgB,IAAI,IAAI,GAAG,KAAK;EAC1C,UAAU,KAAa,UAAkB;GACvC,IAAI,IAAI,KAAK,KAAK;EACpB;CACF;AACF;AAEA,SAAgB,YAAY,YAA8B,CAAC,GAAY;CACrE,OAAO;EACL,SAAS;EACT,SAAS,cAAc;EACvB,UAAU;EACV,UAAU;EACV,aAAa;EACb,gBAA2B,CAAC;EAC5B,mBAAmB;EACnB,GAAG;CACL;AACF;;AAGA,SAAgB,aAAa,MAAe,SAAS,KAAK;CACxD,OAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;EACxC;EACA,SAAS,EAAE,gBAAgB,mBAAmB;CAChD,CAAC;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-pixel.cjs","names":["mapFBEvent","getFirst"],"sources":["../../src/third-parties/meta-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { type FBQ, type PixelId, mapFBEvent } from '../track/fbq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ndeclare global {\n interface Window {\n /** Undefined until the Meta Pixel script has loaded. */\n fbq?: FBQ['fbq'];\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\nexport function sendFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n event_id?: string\n) {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n if (metrics.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 options = { eventID: event_id };\n const [type, fbEventName, fbEventProperties] = mapFBEvent(name, properties);\n // The two branches are identical on purpose. `fbq` is overloaded per `type` — 'track' takes a\n // standard event name with its typed properties, 'trackCustom' an arbitrary string — and what\n // `mapFBEvent` returns is a union of both shapes. Narrowing `type` is what picks a single\n // overload; collapsing the branches leaves the call matching none of them.\n if (type === 'track') {\n window.fbq(type, fbEventName, fbEventProperties, options);\n } else {\n window.fbq(type, fbEventName, fbEventProperties, options);\n }\n}\n\nexport function setFBUser(pixelId: PixelId) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n\n const address = getFirst(user_data?.address);\n\n window.fbq('init', pixelId, {\n em: getFirst(user_data?.email),\n fn: address?.first_name,\n ln: address?.last_name,\n ph: getFirst(user_data?.phone_number),\n external_id: user_id,\n ct: address?.city,\n st: address?.
|
|
1
|
+
{"version":3,"file":"meta-pixel.cjs","names":["mapFBEvent","getFirst"],"sources":["../../src/third-parties/meta-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { type FBQ, type PixelId, mapFBEvent } from '../track/fbq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ndeclare global {\n interface Window {\n /** Undefined until the Meta Pixel script has loaded. */\n fbq?: FBQ['fbq'];\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\nexport function sendFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n event_id?: string\n) {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n if (metrics.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 options = { eventID: event_id };\n const [type, fbEventName, fbEventProperties] = mapFBEvent(name, properties);\n // The two branches are identical on purpose. `fbq` is overloaded per `type` — 'track' takes a\n // standard event name with its typed properties, 'trackCustom' an arbitrary string — and what\n // `mapFBEvent` returns is a union of both shapes. Narrowing `type` is what picks a single\n // overload; collapsing the branches leaves the call matching none of them.\n if (type === 'track') {\n window.fbq(type, fbEventName, fbEventProperties, options);\n } else {\n window.fbq(type, fbEventName, fbEventProperties, options);\n }\n}\n\nexport function setFBUser(pixelId: PixelId) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n\n const address = getFirst(user_data?.address);\n\n window.fbq('init', pixelId, {\n em: getFirst(user_data?.email),\n fn: address?.first_name,\n ln: address?.last_name,\n ph: getFirst(user_data?.phone_number),\n external_id: user_id,\n ct: address?.city,\n // `st` is Meta's state/province field, not the street address.\n st: address?.region,\n zp: address?.postal_code,\n country: address?.country,\n });\n };\n}\n"],"mappings":";;;;AAYA,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;AAAM;AAE1D,SAAgB,YACd,MACA,YACA,UACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,KAAK;EAChD,QAAQ,KAAK,8BAA8B;EAC3C;CACF;CACA,IAAI,QAAQ,SAAS,IAAI,GAAG;CAC5B,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,UAAU,EAAE,SAAS,SAAS;CACpC,MAAM,CAAC,MAAM,aAAa,qBAAqBA,kBAAAA,WAAW,MAAM,UAAU;CAK1E,IAAI,SAAS,SACX,OAAO,IAAI,MAAM,aAAa,mBAAmB,OAAO;MAExD,OAAO,IAAI,MAAM,aAAa,mBAAmB,OAAO;AAE5D;AAEA,SAAgB,UAAU,SAAkB;CAC1C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,KAAK;GAChD,QAAQ,KAAK,8BAA8B;GAC3C;EACF;EAEA,MAAM,UAAUC,oBAAAA,SAAS,WAAW,OAAO;EAE3C,OAAO,IAAI,QAAQ,SAAS;GAC1B,IAAIA,oBAAAA,SAAS,WAAW,KAAK;GAC7B,IAAI,SAAS;GACb,IAAI,SAAS;GACb,IAAIA,oBAAAA,SAAS,WAAW,YAAY;GACpC,aAAa;GACb,IAAI,SAAS;GAEb,IAAI,SAAS;GACb,IAAI,SAAS;GACb,SAAS,SAAS;EACpB,CAAC;CACH;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta-pixel.mjs","names":[],"sources":["../../src/third-parties/meta-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { type FBQ, type PixelId, mapFBEvent } from '../track/fbq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ndeclare global {\n interface Window {\n /** Undefined until the Meta Pixel script has loaded. */\n fbq?: FBQ['fbq'];\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\nexport function sendFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n event_id?: string\n) {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n if (metrics.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 options = { eventID: event_id };\n const [type, fbEventName, fbEventProperties] = mapFBEvent(name, properties);\n // The two branches are identical on purpose. `fbq` is overloaded per `type` — 'track' takes a\n // standard event name with its typed properties, 'trackCustom' an arbitrary string — and what\n // `mapFBEvent` returns is a union of both shapes. Narrowing `type` is what picks a single\n // overload; collapsing the branches leaves the call matching none of them.\n if (type === 'track') {\n window.fbq(type, fbEventName, fbEventProperties, options);\n } else {\n window.fbq(type, fbEventName, fbEventProperties, options);\n }\n}\n\nexport function setFBUser(pixelId: PixelId) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n\n const address = getFirst(user_data?.address);\n\n window.fbq('init', pixelId, {\n em: getFirst(user_data?.email),\n fn: address?.first_name,\n ln: address?.last_name,\n ph: getFirst(user_data?.phone_number),\n external_id: user_id,\n ct: address?.city,\n st: address?.
|
|
1
|
+
{"version":3,"file":"meta-pixel.mjs","names":[],"sources":["../../src/third-parties/meta-pixel.ts"],"sourcesContent":["import type { UpdateVisitorDTO } from '../schema/index';\nimport { type FBQ, type PixelId, mapFBEvent } from '../track/fbq';\nimport type { EventName, TrackName, TrackProperties } from '../track/types';\nimport { getFirst } from '../utils/field';\n\ndeclare global {\n interface Window {\n /** Undefined until the Meta Pixel script has loaded. */\n fbq?: FBQ['fbq'];\n }\n}\n\nconst metrics = ['CLS', 'FCP', 'FID', 'INP', 'LCP', 'TTFB'];\n\nexport function sendFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>,\n event_id?: string\n) {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n if (metrics.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 options = { eventID: event_id };\n const [type, fbEventName, fbEventProperties] = mapFBEvent(name, properties);\n // The two branches are identical on purpose. `fbq` is overloaded per `type` — 'track' takes a\n // standard event name with its typed properties, 'trackCustom' an arbitrary string — and what\n // `mapFBEvent` returns is a union of both shapes. Narrowing `type` is what picks a single\n // overload; collapsing the branches leaves the call matching none of them.\n if (type === 'track') {\n window.fbq(type, fbEventName, fbEventProperties, options);\n } else {\n window.fbq(type, fbEventName, fbEventProperties, options);\n }\n}\n\nexport function setFBUser(pixelId: PixelId) {\n return ({ user_id, user_data }: UpdateVisitorDTO) => {\n if (typeof window === 'undefined' || !window.fbq) {\n console.warn('fbq has not been initialized');\n return;\n }\n\n const address = getFirst(user_data?.address);\n\n window.fbq('init', pixelId, {\n em: getFirst(user_data?.email),\n fn: address?.first_name,\n ln: address?.last_name,\n ph: getFirst(user_data?.phone_number),\n external_id: user_id,\n ct: address?.city,\n // `st` is Meta's state/province field, not the street address.\n st: address?.region,\n zp: address?.postal_code,\n country: address?.country,\n });\n };\n}\n"],"mappings":";;;AAYA,MAAM,UAAU;CAAC;CAAO;CAAO;CAAO;CAAO;CAAO;AAAM;AAE1D,SAAgB,YACd,MACA,YACA,UACA;CACA,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,KAAK;EAChD,QAAQ,KAAK,8BAA8B;EAC3C;CACF;CACA,IAAI,QAAQ,SAAS,IAAI,GAAG;CAC5B,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAChD,IAAI,OAAO,SAAS,KAAK,SAAS,WAAW,GAAG;CAEhD,MAAM,UAAU,EAAE,SAAS,SAAS;CACpC,MAAM,CAAC,MAAM,aAAa,qBAAqB,WAAW,MAAM,UAAU;CAK1E,IAAI,SAAS,SACX,OAAO,IAAI,MAAM,aAAa,mBAAmB,OAAO;MAExD,OAAO,IAAI,MAAM,aAAa,mBAAmB,OAAO;AAE5D;AAEA,SAAgB,UAAU,SAAkB;CAC1C,QAAQ,EAAE,SAAS,gBAAkC;EACnD,IAAI,OAAO,WAAW,eAAe,CAAC,OAAO,KAAK;GAChD,QAAQ,KAAK,8BAA8B;GAC3C;EACF;EAEA,MAAM,UAAU,SAAS,WAAW,OAAO;EAE3C,OAAO,IAAI,QAAQ,SAAS;GAC1B,IAAI,SAAS,WAAW,KAAK;GAC7B,IAAI,SAAS;GACb,IAAI,SAAS;GACb,IAAI,SAAS,WAAW,YAAY;GACpC,aAAa;GACb,IAAI,SAAS;GAEb,IAAI,SAAS;GACb,IAAI,SAAS;GACb,SAAS,SAAS;EACpB,CAAC;CACH;AACF"}
|
package/dist/track/fbq.cjs
CHANGED
|
@@ -12,9 +12,9 @@ function normalize(parameters) {
|
|
|
12
12
|
zp: parameters.zp?.split("-").at(0)?.trim(),
|
|
13
13
|
fn: parameters.fn?.toLowerCase().trim(),
|
|
14
14
|
ln: parameters.ln?.toLowerCase().trim(),
|
|
15
|
-
ct: parameters.ct?.toLowerCase().replace(/[s/-]/g, "")
|
|
16
|
-
st: parameters.st?.toLowerCase().replace(/[s
|
|
17
|
-
country: parameters.country?.toLowerCase().replace(/[s/-]/g, "")
|
|
15
|
+
ct: parameters.ct?.toLowerCase().replace(/[\s/-]/g, ""),
|
|
16
|
+
st: parameters.st?.toLowerCase().replace(/[\s/,.-]/g, ""),
|
|
17
|
+
country: parameters.country?.toLowerCase().replace(/[\s/-]/g, "")
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
function mapItems(items) {
|
package/dist/track/fbq.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fbq.cjs","names":["p"],"sources":["../../src/track/fbq.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport type { Item } from './gtag';\nimport type { EventName, TrackName, TrackProperties } from './types';\n\nexport type Content = {\n id: string;\n quantity: number;\n item_price?: number;\n title?: string;\n description?: string;\n brand?: string;\n category?: string;\n delivery_category?: string;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport type MatchingParameters = {\n /** Email: Unhashed lowercase or hashed SHA-256 */\n em?: string;\n\n /** First Name: Lowercase letters */\n fn?: string;\n\n /** Last Name: Lowercase letters */\n ln?: string;\n\n /** Phone Number: Digits only including country code and area code */\n ph?: string;\n\n /**\n * External ID: Any unique ID from the advertiser, such as loyalty membership ID, user ID, and\n * external cookie ID.\n */\n external_id?: string;\n\n /** Gender: Single lowercase letter, f or m, if unknown, leave blank */\n ge?: 'f' | 'm' | '';\n\n /** Birthdate: Digits only with birth year, month, then day, YYYYMMDD */\n db?: number;\n\n /** City: Lowercase with any spaces removed, e.g. \"menlopark\" */\n ct?: string;\n\n /** State or Province: Lowercase two-letter state or province code, e.g. \"ca\" */\n st?: string;\n\n /** Zip or Postal Code: String */\n zp?: string;\n\n /** Country: Lowercase two-letter country code, e.g. \"us\" */\n country?: string;\n\n /** Client IP Address: Do not hash. */\n client_ip_address?: string;\n\n /** Client User Agent: Do not hash. */\n client_user_agent?: string;\n\n /**\n * Click ID: Do not hash.\n * The Facebook click ID value is stored in the _fbc browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value or generate this value from a fbclid\n * query parameter.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${fbclid}.\n */\n fbc?: string;\n\n /**\n * Browser ID: Do not hash.\n * The Facebook browser ID value is stored in the _fbp browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${random_number}.\n */\n fbp?: string;\n\n /**\n * Subscription ID: Do not hash.\n * The subscription ID for the user in this transaction; it is similar to the order ID for an\n * individual product.\n */\n subscription_id?: string;\n\n /**\n * Facebook Login ID: Do not hash.\n * The ID issued by Meta when a person first logs into an instance of an app. This is also known\n * as App-Scoped ID.\n */\n fb_login_id?: number;\n\n /**\n * Lead ID: Do not hash.\n * The ID associated with a lead generated by [Meta's Lead Ads](https://developers.facebook.com/docs/marketing-api/guides/lead-ads).\n */\n lead_id?: number;\n\n /**\n * Install ID: Do not hash.\n * Your install ID. This field represents unique application installation instances.\n * Note: This parameter is for app events only.\n */\n anon_id?: string;\n\n /**\n * Your mobile advertiser ID, the advertising ID from an Android device or the Advertising\n * Identifier (IDFA) from an Apple device.\n */\n madid?: string;\n\n /**\n * Page ID: Do not hash.\n * Your Page ID. Specifies the page ID associated with the event. Use the Facebook page ID of the\n * page associated with the bot.\n */\n page_id?: string;\n\n /**\n * Page Scoped User ID: Do not hash.\n * Specifies the page-scoped user ID associated with the messenger bot that logs the event. Use\n * the page-scoped user ID provided to your webhook.\n */\n page_scoped_user_id?: string;\n\n /**\n * Do not hash.\n * Click ID generated by Meta for ads that click to WhatsApp.\n */\n ctwa_clid?: string;\n\n /**\n * Do not hash.\n * Instagram Account ID that is associated with the business.\n */\n ig_account_id?: string;\n\n /**\n * Do not hash.\n * Users who interact with Instagram are identified by Instagram-Scoped User IDs (IGSID). IGSID\n * can be obtained from this webhook.\n */\n ig_sid?: string;\n};\n\n/**\n * You can include the following predefined object properties with any custom events, and any\n * standard events that support them. Format your parameter object data using JSON. Learn more about\n * event parameters with Blueprint.\n */\nexport type ObjectProperties = {\n content_category?: string;\n content_ids?: string[];\n content_name?: string;\n\n /**\n * Either product or product_group based on the content_ids or contents being passed. If the IDs\n * being passed in content_ids or contents parameter are IDs of products, then the value should be\n * product. If product group IDs are being passed, then the value should be product_group.\n *\n * If no content_type is provided, Meta will match the event to every item that has the same ID,\n * independent of its type.\n */\n content_type?: 'product' | 'product_group' | (string & {});\n contents?: Content[];\n delivery_category?: 'in_store' | 'curbside' | 'home_delivery';\n currency?: string;\n num_items?: number;\n predicted_ltv?: number;\n search_string?: string;\n\n /** Used with the CompleteRegistration event, to show the status of the registration. */\n status?: boolean;\n value?: number;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/payload-helper\n */\nexport type StandardEvents = {\n AddPaymentInfo: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n AddToCart: {\n content_ids?: string[];\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents\n currency?: string;\n value?: number;\n };\n AddToWishlist: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n CompleteRegistration: {\n currency?: string;\n value?: number;\n method?: string;\n };\n Contact: {};\n CustomizeProduct: {};\n Donate: {};\n FindLocation: {};\n InitiateCheckout: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n num_items?: number;\n value?: number;\n };\n Lead: {\n currency?: string;\n value?: number;\n };\n Purchase: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency: string; // required\n num_items?: number;\n value: number; // required\n };\n Schedule: {};\n Search: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n search_string?: string;\n value?: number;\n };\n StartTrial: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n SubmitApplication: {};\n Subscribe: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n ViewContent: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n value?: number;\n };\n};\n\ntype JSONValue =\n | null\n | string\n | number\n | boolean\n | Array<JSONValue>\n | { [value: string]: JSONValue };\n\nexport type PixelId = `${number}`;\nexport type Options = { eventID?: string };\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/reference#standard-events\n *\n * We determine if events are identical based on their ID and name. So, for an event to be deduplicated:\n * - In corresponding events, a Meta Pixel's eventID must match the Conversion API's event_id.\n * - In corresponding events, a Meta Pixel's event must match the Conversion API's event_name.\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport interface FBQ {\n /**\n * reference: https://stackoverflow.com/questions/62304291/sending-user-data-parameters-via-pixel\n *\n * Call init the normal default way first:\n * `fbq('init', 'XXXXX')`\n *\n * And at a later point in time, when you have obtained additional user data, you can call init\n * again basically enriching the already running fbq instance with additional data:\n * `fbq('init', 'XXXXX', { external_id: 1234, em: 'abc@abc.com' } )`\n *\n * Only caveat is that you have to send an event after this additional init call, otherwise the\n * provided data will not be sent to Facebook.\n */\n fbq(type: 'init', pixelId: PixelId, parameters?: MatchingParameters): void;\n\n /** Enable Manual Only mode. (value = false) */\n fbq(type: 'set', key: 'autoConfig', value: boolean, pixelId: PixelId): void;\n\n fbq<T extends keyof StandardEvents>(\n type: 'track',\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n fbq(\n type: 'trackCustom',\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq<T extends keyof StandardEvents>(\n type: 'trackSingle',\n pixelId: PixelId,\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq(\n type: 'trackSingleCustom',\n pixelId: PixelId,\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n}\n\n/**\n * Please download this CSV filefor examples of properly normalized and hashed data for the\n * parameters below.\n */\nexport function normalize(parameters: MatchingParameters): MatchingParameters {\n return {\n ...parameters,\n em: parameters.em?.toLowerCase().trim(),\n ph: parameters.ph?.replace(/[-+()\\s]/g, '').replace(/^0+/, ''),\n zp: parameters.zp?.split('-').at(0)?.trim(),\n fn: parameters.fn?.toLowerCase().trim(),\n ln: parameters.ln?.toLowerCase().trim(),\n ct: parameters.ct?.toLowerCase().replace(/[s/-]/g, '').trim(),\n st: parameters.st\n ?.toLowerCase()\n .replace(/[s/-/,.]/g, '')\n .trim(),\n country: parameters.country?.toLowerCase().replace(/[s/-]/g, '').trim(),\n };\n}\n\nexport function mapItems(items?: Item[]): ObjectProperties {\n if (!items) return {};\n const categories = Array.from(new Set(items.map((i) => i.item_category).filter(Boolean)));\n const contents: Content[] = items.map(\n ({ item_id, quantity, price, item_name, item_brand, item_category, ..._others }) => ({\n id: item_id,\n quantity: quantity ?? 1,\n item_price: price,\n title: item_name,\n brand: item_brand,\n category: item_category,\n })\n );\n\n return {\n content_category: categories.length === 1 ? categories.at(0) : undefined,\n contents,\n content_ids: contents.map((c) => c.id),\n num_items: items.reduce((acc, i) => acc + (i.quantity ?? 1), 0),\n };\n}\n\ntype Mapped<F extends keyof StandardEvents> = ['track', F, StandardEvents[F] & ObjectProperties];\ntype Missed<F extends string> = ['trackCustom', F, Record<string, JSONValue> & ObjectProperties];\n\nexport function mapFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n): Mapped<keyof StandardEvents> | Missed<TrackName<T>> {\n if (name === 'add_payment_info') {\n const p = properties as TrackProperties<'add_payment_info'> | undefined;\n return [\n 'track',\n 'AddPaymentInfo',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_cart') {\n const p = properties as TrackProperties<'add_to_cart'> | undefined;\n return [\n 'track',\n 'AddToCart',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_wishlist') {\n const p = properties as TrackProperties<'add_to_wishlist'> | undefined;\n return [\n 'track',\n 'AddToWishlist',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'login') {\n const p = properties as TrackProperties<'login'> | undefined;\n return ['track', 'CompleteRegistration', { method: p?.method }];\n } else if (name === 'contact') {\n return ['track', 'Contact', {}];\n } else if (name === 'customize_product') {\n return ['track', 'CustomizeProduct', {}];\n } else if (name === 'donate') {\n return ['track', 'Donate', {}];\n } else if (name === 'find_location') {\n return ['track', 'FindLocation', {}];\n } else if (name === 'begin_checkout') {\n const p = properties as TrackProperties<'begin_checkout'> | undefined;\n return [\n 'track',\n 'InitiateCheckout',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'generate_lead') {\n const p = properties as TrackProperties<'generate_lead'> | undefined;\n return ['track', 'Lead', { currency: p?.currency, value: p?.value }];\n } else if (name === 'purchase') {\n const p = properties as TrackProperties<'purchase'> | undefined;\n return [\n 'track',\n 'Purchase',\n { currency: p?.currency ?? 'USD', value: p?.value ?? 0, ...mapItems(p?.items) },\n ];\n } else if (name === 'schedule') {\n return ['track', 'Schedule', {}];\n } else if (name === 'search') {\n const p = properties as TrackProperties<'search'> | undefined;\n return ['track', 'Search', { search_string: p?.search_term }];\n } else if (name === 'trial_begin') {\n const p = properties as TrackProperties<'trial_begin'> | undefined;\n return ['track', 'StartTrial', { currency: p?.currency, value: p?.value }];\n } else if (name === 'submit_application') {\n return ['track', 'SubmitApplication', {}];\n } else if (name === 'subscribe') {\n const p = properties as TrackProperties<'subscribe'> | undefined;\n return ['track', 'Subscribe', { currency: p?.currency, value: p?.value }];\n } else if (name === 'view_item') {\n const p = properties as TrackProperties<'view_item'> | undefined;\n return [\n 'track',\n 'ViewContent',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else {\n return ['trackCustom', name, properties ?? {}];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fbq.cjs","names":["p"],"sources":["../../src/track/fbq.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-empty-object-type */\nimport type { Item } from './gtag';\nimport type { EventName, TrackName, TrackProperties } from './types';\n\nexport type Content = {\n id: string;\n quantity: number;\n item_price?: number;\n title?: string;\n description?: string;\n brand?: string;\n category?: string;\n delivery_category?: string;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/advanced/advanced-matching\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport type MatchingParameters = {\n /** Email: Unhashed lowercase or hashed SHA-256 */\n em?: string;\n\n /** First Name: Lowercase letters */\n fn?: string;\n\n /** Last Name: Lowercase letters */\n ln?: string;\n\n /** Phone Number: Digits only including country code and area code */\n ph?: string;\n\n /**\n * External ID: Any unique ID from the advertiser, such as loyalty membership ID, user ID, and\n * external cookie ID.\n */\n external_id?: string;\n\n /** Gender: Single lowercase letter, f or m, if unknown, leave blank */\n ge?: 'f' | 'm' | '';\n\n /** Birthdate: Digits only with birth year, month, then day, YYYYMMDD */\n db?: number;\n\n /** City: Lowercase with any spaces removed, e.g. \"menlopark\" */\n ct?: string;\n\n /** State or Province: Lowercase two-letter state or province code, e.g. \"ca\" */\n st?: string;\n\n /** Zip or Postal Code: String */\n zp?: string;\n\n /** Country: Lowercase two-letter country code, e.g. \"us\" */\n country?: string;\n\n /** Client IP Address: Do not hash. */\n client_ip_address?: string;\n\n /** Client User Agent: Do not hash. */\n client_user_agent?: string;\n\n /**\n * Click ID: Do not hash.\n * The Facebook click ID value is stored in the _fbc browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value or generate this value from a fbclid\n * query parameter.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${fbclid}.\n */\n fbc?: string;\n\n /**\n * Browser ID: Do not hash.\n * The Facebook browser ID value is stored in the _fbp browser cookie under your domain. See\n * Managing fbc and fbp Parameters for how to get this value.\n *\n * The format is fb.${subdomain_index}.${creation_time}.${random_number}.\n */\n fbp?: string;\n\n /**\n * Subscription ID: Do not hash.\n * The subscription ID for the user in this transaction; it is similar to the order ID for an\n * individual product.\n */\n subscription_id?: string;\n\n /**\n * Facebook Login ID: Do not hash.\n * The ID issued by Meta when a person first logs into an instance of an app. This is also known\n * as App-Scoped ID.\n */\n fb_login_id?: number;\n\n /**\n * Lead ID: Do not hash.\n * The ID associated with a lead generated by [Meta's Lead Ads](https://developers.facebook.com/docs/marketing-api/guides/lead-ads).\n */\n lead_id?: number;\n\n /**\n * Install ID: Do not hash.\n * Your install ID. This field represents unique application installation instances.\n * Note: This parameter is for app events only.\n */\n anon_id?: string;\n\n /**\n * Your mobile advertiser ID, the advertising ID from an Android device or the Advertising\n * Identifier (IDFA) from an Apple device.\n */\n madid?: string;\n\n /**\n * Page ID: Do not hash.\n * Your Page ID. Specifies the page ID associated with the event. Use the Facebook page ID of the\n * page associated with the bot.\n */\n page_id?: string;\n\n /**\n * Page Scoped User ID: Do not hash.\n * Specifies the page-scoped user ID associated with the messenger bot that logs the event. Use\n * the page-scoped user ID provided to your webhook.\n */\n page_scoped_user_id?: string;\n\n /**\n * Do not hash.\n * Click ID generated by Meta for ads that click to WhatsApp.\n */\n ctwa_clid?: string;\n\n /**\n * Do not hash.\n * Instagram Account ID that is associated with the business.\n */\n ig_account_id?: string;\n\n /**\n * Do not hash.\n * Users who interact with Instagram are identified by Instagram-Scoped User IDs (IGSID). IGSID\n * can be obtained from this webhook.\n */\n ig_sid?: string;\n};\n\n/**\n * You can include the following predefined object properties with any custom events, and any\n * standard events that support them. Format your parameter object data using JSON. Learn more about\n * event parameters with Blueprint.\n */\nexport type ObjectProperties = {\n content_category?: string;\n content_ids?: string[];\n content_name?: string;\n\n /**\n * Either product or product_group based on the content_ids or contents being passed. If the IDs\n * being passed in content_ids or contents parameter are IDs of products, then the value should be\n * product. If product group IDs are being passed, then the value should be product_group.\n *\n * If no content_type is provided, Meta will match the event to every item that has the same ID,\n * independent of its type.\n */\n content_type?: 'product' | 'product_group' | (string & {});\n contents?: Content[];\n delivery_category?: 'in_store' | 'curbside' | 'home_delivery';\n currency?: string;\n num_items?: number;\n predicted_ltv?: number;\n search_string?: string;\n\n /** Used with the CompleteRegistration event, to show the status of the registration. */\n status?: boolean;\n value?: number;\n};\n\n/**\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/payload-helper\n */\nexport type StandardEvents = {\n AddPaymentInfo: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n AddToCart: {\n content_ids?: string[];\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents\n currency?: string;\n value?: number;\n };\n AddToWishlist: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n value?: number;\n };\n CompleteRegistration: {\n currency?: string;\n value?: number;\n method?: string;\n };\n Contact: {};\n CustomizeProduct: {};\n Donate: {};\n FindLocation: {};\n InitiateCheckout: {\n content_ids?: string[];\n contents?: Content[];\n currency?: string;\n num_items?: number;\n value?: number;\n };\n Lead: {\n currency?: string;\n value?: number;\n };\n Purchase: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency: string; // required\n num_items?: number;\n value: number; // required\n };\n Schedule: {};\n Search: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n search_string?: string;\n value?: number;\n };\n StartTrial: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n SubmitApplication: {};\n Subscribe: {\n currency?: string;\n predicted_ltv?: number;\n value?: number;\n };\n ViewContent: {\n content_ids?: string[]; // Required for Advantage+ catalog ads: contents or content_ids\n content_type?: string;\n contents?: Content[]; // Required for Advantage+ catalog ads: contents or content_ids\n currency?: string;\n value?: number;\n };\n};\n\ntype JSONValue =\n | null\n | string\n | number\n | boolean\n | Array<JSONValue>\n | { [value: string]: JSONValue };\n\nexport type PixelId = `${number}`;\nexport type Options = { eventID?: string };\n\n/**\n * reference: https://developers.facebook.com/docs/meta-pixel/reference#standard-events\n *\n * We determine if events are identical based on their ID and name. So, for an event to be deduplicated:\n * - In corresponding events, a Meta Pixel's eventID must match the Conversion API's event_id.\n * - In corresponding events, a Meta Pixel's event must match the Conversion API's event_name.\n *\n * reference: https://developers.facebook.com/docs/marketing-api/conversions-api/parameters/customer-information-parameters\n */\nexport interface FBQ {\n /**\n * reference: https://stackoverflow.com/questions/62304291/sending-user-data-parameters-via-pixel\n *\n * Call init the normal default way first:\n * `fbq('init', 'XXXXX')`\n *\n * And at a later point in time, when you have obtained additional user data, you can call init\n * again basically enriching the already running fbq instance with additional data:\n * `fbq('init', 'XXXXX', { external_id: 1234, em: 'abc@abc.com' } )`\n *\n * Only caveat is that you have to send an event after this additional init call, otherwise the\n * provided data will not be sent to Facebook.\n */\n fbq(type: 'init', pixelId: PixelId, parameters?: MatchingParameters): void;\n\n /** Enable Manual Only mode. (value = false) */\n fbq(type: 'set', key: 'autoConfig', value: boolean, pixelId: PixelId): void;\n\n fbq<T extends keyof StandardEvents>(\n type: 'track',\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n fbq(\n type: 'trackCustom',\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq<T extends keyof StandardEvents>(\n type: 'trackSingle',\n pixelId: PixelId,\n event: T,\n properties?: StandardEvents[T] & ObjectProperties,\n options?: Options\n ): void;\n\n /** https://developers.facebook.com/docs/meta-pixel/guides/track-multiple-events/ */\n fbq(\n type: 'trackSingleCustom',\n pixelId: PixelId,\n event: string,\n properties?: Record<string, JSONValue> & ObjectProperties,\n options?: Options\n ): void;\n}\n\n/**\n * Please download this CSV filefor examples of properly normalized and hashed data for the\n * parameters below.\n */\nexport function normalize(parameters: MatchingParameters): MatchingParameters {\n return {\n ...parameters,\n em: parameters.em?.toLowerCase().trim(),\n ph: parameters.ph?.replace(/[-+()\\s]/g, '').replace(/^0+/, ''),\n zp: parameters.zp?.split('-').at(0)?.trim(),\n fn: parameters.fn?.toLowerCase().trim(),\n ln: parameters.ln?.toLowerCase().trim(),\n ct: parameters.ct?.toLowerCase().replace(/[\\s/-]/g, ''),\n st: parameters.st?.toLowerCase().replace(/[\\s/,.-]/g, ''),\n country: parameters.country?.toLowerCase().replace(/[\\s/-]/g, ''),\n };\n}\n\nexport function mapItems(items?: Item[]): ObjectProperties {\n if (!items) return {};\n const categories = Array.from(new Set(items.map((i) => i.item_category).filter(Boolean)));\n const contents: Content[] = items.map(\n ({ item_id, quantity, price, item_name, item_brand, item_category, ..._others }) => ({\n id: item_id,\n quantity: quantity ?? 1,\n item_price: price,\n title: item_name,\n brand: item_brand,\n category: item_category,\n })\n );\n\n return {\n content_category: categories.length === 1 ? categories.at(0) : undefined,\n contents,\n content_ids: contents.map((c) => c.id),\n num_items: items.reduce((acc, i) => acc + (i.quantity ?? 1), 0),\n };\n}\n\ntype Mapped<F extends keyof StandardEvents> = ['track', F, StandardEvents[F] & ObjectProperties];\ntype Missed<F extends string> = ['trackCustom', F, Record<string, JSONValue> & ObjectProperties];\n\nexport function mapFBEvent<T extends EventName>(\n name: TrackName<T>,\n properties?: TrackProperties<T>\n): Mapped<keyof StandardEvents> | Missed<TrackName<T>> {\n if (name === 'add_payment_info') {\n const p = properties as TrackProperties<'add_payment_info'> | undefined;\n return [\n 'track',\n 'AddPaymentInfo',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_cart') {\n const p = properties as TrackProperties<'add_to_cart'> | undefined;\n return [\n 'track',\n 'AddToCart',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'add_to_wishlist') {\n const p = properties as TrackProperties<'add_to_wishlist'> | undefined;\n return [\n 'track',\n 'AddToWishlist',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'login') {\n const p = properties as TrackProperties<'login'> | undefined;\n return ['track', 'CompleteRegistration', { method: p?.method }];\n } else if (name === 'contact') {\n return ['track', 'Contact', {}];\n } else if (name === 'customize_product') {\n return ['track', 'CustomizeProduct', {}];\n } else if (name === 'donate') {\n return ['track', 'Donate', {}];\n } else if (name === 'find_location') {\n return ['track', 'FindLocation', {}];\n } else if (name === 'begin_checkout') {\n const p = properties as TrackProperties<'begin_checkout'> | undefined;\n return [\n 'track',\n 'InitiateCheckout',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else if (name === 'generate_lead') {\n const p = properties as TrackProperties<'generate_lead'> | undefined;\n return ['track', 'Lead', { currency: p?.currency, value: p?.value }];\n } else if (name === 'purchase') {\n const p = properties as TrackProperties<'purchase'> | undefined;\n return [\n 'track',\n 'Purchase',\n { currency: p?.currency ?? 'USD', value: p?.value ?? 0, ...mapItems(p?.items) },\n ];\n } else if (name === 'schedule') {\n return ['track', 'Schedule', {}];\n } else if (name === 'search') {\n const p = properties as TrackProperties<'search'> | undefined;\n return ['track', 'Search', { search_string: p?.search_term }];\n } else if (name === 'trial_begin') {\n const p = properties as TrackProperties<'trial_begin'> | undefined;\n return ['track', 'StartTrial', { currency: p?.currency, value: p?.value }];\n } else if (name === 'submit_application') {\n return ['track', 'SubmitApplication', {}];\n } else if (name === 'subscribe') {\n const p = properties as TrackProperties<'subscribe'> | undefined;\n return ['track', 'Subscribe', { currency: p?.currency, value: p?.value }];\n } else if (name === 'view_item') {\n const p = properties as TrackProperties<'view_item'> | undefined;\n return [\n 'track',\n 'ViewContent',\n { currency: p?.currency, value: p?.value, ...mapItems(p?.items) },\n ];\n } else {\n return ['trackCustom', name, properties ?? {}];\n }\n}\n"],"mappings":"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"}
|
package/dist/track/fbq.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fbq.d.cts","names":[],"sources":["../../src/track/fbq.ts"],"mappings":";;;KAIY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU;;EAEV;;EAGA;;EAGA;;EAGA;;;;;EAMA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;;;;;;EAUA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;EAMA;;;;;;EAOA;;;;;;;KAQU;EACV;EACA;EACA;;;;;;;;;EAUA;EACA,WAAW;EACX;EACA;EACA;EACA;EACA;;EAGA;EACA;;;;;KAMU;EACV;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;EACA;EACA;EACA;IACE;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;;KAIC,+CAKD,MAAM;GACH,gBAAgB;;KAEX;KACA;EAAY;;;;;;;;;;;UAWP;;;;;;;;;;;;;;EAcf,IAAI,cAAc,SAAS,SAAS,aAAa;;EAGjD,IAAI,aAAa,mBAAmB,gBAAgB,SAAS;EAE7D,IAAI,gBAAgB,gBAClB,eACA,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;EAGZ,IACE,qBACA,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;EAIZ,IAAI,gBAAgB,gBAClB,qBACA,SAAS,SACT,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;;EAIZ,IACE,2BACA,SAAS,SACT,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;;;;;iBAQE,UAAU,YAAY,qBAAqB;
|
|
1
|
+
{"version":3,"file":"fbq.d.cts","names":[],"sources":["../../src/track/fbq.ts"],"mappings":";;;KAIY;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU;;EAEV;;EAGA;;EAGA;;EAGA;;;;;EAMA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;EAGA;;;;;;;;;EAUA;;;;;;;;EASA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;EAMA;;;;;;EAOA;;;;;;EAOA;;;;;EAMA;;;;;EAMA;;;;;;EAOA;;;;;;;KAQU;EACV;EACA;EACA;;;;;;;;;EAUA;EACA,WAAW;EACX;EACA;EACA;EACA;EACA;;EAGA;EACA;;;;;KAMU;EACV;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA,WAAW;IACX;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;EACA;EACA;EACA;IACE;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA,WAAW;IACX;IACA;IACA;;EAEF;IACE;IACA;IACA;;EAEF;EACA;IACE;IACA;IACA;;EAEF;IACE;IACA;IACA,WAAW;IACX;IACA;;;KAIC,+CAKD,MAAM;GACH,gBAAgB;;KAEX;KACA;EAAY;;;;;;;;;;;UAWP;;;;;;;;;;;;;;EAcf,IAAI,cAAc,SAAS,SAAS,aAAa;;EAGjD,IAAI,aAAa,mBAAmB,gBAAgB,SAAS;EAE7D,IAAI,gBAAgB,gBAClB,eACA,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;EAGZ,IACE,qBACA,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;EAIZ,IAAI,gBAAgB,gBAClB,qBACA,SAAS,SACT,OAAO,GACP,aAAa,eAAe,KAAK,kBACjC,UAAU;;EAIZ,IACE,2BACA,SAAS,SACT,eACA,aAAa,eAAe,aAAa,kBACzC,UAAU;;;;;;iBAQE,UAAU,YAAY,qBAAqB;iBAc3C,SAAS,QAAQ,SAAS;KAsBrC,OAAO,gBAAgB,4BAA4B,GAAG,eAAe,KAAK;KAC1E,OAAO,oCAAoC,GAAG,eAAe,aAAa;iBAE/D,WAAW,UAAU,WACnC,MAAM,UAAU,IAChB,aAAa,gBAAgB,KAC5B,aAAa,kBAAkB,OAAO,UAAU"}
|