@escape-game-over/atlas 0.1.116 → 0.1.117

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 CHANGED
@@ -398,7 +398,7 @@ src/ the package — see the export map above for what is reachable
398
398
  meta/ head tags: canonical, hreflang, Open Graph, Twitter, robots
399
399
  jsonld/ the @graph — one file per node, each linked to Google's docs
400
400
  i18n/ defineMessages(), t(), placeholder extraction
401
- routes/ defineRoutes(), routeFamily(), merging and collisions
401
+ routes/ defineRoutes(), route families, merging and collisions
402
402
  analytics/ Umami and Google, the Consent Mode defaults, and whether a
403
403
  visitor has to be asked anything at all
404
404
  astro/ the only framework-aware code, plus the one component
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.116",
3
+ "version": "0.1.117",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
@@ -67,7 +67,7 @@
67
67
  "@biomejs/biome": "2.6.7",
68
68
  "@types/node": "26.6.6",
69
69
  "@vitest/coverage-istanbul": "5.1.0",
70
- "astro": "7.4.11",
70
+ "astro": "7.4.12",
71
71
  "typescript": "6.0.3",
72
72
  "vitest": "5.1.0"
73
73
  }
@@ -24,18 +24,6 @@ export function invalidId(at: string, message: string): never {
24
24
  throw new Error(`${at}: ${message}`);
25
25
  }
26
26
 
27
- /** A Meta pixel id: 15 or 16 digits. */
28
- export const META_PIXEL_ID = /^\d{15,16}$/;
29
-
30
- /** A TikTok pixel id: 20 capital letters and digits. */
31
- export const TIKTOK_PIXEL_ID = /^[A-Z0-9]{20}$/;
32
-
33
- /** A Drip account id: digits. */
34
- export const DRIP_ACCOUNT_ID = /^\d+$/;
35
-
36
- /** A Clarity project id: 10 lowercase letters and digits. */
37
- export const CLARITY_PROJECT_ID = /^[a-z0-9]{10}$/;
38
-
39
- /** A UUID, as Umami, Snap and Axon issue them. */
27
+ /** A UUID, as Umami, Snap, Axon and the booking vendors issue them. */
40
28
  export const UUID =
41
29
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -1,18 +1,21 @@
1
1
  import type { MetaTag } from "../meta/tag.ts";
2
2
  import type { HttpsUrl } from "../url.ts";
3
- import { type AxonPixelSettings, checkAxonPixel } from "./axon-pixel.ts";
4
- import { type ClaritySettings, checkClarity } from "./clarity.ts";
5
- import { checkDrip, type DripSettings } from "./drip.ts";
6
3
  import { type GoogleSettings, googleEmits, googleScripts } from "./google.ts";
7
- import { checkMetaPixel, type MetaPixelSettings } from "./meta-pixel.ts";
8
- import { checkSnapPixel, type SnapPixelSettings } from "./snap-pixel.ts";
9
4
  import type { AnalyticsTags } from "./tags.ts";
10
- import { checkTikTokPixel, type TikTokPixelSettings } from "./tiktok-pixel.ts";
11
5
  import {
12
6
  checkUmamiDomains,
13
7
  type UmamiSettings,
14
8
  umamiScripts,
15
9
  } from "./umami.ts";
10
+ import {
11
+ type AxonPixelSettings,
12
+ type ClaritySettings,
13
+ checkVendorIds,
14
+ type DripSettings,
15
+ type MetaPixelSettings,
16
+ type SnapPixelSettings,
17
+ type TikTokPixelSettings,
18
+ } from "./vendor-settings.ts";
16
19
 
17
20
  /**
18
21
  * Analytics, as head tags.
@@ -29,9 +32,6 @@ import {
29
32
  * project states ids; lib decides everything that follows from them.
30
33
  */
31
34
 
32
- export type { AxonPixelSettings } from "./axon-pixel.ts";
33
- export type { ClaritySettings } from "./clarity.ts";
34
- export type { DripSettings } from "./drip.ts";
35
35
  export {
36
36
  CONSENT_UPDATE_GLOBAL,
37
37
  type ConsentDefaults,
@@ -39,15 +39,20 @@ export {
39
39
  type GoogleSettings,
40
40
  } from "./google.ts";
41
41
  export type { GoogleTagId, NumericId, TagManagerId, Uuid } from "./ids.ts";
42
- export type { MetaPixelSettings } from "./meta-pixel.ts";
43
- export type { SnapPixelSettings } from "./snap-pixel.ts";
44
42
  export type { AnalyticsTags } from "./tags.ts";
45
- export type { TikTokPixelSettings } from "./tiktok-pixel.ts";
46
43
  export type {
47
44
  UmamiReplay,
48
45
  UmamiSettings,
49
46
  UmamiTracker,
50
47
  } from "./umami.ts";
48
+ export type {
49
+ AxonPixelSettings,
50
+ ClaritySettings,
51
+ DripSettings,
52
+ MetaPixelSettings,
53
+ SnapPixelSettings,
54
+ TikTokPixelSettings,
55
+ } from "./vendor-settings.ts";
51
56
 
52
57
  /** What a project may switch on. A vendor is a key here. */
53
58
  export interface AnalyticsSettings {
@@ -85,12 +90,7 @@ export function analyticsScripts(
85
90
  if (analytics === undefined) return { head: [], body: [] };
86
91
 
87
92
  checkUmamiDomains(analytics.umami, origin);
88
- checkMetaPixel(analytics.meta, origin);
89
- checkTikTokPixel(analytics.tiktok, origin);
90
- checkSnapPixel(analytics.snapchat, origin);
91
- checkAxonPixel(analytics.axon, origin);
92
- checkDrip(analytics.drip, origin);
93
- checkClarity(analytics.clarity, origin);
93
+ checkVendorIds(analytics, origin);
94
94
  const google = googleScripts(analytics.google, origin);
95
95
  return {
96
96
  head: [...google.head, ...umamiScripts(analytics.umami)],
@@ -0,0 +1,162 @@
1
+ import type { NonEmpty } from "../types.ts";
2
+ import { invalidId, type NumericId, UUID, type Uuid } from "./ids.ts";
3
+
4
+ /**
5
+ * Meta's pixel. Marketing: it loads only once a visitor grants that.
6
+ *
7
+ * @see https://developers.facebook.com/docs/meta-pixel/get-started
8
+ * @see https://developers.facebook.com/docs/meta-pixel/reference
9
+ * @see https://developers.facebook.com/docs/meta-pixel/implementation/gdpr
10
+ */
11
+ export interface MetaPixelSettings {
12
+ /** From Events Manager: 15 or 16 digits. Several share one script. */
13
+ readonly pixelIds: NonEmpty<NumericId>;
14
+ }
15
+
16
+ /**
17
+ * TikTok's pixel. Marketing: it loads only once a visitor grants that.
18
+ *
19
+ * @see https://ads.tiktok.com/help/article/get-started-pixel
20
+ * @see https://ads.tiktok.com/help/article/standard-events-parameters
21
+ * @see https://ads.tiktok.com/help/article/how-to-use-cookies-with-tiktok-pixel
22
+ */
23
+ export interface TikTokPixelSettings {
24
+ /** From TikTok Events Manager: 20 capital letters and digits. */
25
+ readonly pixelIds: NonEmpty<string>;
26
+ }
27
+
28
+ /**
29
+ * Snapchat's pixel. Marketing: it loads only once a visitor grants that.
30
+ *
31
+ * @see https://businesshelp.snapchat.com/s/article/pixel-direct-implementation
32
+ * @see https://forbusiness.snapchat.com/en-US/blog/the-snap-pixel-how-it-works-and-how-to-install-it
33
+ */
34
+ export interface SnapPixelSettings {
35
+ /** From Snap's Events Manager: a UUID. */
36
+ readonly pixelIds: NonEmpty<Uuid>;
37
+ }
38
+
39
+ /**
40
+ * AppLovin's Axon pixel. Marketing: it loads only once a visitor grants that.
41
+ *
42
+ * @see https://support.applovin.com/en/growth/promoting-your-websites/axon-pixel-integration/axon-pixel-native-js
43
+ * @see https://support.applovin.com/en/growth/promoting-your-websites/axon-pixel-integration/events-and-objects
44
+ */
45
+ export interface AxonPixelSettings {
46
+ /** From the Axon dashboard: a UUID. */
47
+ readonly eventKey: Uuid;
48
+ }
49
+
50
+ /**
51
+ * Drip's site tracking. Marketing: it loads only once a visitor grants that.
52
+ *
53
+ * @see https://help.drip.com/hc/en-us/articles/4424702539789-Install-Your-JavaScript-Snippet
54
+ */
55
+ export interface DripSettings {
56
+ /** From Drip's Site Setup: digits, the `_dcs.account` of its snippet. */
57
+ readonly accountId: NumericId;
58
+ }
59
+
60
+ /**
61
+ * Microsoft Clarity. It has a cookieless mode, so like Google it loads for
62
+ * every visitor: without consent it records each page view on its own and sets
63
+ * no cookie, and the visitor's answer switches its cookies on or off.
64
+ *
65
+ * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-setup
66
+ * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/consent-mode
67
+ * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-consent-api-v2
68
+ */
69
+ export interface ClaritySettings {
70
+ /** From the Clarity project's settings: 10 lowercase letters and digits. */
71
+ readonly projectId: string;
72
+ }
73
+
74
+ /** What these vendors' checks read: their slice of `AnalyticsSettings`. */
75
+ interface VendorSettings {
76
+ readonly meta?: MetaPixelSettings;
77
+ readonly tiktok?: TikTokPixelSettings;
78
+ readonly snapchat?: SnapPixelSettings;
79
+ readonly axon?: AxonPixelSettings;
80
+ readonly drip?: DripSettings;
81
+ readonly clarity?: ClaritySettings;
82
+ }
83
+
84
+ /** One vendor's id: where it is set, its shape, and where to copy it from. */
85
+ interface IdCheck {
86
+ readonly ids: (settings: VendorSettings) => readonly string[];
87
+ /** What the id is called, as the vendor calls it. */
88
+ readonly name: string;
89
+ readonly shape: RegExp;
90
+ /** The shape in words, for the error. */
91
+ readonly is: string;
92
+ readonly from: string;
93
+ }
94
+
95
+ /**
96
+ * Each vendor's id check: one row each, read in one loop. Only the shape, the
97
+ * words and the place to copy it from differ between vendors.
98
+ */
99
+ const ID_CHECKS: readonly IdCheck[] = [
100
+ {
101
+ ids: (settings) => settings.meta?.pixelIds ?? [],
102
+ name: "Meta pixel id",
103
+ shape: /^\d{15,16}$/,
104
+ is: "15 or 16 digits",
105
+ from: "Events Manager",
106
+ },
107
+ {
108
+ ids: (settings) => settings.tiktok?.pixelIds ?? [],
109
+ name: "TikTok pixel id",
110
+ shape: /^[A-Z0-9]{20}$/,
111
+ is: "20 capital letters and digits",
112
+ from: "TikTok Events Manager",
113
+ },
114
+ {
115
+ ids: (settings) => settings.snapchat?.pixelIds ?? [],
116
+ name: "Snap pixel id",
117
+ shape: UUID,
118
+ is: "a UUID",
119
+ from: "Snap's Events Manager",
120
+ },
121
+ {
122
+ ids: (settings) =>
123
+ settings.axon === undefined ? [] : [settings.axon.eventKey],
124
+ name: "Axon event key",
125
+ shape: UUID,
126
+ is: "a UUID",
127
+ from: "the Axon dashboard",
128
+ },
129
+ {
130
+ ids: (settings) =>
131
+ settings.drip === undefined ? [] : [settings.drip.accountId],
132
+ name: "Drip account id",
133
+ shape: /^\d+$/,
134
+ is: "all digits",
135
+ from: "Drip's Site Setup",
136
+ },
137
+ {
138
+ ids: (settings) =>
139
+ settings.clarity === undefined ? [] : [settings.clarity.projectId],
140
+ name: "Clarity project id",
141
+ shape: /^[a-z0-9]{10}$/,
142
+ is: "10 lowercase letters and digits",
143
+ from: "the Clarity project's settings",
144
+ },
145
+ ];
146
+
147
+ /**
148
+ * Fails the build on an id that is not what its vendor issues: configured,
149
+ * emitted, and reporting nowhere is the failure it prevents.
150
+ */
151
+ export function checkVendorIds(settings: VendorSettings, at: string): void {
152
+ for (const check of ID_CHECKS) {
153
+ for (const id of check.ids(settings)) {
154
+ if (!check.shape.test(id)) {
155
+ invalidId(
156
+ at,
157
+ `${check.name} "${id}" is not ${check.is}, so it would report nowhere. Copy it from ${check.from}.`
158
+ );
159
+ }
160
+ }
161
+ }
162
+ }
@@ -1,5 +1,6 @@
1
1
  import type { CurrencyCode } from "../currency.ts";
2
2
  import { readConsent } from "./consent.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /**
5
6
  * Axon's events that a site without a cart sends. The cart and purchase events
@@ -15,9 +16,6 @@ export interface AxonEventData {
15
16
  }
16
17
 
17
18
  /** What `axon-pixel.ts` defines once marketing is granted. */
18
- interface AxonWindow {
19
- axon?: (...args: unknown[]) => void;
20
- }
21
19
 
22
20
  /**
23
21
  * Records an event with Axon's pixel — a lead sent, an account made.
@@ -28,7 +26,7 @@ interface AxonWindow {
28
26
  */
29
27
  export function axonEvent(event: AxonEvent, data?: AxonEventData): void {
30
28
  if (readConsent()?.marketing !== "granted") return;
31
- const axon = (window as unknown as AxonWindow).axon;
29
+ const axon = vendors().axon;
32
30
  if (data === undefined) axon?.("track", event);
33
31
  else axon?.("track", event, data);
34
32
  }
@@ -1,8 +1,9 @@
1
1
  import { appendScript } from "./append-script.ts";
2
2
  import type { ConsentGated } from "./consent-gate.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /** Axon's `axon`: a queue until its scripts arrive and replay it. */
5
- interface Axon {
6
+ export interface Axon {
6
7
  (...args: unknown[]): void;
7
8
  performOperation?: (...args: unknown[]) => void;
8
9
  operationQueue: unknown[];
@@ -10,10 +11,6 @@ interface Axon {
10
11
  eventKey: string;
11
12
  }
12
13
 
13
- interface PixelWindow {
14
- axon?: Axon;
15
- }
16
-
17
14
  const SCRIPTS = [
18
15
  "https://s.axon.ai/pixel.js",
19
16
  "https://res4.applovin.com/p/l/loader.iife.js",
@@ -21,7 +18,7 @@ const SCRIPTS = [
21
18
 
22
19
  /** Axon's own base code, as it defines `axon` before the scripts load. */
23
20
  function defineAxon(eventKey: string): Axon {
24
- const global = window as unknown as PixelWindow;
21
+ const global = vendors();
25
22
  if (global.axon !== undefined) return global.axon;
26
23
  // `arguments` is the list of whatever a call passed, which every regular
27
24
  // `function` gets without declaring it (an arrow function does not). The
@@ -1,4 +1,5 @@
1
1
  import type { DeclaredEvent } from "./event-data.ts";
2
+ import { vendors } from "./vendors.ts";
2
3
 
3
4
  /**
4
5
  * The project's Clarity events. Clarity carries a name and nothing else, so
@@ -17,9 +18,6 @@ import type { DeclaredEvent } from "./event-data.ts";
17
18
  export interface ClarityEvents {}
18
19
 
19
20
  /** What `clarity.ts` defines once the page has loaded. */
20
- interface ClarityWindow {
21
- clarity?: (...args: unknown[]) => void;
22
- }
23
21
 
24
22
  /**
25
23
  * Marks the current Clarity recording with a named event — a form sent, a
@@ -29,5 +27,5 @@ interface ClarityWindow {
29
27
  * sets no cookie. Dropped before Clarity loads, or where it is not configured.
30
28
  */
31
29
  export function clarityEvent(name: DeclaredEvent<ClarityEvents>): void {
32
- (window as unknown as ClarityWindow).clarity?.("event", name);
30
+ vendors().clarity?.("event", name);
33
31
  }
@@ -1,19 +1,16 @@
1
1
  import { appendScript } from "./append-script.ts";
2
2
  import type { ConsentChoices } from "./consent.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /** Clarity's `clarity`: a queue until its script arrives and replays it. */
5
- interface Clarity {
6
+ export interface Clarity {
6
7
  (...args: unknown[]): void;
7
8
  q?: unknown[];
8
9
  }
9
10
 
10
- interface ClarityWindow {
11
- clarity?: Clarity;
12
- }
13
-
14
11
  /** Clarity's own base code, as it defines `clarity` before the script loads. */
15
12
  function defineClarity(): Clarity {
16
- const global = window as unknown as ClarityWindow;
13
+ const global = vendors();
17
14
  if (global.clarity !== undefined) return global.clarity;
18
15
  // `arguments` is the list of whatever a call passed, which every regular
19
16
  // `function` gets without declaring it (an arrow function does not). The
@@ -12,6 +12,8 @@ export type {
12
12
  ConsentChoices,
13
13
  } from "../analytics/consent-storage.ts";
14
14
 
15
+ import { vendors } from "./vendors.ts";
16
+
15
17
  /**
16
18
  * The browser half of consent: remembering an answer, expiring it, and handing
17
19
  * it to Google.
@@ -120,12 +122,7 @@ export function recordConsent(choices: ConsentChoices): void {
120
122
  * copy of that list.
121
123
  */
122
124
  export function applyConsent(choices: ConsentChoices): void {
123
- const update = (
124
- window as unknown as Record<
125
- string,
126
- ((c: ConsentChoices) => void) | undefined
127
- >
128
- )[CONSENT_UPDATE_GLOBAL];
125
+ const update = vendors()[CONSENT_UPDATE_GLOBAL];
129
126
  // Only the categories, so a record's date does not ride along.
130
127
  update?.({ analytics: choices.analytics, marketing: choices.marketing });
131
128
  }
@@ -1,5 +1,6 @@
1
1
  import { readConsent } from "./consent.ts";
2
2
  import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /**
5
6
  * The project's Drip events — the actions its workflows start on — each with
@@ -18,9 +19,6 @@ import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
18
19
  export interface DripEvents {}
19
20
 
20
21
  /** What `drip.ts` defines once marketing is granted. */
21
- interface DripWindow {
22
- _dcq?: unknown[][];
23
- }
24
22
 
25
23
  /**
26
24
  * Records an event with Drip, to start a workflow — a lead sent, a voucher
@@ -38,7 +36,7 @@ export function dripEvent<E extends DeclaredEvent<DripEvents>>(
38
36
  >
39
37
  ): void {
40
38
  if (readConsent()?.marketing !== "granted") return;
41
- const queue = (window as unknown as DripWindow)._dcq;
39
+ const queue = vendors()._dcq;
42
40
  if (properties === undefined) queue?.push(["track", action]);
43
41
  else queue?.push(["track", action, properties]);
44
42
  }
package/src/astro/drip.ts CHANGED
@@ -1,11 +1,6 @@
1
1
  import { appendScript } from "./append-script.ts";
2
2
  import type { ConsentGated } from "./consent-gate.ts";
3
-
4
- /** Drip's globals: a queue of calls, and the account its script reports to. */
5
- interface DripWindow {
6
- _dcq?: unknown[][];
7
- _dcs?: { account?: string };
8
- }
3
+ import { vendors } from "./vendors.ts";
9
4
 
10
5
  /**
11
6
  * Drip, loaded on the first grant. Its script records visits by itself, so
@@ -19,7 +14,7 @@ export function drip(accountId: string): ConsentGated {
19
14
  grant: () => {
20
15
  if (loaded) return;
21
16
  loaded = true;
22
- const global = window as unknown as DripWindow;
17
+ const global = vendors();
23
18
  global._dcq ??= [];
24
19
  global._dcs ??= {};
25
20
  global._dcs.account = accountId;
@@ -1,6 +1,7 @@
1
1
  import { ESCAPE_NAVIGATOR_LOADER } from "../booking/escape-navigator.ts";
2
2
  import { appendScript } from "./append-script.ts";
3
3
  import { CONSENT_EVENT, type ConsentChoices, readConsent } from "./consent.ts";
4
+ import { vendors } from "./vendors.ts";
4
5
 
5
6
  /**
6
7
  * Escape Navigator's consent, from the visitor's answer on this site: its
@@ -13,12 +14,7 @@ function escapeNavigatorConsent(choices: ConsentChoices | undefined) {
13
14
  };
14
15
  }
15
16
 
16
- interface EscapeNavigatorGlobal {
17
- __pendingConsent?: { analytics: boolean; ads: boolean };
18
- setConsent?: (consent: { analytics: boolean; ads: boolean }) => void;
19
- }
20
-
21
- const host = window as unknown as { EscapeNavigator?: EscapeNavigatorGlobal };
17
+ const host = vendors();
22
18
 
23
19
  // Before the loader runs, which reads the pending answer as it starts: its
24
20
  // tags never load on an answer the visitor has not given.
@@ -1,15 +1,12 @@
1
1
  import { appendScript } from "./append-script.ts";
2
+ import { vendors } from "./vendors.ts";
2
3
 
3
4
  /**
4
5
  * Draws each EscapeAll calendar on the page, once EscapeAll's script has
5
6
  * loaded: the call its store scripts make, with the settings the component
6
7
  * wrote on the element.
7
8
  */
8
- // Its script builds selectors by appending to the one it is given, so the
9
- // calendar is named by its id, not handed over as an element.
10
- type Calendar = (selector: string, settings: Record<string, unknown>) => void;
11
-
12
- const host = window as unknown as { GetBookingCalendra?: Calendar };
9
+ const host = vendors();
13
10
 
14
11
  const elements = [...document.querySelectorAll("[data-escapeall-company]")];
15
12
  const [first] = elements;
@@ -1,9 +1,7 @@
1
- import {
2
- GOOGLE_EVENT_GLOBAL,
3
- type GoogleEventSink,
4
- } from "../analytics/google.ts";
1
+ import { GOOGLE_EVENT_GLOBAL } from "../analytics/google.ts";
5
2
  import type { CurrencyCode } from "../currency.ts";
6
3
  import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
4
+ import { vendors } from "./vendors.ts";
7
5
 
8
6
  /** GA4's recommended events that carry items and revenue. */
9
7
  const ECOMMERCE_EVENTS = [
@@ -110,9 +108,7 @@ export function googleEvent<E extends GoogleEventName>(
110
108
  event: E,
111
109
  ...[data]: GoogleEventArgs<E>
112
110
  ): void {
113
- const send = (
114
- window as unknown as Record<string, GoogleEventSink | undefined>
115
- )[GOOGLE_EVENT_GLOBAL];
111
+ const send = vendors()[GOOGLE_EVENT_GLOBAL];
116
112
  send?.(
117
113
  event,
118
114
  data,
@@ -6,6 +6,7 @@ import { reportDevError } from "./dev-log.ts";
6
6
  import { element } from "./element.ts";
7
7
  import { triggered } from "./load-script.ts";
8
8
  import { data } from "./ref.ts";
9
+ import { GOOGLE_MAPS_CALLBACK, vendors } from "./vendors.ts";
9
10
 
10
11
  /** What `GoogleMap.astro` hands the element, serialised. */
11
12
  export interface GoogleMapConfig {
@@ -27,20 +28,17 @@ export interface GoogleMapConfig {
27
28
  }[];
28
29
  }
29
30
 
30
- const CALLBACK = "__atlasGoogleMaps";
31
-
32
31
  let loading: Promise<typeof google.maps> | undefined;
33
32
 
34
33
  /** The Maps JavaScript API, fetched once per page however many maps ask. */
35
34
  export function loadGoogleMaps(apiKey: string): Promise<typeof google.maps> {
36
35
  loading ??= new Promise((resolve, reject) => {
37
- (window as unknown as Record<string, () => void>)[CALLBACK] = () =>
38
- resolve(google.maps);
36
+ vendors()[GOOGLE_MAPS_CALLBACK] = () => resolve(google.maps);
39
37
  const params = {
40
38
  key: apiKey,
41
39
  v: "weekly",
42
40
  loading: "async",
43
- callback: CALLBACK,
41
+ callback: GOOGLE_MAPS_CALLBACK,
44
42
  };
45
43
  appendScript(
46
44
  `https://maps.googleapis.com/maps/api/js?${new URLSearchParams(params)}`
@@ -1,5 +1,6 @@
1
1
  import type { CurrencyCode } from "../currency.ts";
2
2
  import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /** Meta's standard events, which its ad optimisation understands by name. */
5
6
  const META_STANDARD_EVENTS = [
@@ -66,9 +67,6 @@ type MetaEventArgs<E extends MetaEventName> = E extends MetaStandardEvent
66
67
  : EventDataArgs<MetaCustomEvents[E & keyof MetaCustomEvents]>;
67
68
 
68
69
  /** What `meta-pixel.ts` defines once marketing is granted. */
69
- interface MetaWindow {
70
- fbq?: (...args: unknown[]) => void;
71
- }
72
70
 
73
71
  /**
74
72
  * Records an event with Meta's pixel — a lead sent, a booking made. A declared
@@ -83,7 +81,7 @@ export function metaEvent<E extends MetaEventName>(
83
81
  event: E,
84
82
  ...[data]: MetaEventArgs<E>
85
83
  ): void {
86
- const fbq = (window as unknown as MetaWindow).fbq;
84
+ const fbq = vendors().fbq;
87
85
  const method = (META_STANDARD_EVENTS as readonly string[]).includes(event)
88
86
  ? "track"
89
87
  : "trackCustom";
@@ -1,8 +1,9 @@
1
1
  import { appendScript } from "./append-script.ts";
2
2
  import type { ConsentGated } from "./consent-gate.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /** Meta's `fbq`: a queue until `fbevents.js` arrives and replays it. */
5
- interface Fbq {
6
+ export interface Fbq {
6
7
  (...args: unknown[]): void;
7
8
  callMethod?: (...args: unknown[]) => void;
8
9
  queue: unknown[];
@@ -11,16 +12,11 @@ interface Fbq {
11
12
  version: string;
12
13
  }
13
14
 
14
- interface PixelWindow {
15
- fbq?: Fbq;
16
- _fbq?: Fbq;
17
- }
18
-
19
15
  const SCRIPT = "https://connect.facebook.net/en_US/fbevents.js";
20
16
 
21
17
  /** Meta's own base code, as it defines `fbq` before the script loads. */
22
18
  function defineFbq(): Fbq {
23
- const global = window as unknown as PixelWindow;
19
+ const global = vendors();
24
20
  if (global.fbq !== undefined) return global.fbq;
25
21
  // `arguments` is the list of whatever a call passed, which every regular
26
22
  // `function` gets without declaring it (an arrow function does not). The
@@ -57,7 +53,7 @@ export function metaPixel(pixelIds: readonly string[]): ConsentGated {
57
53
  appendScript(SCRIPT);
58
54
  },
59
55
  revoke: () => {
60
- (window as unknown as PixelWindow).fbq?.("consent", "revoke");
56
+ vendors().fbq?.("consent", "revoke");
61
57
  },
62
58
  };
63
59
  }
@@ -1,5 +1,6 @@
1
1
  import type { CurrencyCode } from "../currency.ts";
2
2
  import { readConsent } from "./consent.ts";
3
+ import { vendors } from "./vendors.ts";
3
4
 
4
5
  /** Snap's standard events that a website sends. */
5
6
  export type SnapStandardEvent =
@@ -37,9 +38,6 @@ type SnapEventArgs<E extends SnapStandardEvent> = E extends "PURCHASE"
37
38
  : [data?: SnapEventData];
38
39
 
39
40
  /** What `snap-pixel.ts` defines once marketing is granted. */
40
- interface SnapWindow {
41
- snaptr?: (...args: unknown[]) => void;
42
- }
43
41
 
44
42
  /**
45
43
  * Records a standard event with Snap's pixel — a lead sent, a booking made.
@@ -53,7 +51,7 @@ export function snapEvent<E extends SnapStandardEvent>(
53
51
  ...[data]: SnapEventArgs<E>
54
52
  ): void {
55
53
  if (readConsent()?.marketing !== "granted") return;
56
- const snaptr = (window as unknown as SnapWindow).snaptr;
54
+ const snaptr = vendors().snaptr;
57
55
  if (data === undefined) snaptr?.("track", event);
58
56
  else snaptr?.("track", event, data);
59
57
  }