@escape-game-over/atlas 0.1.64 → 0.1.66
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/package.json +1 -1
- package/src/analytics/google-tag.ts +24 -1
- package/src/analytics/google.ts +22 -0
- package/src/analytics/umami.ts +12 -3
- package/src/astro/clarity-event.ts +19 -1
- package/src/astro/client.ts +1 -0
- package/src/astro/drip-event.ts +24 -4
- package/src/astro/event-data.ts +15 -0
- package/src/astro/google-event.ts +119 -0
- package/src/astro/meta-event.ts +57 -26
- package/src/astro/tiktok-event.ts +33 -6
- package/src/astro/umami-event.ts +22 -3
package/package.json
CHANGED
|
@@ -14,8 +14,13 @@ type ConsentGlobal = {
|
|
|
14
14
|
) => void;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// `googleEvent`'s way in, under the same arrangement.
|
|
18
|
+
type EventGlobal = {
|
|
19
|
+
[Name in typeof import("./google.ts").GOOGLE_EVENT_GLOBAL]?: import("./google.ts").GoogleEventSink;
|
|
20
|
+
};
|
|
21
|
+
|
|
17
22
|
// biome-ignore lint/correctness/noUnusedVariables: merges into the global `Window`.
|
|
18
|
-
interface Window extends ConsentGlobal {
|
|
23
|
+
interface Window extends ConsentGlobal, EventGlobal {
|
|
19
24
|
dataLayer: unknown[];
|
|
20
25
|
gtag: (...args: unknown[]) => void;
|
|
21
26
|
}
|
|
@@ -86,6 +91,24 @@ interface Window extends ConsentGlobal {
|
|
|
86
91
|
|
|
87
92
|
defineGtag();
|
|
88
93
|
|
|
94
|
+
// An event, wherever this deployment's settings send one. Tag Manager
|
|
95
|
+
// reads a pushed object rather than a `gtag` call, and clears the previous
|
|
96
|
+
// `ecommerce` only when told to, as Google documents.
|
|
97
|
+
window.__googleEvent = (name, params, ecommerce) => {
|
|
98
|
+
if (config.tagIds.length > 0) {
|
|
99
|
+
if (params === undefined) window.gtag("event", name);
|
|
100
|
+
else window.gtag("event", name, params);
|
|
101
|
+
}
|
|
102
|
+
if (config.containerIds.length > 0) {
|
|
103
|
+
if (ecommerce) {
|
|
104
|
+
window.dataLayer.push({ ecommerce: null });
|
|
105
|
+
window.dataLayer.push({ event: name, ecommerce: params });
|
|
106
|
+
} else {
|
|
107
|
+
window.dataLayer.push({ event: name, ...params });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
89
112
|
// Ahead of everything, which is the entire point of emitting this here.
|
|
90
113
|
for (const given of config.defaults) {
|
|
91
114
|
window.gtag("consent", "default", given);
|
package/src/analytics/google.ts
CHANGED
|
@@ -217,6 +217,28 @@ const CONSENT_KEYS = {
|
|
|
217
217
|
*/
|
|
218
218
|
export const CONSENT_UPDATE_GLOBAL = "__consent";
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* The global `googleEvent` calls to record an event:
|
|
222
|
+
* `__googleEvent("generate_lead", { value: 40 }, true)`.
|
|
223
|
+
*
|
|
224
|
+
* A global for the reason `CONSENT_UPDATE_GLOBAL` is one, and emitted here
|
|
225
|
+
* because only this side knows where an event goes: to `gtag.js` when there
|
|
226
|
+
* are tag ids, onto `dataLayer` as a Tag Manager event when there are
|
|
227
|
+
* containers. A client script deciding that would be a second copy of the
|
|
228
|
+
* settings.
|
|
229
|
+
*/
|
|
230
|
+
export const GOOGLE_EVENT_GLOBAL = "__googleEvent";
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* What `GOOGLE_EVENT_GLOBAL` holds. `ecommerce` says the parameters go under
|
|
234
|
+
* Tag Manager's `ecommerce` key, where its GA4 tags read items and revenue.
|
|
235
|
+
*/
|
|
236
|
+
export type GoogleEventSink = (
|
|
237
|
+
name: string,
|
|
238
|
+
params: object | undefined,
|
|
239
|
+
ecommerce: boolean
|
|
240
|
+
) => void;
|
|
241
|
+
|
|
220
242
|
/** The signal keys of a set of defaults — not `region`, not `waitForUpdate`. */
|
|
221
243
|
const SIGNAL_KEYS = [
|
|
222
244
|
"adStorage",
|
package/src/analytics/umami.ts
CHANGED
|
@@ -36,7 +36,16 @@ export interface UmamiTracker {
|
|
|
36
36
|
readonly performance?: boolean;
|
|
37
37
|
/** Leave `?query` out of recorded URLs. */
|
|
38
38
|
readonly excludeSearch?: boolean;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Leave `#hash` out of recorded URLs. **Defaults to on here.**
|
|
41
|
+
*
|
|
42
|
+
* Umami's own default is off, and the tracker counts a pageview whenever
|
|
43
|
+
* the URL it compares changes. So anything on the page that rewrites the
|
|
44
|
+
* fragment (a gallery, a tab, an anchor link) is recorded as another visit
|
|
45
|
+
* to a page nobody left.
|
|
46
|
+
*
|
|
47
|
+
* `false` turns it off, and writes the word rather than omitting it.
|
|
48
|
+
*/
|
|
40
49
|
readonly excludeHash?: boolean;
|
|
41
50
|
/** Honour the browser's Do Not Track setting. */
|
|
42
51
|
readonly doNotTrack?: boolean;
|
|
@@ -267,15 +276,15 @@ export function umamiScripts(
|
|
|
267
276
|
src: at(umami.tracker?.path ?? "/script.js"),
|
|
268
277
|
attrs: stated({
|
|
269
278
|
...common,
|
|
270
|
-
// The
|
|
279
|
+
// The two defaults lib overrides — see `UmamiTracker`. The
|
|
271
280
|
// others are left to Umami: `auto-track` and `auto-pageview`
|
|
272
281
|
// are already on unless the word `false` appears, so stating
|
|
273
282
|
// them would be bytes that change nothing, and whether to
|
|
274
283
|
// honour Do Not Track or drop query strings is a decision about
|
|
275
284
|
// this business rather than about this kind of site.
|
|
276
285
|
"data-performance": flag(umami.tracker?.performance ?? true),
|
|
286
|
+
"data-exclude-hash": flag(umami.tracker?.excludeHash ?? true),
|
|
277
287
|
"data-exclude-search": flag(umami.tracker?.excludeSearch),
|
|
278
|
-
"data-exclude-hash": flag(umami.tracker?.excludeHash),
|
|
279
288
|
"data-do-not-track": flag(umami.tracker?.doNotTrack),
|
|
280
289
|
"data-auto-track": flag(umami.tracker?.autoTrack),
|
|
281
290
|
"data-auto-pageview": flag(umami.tracker?.autoPageview),
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import type { DeclaredEvent } from "./event-data.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The project's Clarity events. Clarity carries a name and nothing else, so
|
|
5
|
+
* each is declared `undefined`. Nothing compiles until a project declares its
|
|
6
|
+
* own, once:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* declare module "@escape-game-over/atlas/client" {
|
|
10
|
+
* interface ClarityEvents {
|
|
11
|
+
* "form sent": undefined;
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
// biome-ignore lint/suspicious/noEmptyInterface: filled by each project's declaration.
|
|
17
|
+
export interface ClarityEvents {}
|
|
18
|
+
|
|
1
19
|
/** What `clarity.ts` defines once the page has loaded. */
|
|
2
20
|
interface ClarityWindow {
|
|
3
21
|
clarity?: (...args: unknown[]) => void;
|
|
@@ -10,6 +28,6 @@ interface ClarityWindow {
|
|
|
10
28
|
* Sent with or without consent: Clarity records either way, and without it
|
|
11
29
|
* sets no cookie. Dropped before Clarity loads, or where it is not configured.
|
|
12
30
|
*/
|
|
13
|
-
export function clarityEvent(name:
|
|
31
|
+
export function clarityEvent(name: DeclaredEvent<ClarityEvents>): void {
|
|
14
32
|
(window as unknown as ClarityWindow).clarity?.("event", name);
|
|
15
33
|
}
|
package/src/astro/client.ts
CHANGED
|
@@ -26,6 +26,7 @@ export {
|
|
|
26
26
|
} from "./element.ts";
|
|
27
27
|
export * from "./filters.ts";
|
|
28
28
|
export * from "./filters-view.ts";
|
|
29
|
+
export * from "./google-event.ts";
|
|
29
30
|
export * from "./meta-event.ts";
|
|
30
31
|
export { data, ref, refs } from "./ref.ts";
|
|
31
32
|
export * from "./snap-event.ts";
|
package/src/astro/drip-event.ts
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
import { readConsent } from "./consent.ts";
|
|
2
|
+
import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The project's Drip events — the actions its workflows start on — each with
|
|
6
|
+
* the properties it carries, `undefined` for none. Drip's events are named by
|
|
7
|
+
* the site, so nothing compiles until a project declares its own, once:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* declare module "@escape-game-over/atlas/client" {
|
|
11
|
+
* interface DripEvents {
|
|
12
|
+
* "Bought a voucher": { readonly value: number };
|
|
13
|
+
* }
|
|
14
|
+
* }
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
// biome-ignore lint/suspicious/noEmptyInterface: filled by each project's declaration.
|
|
18
|
+
export interface DripEvents {}
|
|
2
19
|
|
|
3
20
|
/** What `drip.ts` defines once marketing is granted. */
|
|
4
21
|
interface DripWindow {
|
|
@@ -7,15 +24,18 @@ interface DripWindow {
|
|
|
7
24
|
|
|
8
25
|
/**
|
|
9
26
|
* Records an event with Drip, to start a workflow — a lead sent, a voucher
|
|
10
|
-
* bought.
|
|
27
|
+
* bought.
|
|
11
28
|
*
|
|
12
29
|
* Dropped unless Drip loaded and marketing is still granted: Drip has no
|
|
13
30
|
* consent call of its own, so the check is here. An analytics call must never
|
|
14
31
|
* be the reason a page misbehaves, so a dropped event is silent.
|
|
15
32
|
*/
|
|
16
|
-
export function dripEvent(
|
|
17
|
-
action:
|
|
18
|
-
|
|
33
|
+
export function dripEvent<E extends DeclaredEvent<DripEvents>>(
|
|
34
|
+
action: E,
|
|
35
|
+
// Drip takes flat values; a nested object in a declaration fails here.
|
|
36
|
+
...[properties]: EventDataArgs<
|
|
37
|
+
DripEvents[E] & Readonly<Record<string, string | number | boolean>>
|
|
38
|
+
>
|
|
19
39
|
): void {
|
|
20
40
|
if (readConsent()?.marketing !== "granted") return;
|
|
21
41
|
const queue = (window as unknown as DripWindow)._dcq;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The arguments an event's declared data asks for: none when it is
|
|
3
|
+
* `undefined`, optional when every field is, required otherwise.
|
|
4
|
+
*
|
|
5
|
+
* Shared by every vendor whose events a project declares for itself, so
|
|
6
|
+
* `{ form_sent: undefined }` means the same thing in each.
|
|
7
|
+
*/
|
|
8
|
+
export type EventDataArgs<Data> = [Data] extends [undefined]
|
|
9
|
+
? []
|
|
10
|
+
: Partial<Data> extends Data
|
|
11
|
+
? [data?: Data]
|
|
12
|
+
: [data: Data];
|
|
13
|
+
|
|
14
|
+
/** The event names a project declared, as the strings they are sent as. */
|
|
15
|
+
export type DeclaredEvent<Declared> = Extract<keyof Declared, string>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GOOGLE_EVENT_GLOBAL,
|
|
3
|
+
type GoogleEventSink,
|
|
4
|
+
} from "../analytics/google.ts";
|
|
5
|
+
import type { CurrencyCode } from "../money.ts";
|
|
6
|
+
import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
|
|
7
|
+
|
|
8
|
+
/** GA4's recommended events that carry items and revenue. */
|
|
9
|
+
const ECOMMERCE_EVENTS = [
|
|
10
|
+
"add_payment_info",
|
|
11
|
+
"add_to_cart",
|
|
12
|
+
"add_to_wishlist",
|
|
13
|
+
"begin_checkout",
|
|
14
|
+
"purchase",
|
|
15
|
+
"select_item",
|
|
16
|
+
"select_promotion",
|
|
17
|
+
"view_cart",
|
|
18
|
+
"view_item",
|
|
19
|
+
"view_item_list",
|
|
20
|
+
"view_promotion",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
/** GA4's recommended events that a website sends, which its reports know by name. */
|
|
24
|
+
export type GoogleRecommendedEvent =
|
|
25
|
+
| (typeof ECOMMERCE_EVENTS)[number]
|
|
26
|
+
| "generate_lead"
|
|
27
|
+
| "login"
|
|
28
|
+
| "search"
|
|
29
|
+
| "select_content"
|
|
30
|
+
| "share"
|
|
31
|
+
| "sign_up";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The project's own events — what its Tag Manager triggers listen for — each
|
|
35
|
+
* with the data it carries, `undefined` for none. Empty here; a project fills
|
|
36
|
+
* it once, and a name it did not declare does not compile:
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* declare module "@escape-game-over/atlas/client" {
|
|
40
|
+
* interface GoogleCustomEvents {
|
|
41
|
+
* form_submission_contact: undefined;
|
|
42
|
+
* form_submission_quote: { readonly room: string };
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
// biome-ignore lint/suspicious/noEmptyInterface: filled by each project's declaration.
|
|
48
|
+
export interface GoogleCustomEvents {}
|
|
49
|
+
|
|
50
|
+
/** A recommended event, or one the project declared. */
|
|
51
|
+
export type GoogleEventName =
|
|
52
|
+
| GoogleRecommendedEvent
|
|
53
|
+
| DeclaredEvent<GoogleCustomEvents>;
|
|
54
|
+
|
|
55
|
+
/** One item of an ecommerce event. Google needs its id or its name. */
|
|
56
|
+
export type GoogleItem = (
|
|
57
|
+
| { readonly item_id: string; readonly item_name?: string }
|
|
58
|
+
| { readonly item_id?: string; readonly item_name: string }
|
|
59
|
+
) & {
|
|
60
|
+
readonly item_category?: string;
|
|
61
|
+
readonly item_variant?: string;
|
|
62
|
+
readonly price?: number;
|
|
63
|
+
readonly quantity?: number;
|
|
64
|
+
readonly coupon?: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/** GA4's standard parameters. What the visitor chose, never who they are. */
|
|
68
|
+
export interface GoogleEventData {
|
|
69
|
+
readonly value?: number;
|
|
70
|
+
readonly currency?: CurrencyCode;
|
|
71
|
+
readonly transaction_id?: string;
|
|
72
|
+
readonly coupon?: string;
|
|
73
|
+
readonly items?: readonly GoogleItem[];
|
|
74
|
+
readonly search_term?: string;
|
|
75
|
+
readonly method?: string;
|
|
76
|
+
readonly content_type?: string;
|
|
77
|
+
readonly content_id?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A recommended event takes GA4's parameters; `purchase` without its three
|
|
82
|
+
* reports a sale of nothing, possibly twice. A declared one takes what the
|
|
83
|
+
* project declared.
|
|
84
|
+
*/
|
|
85
|
+
type GoogleEventArgs<E extends GoogleEventName> =
|
|
86
|
+
E extends GoogleRecommendedEvent
|
|
87
|
+
? E extends "purchase"
|
|
88
|
+
? [
|
|
89
|
+
data: GoogleEventData & {
|
|
90
|
+
value: number;
|
|
91
|
+
currency: CurrencyCode;
|
|
92
|
+
transaction_id: string;
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
: [data?: GoogleEventData]
|
|
96
|
+
: EventDataArgs<GoogleCustomEvents[E & keyof GoogleCustomEvents]>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Records an event with Google — a lead sent, a booking made — through
|
|
100
|
+
* `gtag.js`, Tag Manager, or both, as the deployment's `google` settings say.
|
|
101
|
+
*
|
|
102
|
+
* Sent whatever the visitor answered: Consent Mode is how Google is told, and
|
|
103
|
+
* it already holds back what was not granted. Dropped where Google is not
|
|
104
|
+
* configured, since an analytics call must never be the reason a page
|
|
105
|
+
* misbehaves.
|
|
106
|
+
*/
|
|
107
|
+
export function googleEvent<E extends GoogleEventName>(
|
|
108
|
+
event: E,
|
|
109
|
+
...[data]: GoogleEventArgs<E>
|
|
110
|
+
): void {
|
|
111
|
+
const send = (
|
|
112
|
+
window as unknown as Record<string, GoogleEventSink | undefined>
|
|
113
|
+
)[GOOGLE_EVENT_GLOBAL];
|
|
114
|
+
send?.(
|
|
115
|
+
event,
|
|
116
|
+
data,
|
|
117
|
+
(ECOMMERCE_EVENTS as readonly string[]).includes(event)
|
|
118
|
+
);
|
|
119
|
+
}
|
package/src/astro/meta-event.ts
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
1
|
import type { CurrencyCode } from "../money.ts";
|
|
2
|
+
import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
|
|
2
3
|
|
|
3
4
|
/** Meta's standard events, which its ad optimisation understands by name. */
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
5
|
+
const META_STANDARD_EVENTS = [
|
|
6
|
+
"AddPaymentInfo",
|
|
7
|
+
"AddToCart",
|
|
8
|
+
"AddToWishlist",
|
|
9
|
+
"CompleteRegistration",
|
|
10
|
+
"Contact",
|
|
11
|
+
"CustomizeProduct",
|
|
12
|
+
"Donate",
|
|
13
|
+
"FindLocation",
|
|
14
|
+
"InitiateCheckout",
|
|
15
|
+
"Lead",
|
|
16
|
+
"Purchase",
|
|
17
|
+
"Schedule",
|
|
18
|
+
"Search",
|
|
19
|
+
"StartTrial",
|
|
20
|
+
"SubmitApplication",
|
|
21
|
+
"Subscribe",
|
|
22
|
+
"ViewContent",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
25
|
+
export type MetaStandardEvent = (typeof META_STANDARD_EVENTS)[number];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The project's own events, sent as Meta's custom events, each with the data
|
|
29
|
+
* it carries, `undefined` for none. Empty here; a project fills it once:
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* declare module "@escape-game-over/atlas/client" {
|
|
33
|
+
* interface MetaCustomEvents {
|
|
34
|
+
* VoucherViewed: undefined;
|
|
35
|
+
* }
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
// biome-ignore lint/suspicious/noEmptyInterface: filled by each project's declaration.
|
|
40
|
+
export interface MetaCustomEvents {}
|
|
41
|
+
|
|
42
|
+
/** A standard event, or one the project declared. */
|
|
43
|
+
export type MetaEventName = MetaStandardEvent | DeclaredEvent<MetaCustomEvents>;
|
|
22
44
|
|
|
23
45
|
/** Meta's standard parameters. What the visitor chose, never who they are. */
|
|
24
46
|
export interface MetaEventData {
|
|
@@ -33,10 +55,15 @@ export interface MetaEventData {
|
|
|
33
55
|
readonly status?: boolean;
|
|
34
56
|
}
|
|
35
57
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
/**
|
|
59
|
+
* A standard event takes Meta's parameters; `Purchase` is the one it rejects
|
|
60
|
+
* without a value and currency. A declared one takes what the project declared.
|
|
61
|
+
*/
|
|
62
|
+
type MetaEventArgs<E extends MetaEventName> = E extends MetaStandardEvent
|
|
63
|
+
? E extends "Purchase"
|
|
64
|
+
? [data: MetaEventData & { value: number; currency: CurrencyCode }]
|
|
65
|
+
: [data?: MetaEventData]
|
|
66
|
+
: EventDataArgs<MetaCustomEvents[E & keyof MetaCustomEvents]>;
|
|
40
67
|
|
|
41
68
|
/** What `meta-pixel.ts` defines once marketing is granted. */
|
|
42
69
|
interface MetaWindow {
|
|
@@ -44,18 +71,22 @@ interface MetaWindow {
|
|
|
44
71
|
}
|
|
45
72
|
|
|
46
73
|
/**
|
|
47
|
-
* Records
|
|
74
|
+
* Records an event with Meta's pixel — a lead sent, a booking made. A declared
|
|
75
|
+
* event is sent as Meta's custom event, which is how Meta tells them apart.
|
|
48
76
|
*
|
|
49
77
|
* `fbq` exists only once the visitor granted marketing and the pixel loaded.
|
|
50
78
|
* Before that, without consent, or where Meta is not configured, the event is
|
|
51
79
|
* dropped: no consent means nothing to send, and an analytics call must never
|
|
52
80
|
* be the reason a page misbehaves.
|
|
53
81
|
*/
|
|
54
|
-
export function metaEvent<E extends
|
|
82
|
+
export function metaEvent<E extends MetaEventName>(
|
|
55
83
|
event: E,
|
|
56
84
|
...[data]: MetaEventArgs<E>
|
|
57
85
|
): void {
|
|
58
86
|
const fbq = (window as unknown as MetaWindow).fbq;
|
|
59
|
-
|
|
60
|
-
|
|
87
|
+
const method = (META_STANDARD_EVENTS as readonly string[]).includes(event)
|
|
88
|
+
? "track"
|
|
89
|
+
: "trackCustom";
|
|
90
|
+
if (data === undefined) fbq?.(method, event);
|
|
91
|
+
else fbq?.(method, event, data);
|
|
61
92
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CurrencyCode } from "../money.ts";
|
|
2
|
+
import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
|
|
2
3
|
|
|
3
4
|
/** TikTok's standard events, by their current names (May 2025 onwards). */
|
|
4
5
|
export type TikTokStandardEvent =
|
|
@@ -18,6 +19,26 @@ export type TikTokStandardEvent =
|
|
|
18
19
|
| "Subscribe"
|
|
19
20
|
| "ViewContent";
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* The project's own events, each with the data it carries, `undefined` for
|
|
24
|
+
* none. Empty here; a project fills it once:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* declare module "@escape-game-over/atlas/client" {
|
|
28
|
+
* interface TikTokCustomEvents {
|
|
29
|
+
* VoucherViewed: undefined;
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
// biome-ignore lint/suspicious/noEmptyInterface: filled by each project's declaration.
|
|
35
|
+
export interface TikTokCustomEvents {}
|
|
36
|
+
|
|
37
|
+
/** A standard event, or one the project declared. */
|
|
38
|
+
export type TikTokEventName =
|
|
39
|
+
| TikTokStandardEvent
|
|
40
|
+
| DeclaredEvent<TikTokCustomEvents>;
|
|
41
|
+
|
|
21
42
|
/** TikTok's standard parameters. What the visitor chose, never who they are. */
|
|
22
43
|
export interface TikTokEventData {
|
|
23
44
|
readonly value?: number;
|
|
@@ -31,10 +52,15 @@ export interface TikTokEventData {
|
|
|
31
52
|
readonly description?: string;
|
|
32
53
|
}
|
|
33
54
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
/**
|
|
56
|
+
* A standard event takes TikTok's parameters; `Purchase` needs a value and
|
|
57
|
+
* currency to be worth anything. A declared one takes what the project declared.
|
|
58
|
+
*/
|
|
59
|
+
type TikTokEventArgs<E extends TikTokEventName> = E extends TikTokStandardEvent
|
|
60
|
+
? E extends "Purchase"
|
|
61
|
+
? [data: TikTokEventData & { value: number; currency: CurrencyCode }]
|
|
62
|
+
: [data?: TikTokEventData]
|
|
63
|
+
: EventDataArgs<TikTokCustomEvents[E & keyof TikTokCustomEvents]>;
|
|
38
64
|
|
|
39
65
|
/** What `tiktok-pixel.ts` defines once marketing is granted. */
|
|
40
66
|
interface TikTokWindow {
|
|
@@ -42,14 +68,15 @@ interface TikTokWindow {
|
|
|
42
68
|
}
|
|
43
69
|
|
|
44
70
|
/**
|
|
45
|
-
* Records
|
|
71
|
+
* Records an event with TikTok's pixel — a lead sent, a booking made. Declared
|
|
72
|
+
* events go through the same call.
|
|
46
73
|
*
|
|
47
74
|
* `ttq` exists only once the visitor granted marketing and the pixel loaded.
|
|
48
75
|
* Before that, without consent, or where TikTok is not configured, the event is
|
|
49
76
|
* dropped: no consent means nothing to send, and an analytics call must never
|
|
50
77
|
* be the reason a page misbehaves.
|
|
51
78
|
*/
|
|
52
|
-
export function tiktokEvent<E extends
|
|
79
|
+
export function tiktokEvent<E extends TikTokEventName>(
|
|
53
80
|
event: E,
|
|
54
81
|
...[data]: TikTokEventArgs<E>
|
|
55
82
|
): void {
|
package/src/astro/umami-event.ts
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The project's Umami events, each with the data it carries, `undefined` for
|
|
5
|
+
* none. Umami has no standard events, so nothing compiles until a project
|
|
6
|
+
* declares its own, once:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* declare module "@escape-game-over/atlas/client" {
|
|
10
|
+
* interface UmamiEvents {
|
|
11
|
+
* "franchise-enquiry": { readonly country: string };
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
// biome-ignore lint/suspicious/noEmptyInterface: filled by each project's declaration.
|
|
17
|
+
export interface UmamiEvents {}
|
|
18
|
+
|
|
1
19
|
/**
|
|
2
20
|
* Records a named event with Umami — a form sent, a deck downloaded — from a
|
|
3
21
|
* client script.
|
|
@@ -11,9 +29,10 @@
|
|
|
11
29
|
* never who they are. For an event on a plain click, Umami's
|
|
12
30
|
* `data-umami-event` attribute needs no script at all.
|
|
13
31
|
*/
|
|
14
|
-
export function umamiEvent(
|
|
15
|
-
name:
|
|
16
|
-
|
|
32
|
+
export function umamiEvent<E extends DeclaredEvent<UmamiEvents>>(
|
|
33
|
+
name: E,
|
|
34
|
+
// Flat string values; anything else in a declaration fails here.
|
|
35
|
+
...[data]: EventDataArgs<UmamiEvents[E] & Readonly<Record<string, string>>>
|
|
17
36
|
): void {
|
|
18
37
|
const umami = (
|
|
19
38
|
window as unknown as {
|