@escape-game-over/atlas 0.1.116 → 0.1.118

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.
@@ -1,22 +1,19 @@
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
  /** Snap's `snaptr`: a queue until `scevent.min.js` arrives and replays it. */
5
- interface Snaptr {
6
+ export interface Snaptr {
6
7
  (...args: unknown[]): void;
7
8
  handleRequest?: (...args: unknown[]) => void;
8
9
  queue: unknown[];
9
10
  }
10
11
 
11
- interface PixelWindow {
12
- snaptr?: Snaptr;
13
- }
14
-
15
12
  const SCRIPT = "https://sc-static.net/scevent.min.js";
16
13
 
17
14
  /** Snap's own base code, as it defines `snaptr` before the script loads. */
18
15
  function defineSnaptr(): Snaptr {
19
- const global = window as unknown as PixelWindow;
16
+ const global = vendors();
20
17
  if (global.snaptr !== undefined) return global.snaptr;
21
18
  // `arguments` is the list of whatever a call passed, which every regular
22
19
  // `function` gets without declaring it (an arrow function does not). The
@@ -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
  /** TikTok's standard events, by their current names (May 2025 onwards). */
5
6
  export type TikTokStandardEvent =
@@ -63,9 +64,6 @@ type TikTokEventArgs<E extends TikTokEventName> = E extends TikTokStandardEvent
63
64
  : EventDataArgs<TikTokCustomEvents[E & keyof TikTokCustomEvents]>;
64
65
 
65
66
  /** What `tiktok-pixel.ts` defines once marketing is granted. */
66
- interface TikTokWindow {
67
- ttq?: { track(event: string, data?: object): void };
68
- }
69
67
 
70
68
  /**
71
69
  * Records an event with TikTok's pixel — a lead sent, a booking made. Declared
@@ -80,7 +78,7 @@ export function tiktokEvent<E extends TikTokEventName>(
80
78
  event: E,
81
79
  ...[data]: TikTokEventArgs<E>
82
80
  ): void {
83
- const ttq = (window as unknown as TikTokWindow).ttq;
81
+ const ttq = vendors().ttq;
84
82
  if (data === undefined) ttq?.track(event);
85
83
  else ttq?.track(event, data);
86
84
  }
@@ -1,8 +1,15 @@
1
1
  import { appendScript } from "./append-script.ts";
2
2
  import type { ConsentGated } from "./consent-gate.ts";
3
+ import { vendors } from "./vendors.ts";
4
+
5
+ /** What a page calls on `ttq`: each queues, until `events.js` takes over. */
6
+ type TtqMethods = Record<
7
+ (typeof METHODS)[number],
8
+ (...args: unknown[]) => void
9
+ >;
3
10
 
4
11
  /** TikTok's `ttq`: a queue of calls until `events.js` arrives and replays it. */
5
- interface Ttq extends Array<unknown> {
12
+ export interface Ttq extends Array<unknown>, TtqMethods {
6
13
  methods: readonly string[];
7
14
  setAndDefer(target: unknown[], method: string): void;
8
15
  instance(id: string): unknown[];
@@ -10,12 +17,6 @@ interface Ttq extends Array<unknown> {
10
17
  _i: Record<string, unknown[] & { _u?: string }>;
11
18
  _t: Record<string, number>;
12
19
  _o: Record<string, object>;
13
- [method: string]: unknown;
14
- }
15
-
16
- interface PixelWindow {
17
- ttq?: Ttq;
18
- TiktokAnalyticsObject?: string;
19
20
  }
20
21
 
21
22
  const SCRIPT = "https://analytics.tiktok.com/i18n/pixel/events.js";
@@ -39,42 +40,73 @@ const METHODS = [
39
40
  "grantConsent",
40
41
  ] as const;
41
42
 
43
+ /**
44
+ * Each method, queuing its call on `target`. Written out rather than looped
45
+ * over `METHODS`, so the type sees every one: the return type rejects a name
46
+ * missing here, or one `METHODS` lacks.
47
+ */
48
+ function deferTo(target: unknown[]): TtqMethods {
49
+ const defer =
50
+ (method: string) =>
51
+ (...args: unknown[]) => {
52
+ target.push([method, ...args]);
53
+ };
54
+ return {
55
+ page: defer("page"),
56
+ track: defer("track"),
57
+ identify: defer("identify"),
58
+ instances: defer("instances"),
59
+ debug: defer("debug"),
60
+ on: defer("on"),
61
+ off: defer("off"),
62
+ once: defer("once"),
63
+ ready: defer("ready"),
64
+ alias: defer("alias"),
65
+ group: defer("group"),
66
+ enableCookie: defer("enableCookie"),
67
+ disableCookie: defer("disableCookie"),
68
+ holdConsent: defer("holdConsent"),
69
+ revokeConsent: defer("revokeConsent"),
70
+ grantConsent: defer("grantConsent"),
71
+ };
72
+ }
73
+
42
74
  /** TikTok's own base code, as it defines `ttq` before the script loads. */
43
75
  function defineTtq(): Ttq {
44
- const global = window as unknown as PixelWindow;
76
+ const global = vendors();
45
77
  if (global.ttq !== undefined) return global.ttq;
46
78
  global.TiktokAnalyticsObject = "ttq";
47
- const ttq = [] as unknown as Ttq;
48
- ttq.methods = METHODS;
49
- ttq.setAndDefer = (target, method) => {
50
- (target as unknown as Record<string, unknown>)[method] = (
51
- ...args: unknown[]
52
- ) => target.push([method, ...args]);
53
- };
54
- for (const method of METHODS) ttq.setAndDefer(ttq, method);
55
- ttq.instance = (id) => {
56
- const queue = ttq._i?.[id] ?? [];
57
- for (const method of METHODS) ttq.setAndDefer(queue, method);
58
- return queue;
59
- };
60
- ttq.load = (id, options = {}) => {
61
- ttq._i ??= {};
62
- ttq._i[id] = Object.assign([], { _u: SCRIPT });
63
- ttq._t ??= {};
64
- ttq._t[id] = Date.now();
65
- ttq._o ??= {};
66
- ttq._o[id] = options;
67
- appendScript(
68
- `${SCRIPT}?${new URLSearchParams({ sdkid: id, lib: "ttq" })}`
69
- );
70
- };
79
+ const queue: unknown[] = [];
80
+ const ttq: Ttq = Object.assign(queue, deferTo(queue), {
81
+ methods: METHODS,
82
+ // TikTok's script calls this itself, with any name, so it stays as
83
+ // loose as the snippet's.
84
+ setAndDefer: (target: unknown[], method: string) => {
85
+ Object.assign(target, {
86
+ [method]: (...args: unknown[]) =>
87
+ target.push([method, ...args]),
88
+ });
89
+ },
90
+ instance: (id: string) => {
91
+ const instance = ttq._i[id] ?? [];
92
+ return Object.assign(instance, deferTo(instance));
93
+ },
94
+ load: (id: string, options: object = {}) => {
95
+ ttq._i[id] = Object.assign([], { _u: SCRIPT });
96
+ ttq._t[id] = Date.now();
97
+ ttq._o[id] = options;
98
+ appendScript(
99
+ `${SCRIPT}?${new URLSearchParams({ sdkid: id, lib: "ttq" })}`
100
+ );
101
+ },
102
+ _i: {},
103
+ _t: {},
104
+ _o: {},
105
+ });
71
106
  global.ttq = ttq;
72
107
  return ttq;
73
108
  }
74
109
 
75
- const call = (ttq: Ttq, method: (typeof METHODS)[number], ...args: unknown[]) =>
76
- (ttq[method] as (...args: unknown[]) => void)(...args);
77
-
78
110
  /** The pixel: loaded on the first grant, paused and resumed by later answers. */
79
111
  export function tiktokPixel(pixelIds: readonly string[]): ConsentGated {
80
112
  let loaded = false;
@@ -83,15 +115,14 @@ export function tiktokPixel(pixelIds: readonly string[]): ConsentGated {
83
115
  const ttq = defineTtq();
84
116
  // Also lifts the hold of TikTok's Cookie Consent Mode, where a
85
117
  // pixel has it on in Events Manager.
86
- call(ttq, "grantConsent");
118
+ ttq.grantConsent();
87
119
  if (loaded) return;
88
120
  loaded = true;
89
121
  for (const id of pixelIds) ttq.load(id);
90
- call(ttq, "page");
122
+ ttq.page();
91
123
  },
92
124
  revoke: () => {
93
- const ttq = (window as unknown as PixelWindow).ttq;
94
- if (ttq !== undefined) call(ttq, "revokeConsent");
125
+ vendors().ttq?.revokeConsent();
95
126
  },
96
127
  };
97
128
  }
@@ -1,4 +1,5 @@
1
1
  import type { DeclaredEvent, EventDataArgs } from "./event-data.ts";
2
+ import { vendors } from "./vendors.ts";
2
3
 
3
4
  /**
4
5
  * The project's Umami events, each with the data it carries, `undefined` for
@@ -34,10 +35,5 @@ export function umamiEvent<E extends DeclaredEvent<UmamiEvents>>(
34
35
  // Flat string values; anything else in a declaration fails here.
35
36
  ...[data]: EventDataArgs<UmamiEvents[E] & Readonly<Record<string, string>>>
36
37
  ): void {
37
- const umami = (
38
- window as unknown as {
39
- umami?: { track(event: string, data?: object): void };
40
- }
41
- ).umami;
42
- umami?.track(name, data);
38
+ vendors().umami?.track(name, data);
43
39
  }
@@ -0,0 +1,64 @@
1
+ import type {
2
+ CONSENT_UPDATE_GLOBAL,
3
+ GOOGLE_EVENT_GLOBAL,
4
+ GoogleEventSink,
5
+ } from "../analytics/google.ts";
6
+ import type { Axon } from "./axon-pixel.ts";
7
+ import type { Clarity } from "./clarity.ts";
8
+ import type { ConsentChoices } from "./consent.ts";
9
+ import type { Fbq } from "./meta-pixel.ts";
10
+ import type { Snaptr } from "./snap-pixel.ts";
11
+ import type { Ttq } from "./tiktok-pixel.ts";
12
+
13
+ /** The callback the Maps API calls once it has loaded: `?callback=` names it. */
14
+ export const GOOGLE_MAPS_CALLBACK = "__atlasGoogleMaps";
15
+
16
+ /**
17
+ * Every global Atlas's scripts read or define: the vendors' own, and the ones
18
+ * Atlas's head script leaves for its modules.
19
+ *
20
+ * Each is optional, since a vendor's snippet defines it only once it runs,
21
+ * and a blocker can stop that. A file reads them through `vendors()` rather
22
+ * than casting `window` at each site.
23
+ *
24
+ * Not a `declare global`: that would put `window.fbq` in every app's types,
25
+ * on sites without Meta too. These stay Atlas's.
26
+ */
27
+ interface Vendors {
28
+ fbq?: Fbq;
29
+ _fbq?: Fbq;
30
+ ttq?: Ttq;
31
+ TiktokAnalyticsObject?: string;
32
+ snaptr?: Snaptr;
33
+ axon?: Axon;
34
+ clarity?: Clarity;
35
+ _dcq?: unknown[][];
36
+ _dcs?: { account?: string };
37
+ umami?: { track(event: string, data?: object): void };
38
+ EscapeNavigator?: {
39
+ __pendingConsent?: { analytics: boolean; ads: boolean };
40
+ setConsent?: (consent: { analytics: boolean; ads: boolean }) => void;
41
+ };
42
+ // Its script builds selectors by appending to the one it is given, so the
43
+ // calendar is named by its id, not handed over as an element.
44
+ GetBookingCalendra?: (
45
+ selector: string,
46
+ settings: Record<string, unknown>
47
+ ) => void;
48
+ [GOOGLE_MAPS_CALLBACK]?: () => void;
49
+ }
50
+
51
+ /** Atlas's head script's, under the names it is emitted with. */
52
+ type HeadGlobals = {
53
+ [Name in typeof CONSENT_UPDATE_GLOBAL]?: (choices: ConsentChoices) => void;
54
+ } & {
55
+ [Name in typeof GOOGLE_EVENT_GLOBAL]?: GoogleEventSink;
56
+ };
57
+
58
+ /**
59
+ * `window`, typed with the globals above. A function, not a constant, so a
60
+ * module that imports it touches `window` only when it runs in a browser.
61
+ */
62
+ export function vendors(): Window & Vendors & HeadGlobals {
63
+ return window;
64
+ }
package/src/index.ts CHANGED
@@ -188,12 +188,7 @@ export type {
188
188
  RedirectTarget,
189
189
  SitePath,
190
190
  } from "./redirects.ts";
191
- export {
192
- enableRoutes,
193
- familyGuard,
194
- familyMemberSlugs,
195
- type RouteIdsOf,
196
- } from "./routes/family.ts";
191
+ export type { RouteFamily, RouteIdsOf } from "./routes/family.ts";
197
192
  export { defineSite } from "./site/define.ts";
198
193
  export type {
199
194
  Crumb,
@@ -43,15 +43,16 @@ type MemberId<M> = M extends string
43
43
  * The route ids a family produced.
44
44
  *
45
45
  * ```ts
46
- * const challengeRoutes = routeFamily(config, "challenges", challenges);
47
- * export type ChallengeRouteId = RouteIdsOf<typeof challengeRoutes>;
46
+ * const challenges = atlas.family("challenges", CHALLENGES);
47
+ * export type ChallengeRouteId = RouteIdsOf<typeof challenges>;
48
48
  * ```
49
49
  *
50
50
  * Reading them back off the family means the prefix is written once, in the
51
51
  * call. Spelling the ids out as `` `challenges-${string}` `` would be a second
52
52
  * copy of it, free to drift the day the URL changes.
53
53
  */
54
- export type RouteIdsOf<Family> = StringKeys<Family>;
54
+ export type RouteIdsOf<Family extends { readonly routes: unknown }> =
55
+ StringKeys<Family["routes"]>;
55
56
 
56
57
  /**
57
58
  * A URL prefix as a route-id prefix: `"news/posts"` -> `"news-posts"`.
@@ -349,3 +350,138 @@ export function familyGuard<
349
350
  return <T extends string>(id: T): id is T & StringKeys<Family> =>
350
351
  Object.hasOwn(family, id);
351
352
  }
353
+
354
+ /** A family's translated prefix, as `slugs` joins it. */
355
+ type PrefixByOf<Options> = Options extends {
356
+ readonly prefixByLocale: infer P extends ByLocale;
357
+ }
358
+ ? P
359
+ : Record<never, never>;
360
+
361
+ /**
362
+ * A family, and what a project does with it: spread its `routes` into the
363
+ * table, and ask the family — not a string — anything about its members.
364
+ *
365
+ * Every helper is bound to the prefix the family was made with, so the prefix
366
+ * is written once: a project cannot enable `"challenge"` members of a
367
+ * `"challenges"` family, or build a member's path under the wrong prefix.
368
+ */
369
+ export interface RouteFamily<
370
+ Prefix extends string,
371
+ Member extends string,
372
+ Routes,
373
+ PrefixBy extends ByLocale,
374
+ > {
375
+ /** The member routes, to spread into `atlas.routes`. */
376
+ readonly routes: Routes;
377
+
378
+ /**
379
+ * Whether a route id is one of this family's, as a type guard. See
380
+ * {@link familyGuard} for why it is generic in the input.
381
+ */
382
+ is<T extends string>(id: T): id is T & StringKeys<Routes>;
383
+
384
+ /** A member's route id: `routeIdOf("blue-room")` is `"rooms-blue-room"`. */
385
+ routeIdOf(member: Member): StringKeys<Routes>;
386
+
387
+ /** The member a route id of this family is the page of. */
388
+ memberOf(routeId: StringKeys<Routes>): Member;
389
+
390
+ /**
391
+ * One override for many members, for a project's `overrideRoutes`: by
392
+ * default, `{ enabled: true }`. Keys stay literal; see {@link enableRoutes}.
393
+ *
394
+ * Takes any id, not only the family's members, so a helper generic over a
395
+ * venue's rooms can pass them through. A member the family lacks is still
396
+ * an error: on the `overrideRoutes` line, as a route the table lacks.
397
+ */
398
+ enable<
399
+ const Picked extends readonly (string | { readonly id: string })[],
400
+ const Override extends { readonly enabled?: boolean } = {
401
+ readonly enabled: true;
402
+ },
403
+ >(
404
+ members: Picked,
405
+ override?: Override
406
+ ): {
407
+ readonly [M in Picked[number] as `${Dashed<Prefix>}-${MemberId<M> &
408
+ string}`]: Override;
409
+ };
410
+
411
+ /**
412
+ * A member's paths at a segment of a project's own, in every locale,
413
+ * under the family's prefix and its translations. See
414
+ * {@link familyMemberSlugs}.
415
+ */
416
+ slugs<
417
+ const Segment extends string,
418
+ const SegmentBy extends ByLocale = Record<never, never>,
419
+ >(
420
+ segment: Segment,
421
+ segmentByLocale?: SegmentBy
422
+ ): FamilyMemberSlugs<Prefix, Segment, PrefixBy, SegmentBy>;
423
+ }
424
+
425
+ /** Builds a {@link RouteFamily}: what `atlas.family` returns. */
426
+ export function createFamily<
427
+ const C extends SiteConfigShape,
428
+ const Prefix extends string,
429
+ const Members extends readonly FamilyMemberInput<LocalesOf<C>>[],
430
+ const Options extends FamilyOptions<LocalesOf<C>> = Record<string, never>,
431
+ >(
432
+ config: C,
433
+ prefix: Prefix,
434
+ members: Members,
435
+ options?: Options
436
+ ): RouteFamily<
437
+ Prefix,
438
+ MemberId<Members[number]> & string,
439
+ ReturnType<typeof routeFamily<C, Prefix, Members, Options>>,
440
+ PrefixByOf<Options>
441
+ > {
442
+ const routes = routeFamily(config, prefix, members, options);
443
+ const enable = enableRoutes(prefix);
444
+ const ids = members.map((member) =>
445
+ typeof member === "string" ? member : member.id
446
+ );
447
+ const routeId = (member: string) =>
448
+ `${prefix.replaceAll("/", "-")}-${member}`;
449
+ const byRouteId = new Map(ids.map((member) => [routeId(member), member]));
450
+
451
+ return {
452
+ routes,
453
+ is: familyGuard(routes),
454
+ routeIdOf: (member) => {
455
+ if (!ids.includes(member)) {
456
+ throw new Error(
457
+ `"${member}" is not a member of the "${prefix}" family.`
458
+ );
459
+ }
460
+ return routeId(member) as never;
461
+ },
462
+ memberOf: (id) => {
463
+ const member = byRouteId.get(id);
464
+ if (member === undefined) {
465
+ throw new Error(
466
+ `"${id}" is not a route of the "${prefix}" family.`
467
+ );
468
+ }
469
+ return member as never;
470
+ },
471
+ enable: (picked, override) => enable(picked, override),
472
+ slugs: (segment, segmentByLocale) => {
473
+ // The join `familyMemberSlugs` makes, with the family's own prefix
474
+ // translations rather than a copy of them.
475
+ const slugByLocale = joinByLocale(
476
+ prefix,
477
+ segment,
478
+ options?.prefixByLocale,
479
+ segmentByLocale
480
+ );
481
+ return {
482
+ slug: `${prefix}/${segment}`,
483
+ ...(slugByLocale === undefined ? {} : { slugByLocale }),
484
+ } as never;
485
+ },
486
+ };
487
+ }
@@ -40,9 +40,9 @@ import {
40
40
  type ValidateSlugDepth,
41
41
  } from "../routes/define.ts";
42
42
  import {
43
+ createFamily,
43
44
  type FamilyMemberInput,
44
45
  type FamilyOptions,
45
- routeFamily,
46
46
  } from "../routes/family.ts";
47
47
  import { createSite } from "./create.ts";
48
48
 
@@ -56,7 +56,7 @@ import { createSite } from "./create.ts";
56
56
  *
57
57
  * // config/routes.ts, messages.ts, projects/<name>/project.ts, site.ts
58
58
  * const rooms = atlas.family("rooms", ROOMS, { enabled: true });
59
- * export const defaultRoutes = atlas.routes({ ...rooms, home: {…} });
59
+ * export const defaultRoutes = atlas.routes({ ...rooms.routes, home: {…} });
60
60
  * export const defaultMessages = atlas.messages({…});
61
61
  * export const messages = atlas.catalog(defaultMessages, { locales: […], override: {…} });
62
62
  * export const { project, redirects } = atlas.project(messages, defaultRoutes, {…});
@@ -76,17 +76,21 @@ export function defineSite<
76
76
  return {
77
77
  config,
78
78
 
79
- /** A route per member, under one prefix: `rooms/red-room` for `rooms`. */
79
+ /**
80
+ * A route per member, under one prefix: `rooms/red-room` for `rooms`.
81
+ * Spread its `routes` into `routes()`; ask it the rest.
82
+ */
80
83
  family<
81
84
  const Prefix extends string,
82
85
  const Members extends readonly FamilyMemberInput<L>[],
83
86
  const Options extends FamilyOptions<L> = Record<string, never>,
84
- >(
85
- prefix: Prefix,
86
- members: Members,
87
- options?: Options
88
- ): ReturnType<typeof routeFamily<C, Prefix, Members, Options>> {
89
- return routeFamily(config, prefix, members, options);
87
+ >(prefix: Prefix, members: Members, options?: Options) {
88
+ return createFamily<C, Prefix, Members, Options>(
89
+ config,
90
+ prefix,
91
+ members,
92
+ options
93
+ );
90
94
  },
91
95
 
92
96
  /** The route table every project starts from. */
package/src/warn.ts CHANGED
@@ -43,12 +43,12 @@ const RESET = `${ESC}[0m`;
43
43
  * `process` and simply gets colour, which is what a browser console renders
44
44
  * anyway.
45
45
  */
46
- const env = () =>
47
- (
48
- globalThis as {
49
- process?: { env?: Readonly<Record<string, string | undefined>> };
50
- }
51
- ).process?.env;
46
+ const env = () => {
47
+ const host: typeof globalThis & {
48
+ process?: { env?: Readonly<Record<string, string | undefined>> };
49
+ } = globalThis;
50
+ return host.process?.env;
51
+ };
52
52
 
53
53
  // Functions, not values worked out at import: a browser bundle that imports
54
54
  // anything near this file would otherwise run the reads on every page.
@@ -1,24 +0,0 @@
1
- import { invalidId, UUID, type Uuid } from "./ids.ts";
2
-
3
- /**
4
- * AppLovin's Axon pixel. Marketing: it loads only once a visitor grants that.
5
- *
6
- * @see https://support.applovin.com/en/growth/promoting-your-websites/axon-pixel-integration/axon-pixel-native-js
7
- * @see https://support.applovin.com/en/growth/promoting-your-websites/axon-pixel-integration/events-and-objects
8
- */
9
- export interface AxonPixelSettings {
10
- /** From the Axon dashboard: a UUID. */
11
- readonly eventKey: Uuid;
12
- }
13
-
14
- /** Fails the build on a key that is not an event key: it would report nowhere. */
15
- export function checkAxonPixel(
16
- axon: AxonPixelSettings | undefined,
17
- at: string
18
- ): void {
19
- if (axon === undefined || UUID.test(axon.eventKey)) return;
20
- invalidId(
21
- at,
22
- `Axon event key "${axon.eventKey}" is not a UUID, so it would report nowhere. Copy it from the Axon dashboard.`
23
- );
24
- }
@@ -1,29 +0,0 @@
1
- import { CLARITY_PROJECT_ID, invalidId } from "./ids.ts";
2
-
3
- /**
4
- * Microsoft Clarity. It has a cookieless mode, so like Google it loads for
5
- * every visitor: without consent it records each page view on its own and sets
6
- * no cookie, and the visitor's answer switches its cookies on or off.
7
- *
8
- * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-setup
9
- * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/consent-mode
10
- * @see https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-consent-api-v2
11
- */
12
- export interface ClaritySettings {
13
- /** From the Clarity project's settings: 10 lowercase letters and digits. */
14
- readonly projectId: string;
15
- }
16
-
17
- /** Fails the build on an id that is not a project id: it would report nowhere. */
18
- export function checkClarity(
19
- clarity: ClaritySettings | undefined,
20
- at: string
21
- ): void {
22
- if (clarity === undefined || CLARITY_PROJECT_ID.test(clarity.projectId)) {
23
- return;
24
- }
25
- invalidId(
26
- at,
27
- `Clarity project id "${clarity.projectId}" is not 10 lowercase letters and digits, so it would report nowhere. Copy it from the Clarity project's settings.`
28
- );
29
- }
@@ -1,20 +0,0 @@
1
- import { DRIP_ACCOUNT_ID, invalidId, type NumericId } from "./ids.ts";
2
-
3
- /**
4
- * Drip's site tracking. Marketing: it loads only once a visitor grants that.
5
- *
6
- * @see https://help.drip.com/hc/en-us/articles/4424702539789-Install-Your-JavaScript-Snippet
7
- */
8
- export interface DripSettings {
9
- /** From Drip's Site Setup: digits, the `_dcs.account` of its snippet. */
10
- readonly accountId: NumericId;
11
- }
12
-
13
- /** Fails the build on an id that is not an account id: it would report nowhere. */
14
- export function checkDrip(drip: DripSettings | undefined, at: string): void {
15
- if (drip === undefined || DRIP_ACCOUNT_ID.test(drip.accountId)) return;
16
- invalidId(
17
- at,
18
- `Drip account id "${drip.accountId}" is not all digits, so it would report nowhere. Copy it from Drip's Site Setup.`
19
- );
20
- }
@@ -1,29 +0,0 @@
1
- import type { NonEmpty } from "../types.ts";
2
- import { invalidId, META_PIXEL_ID, type NumericId } 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
- /** Fails the build on an id that is not a pixel id: it would report nowhere. */
17
- export function checkMetaPixel(
18
- meta: MetaPixelSettings | undefined,
19
- at: string
20
- ): void {
21
- for (const id of meta?.pixelIds ?? []) {
22
- if (!META_PIXEL_ID.test(id)) {
23
- invalidId(
24
- at,
25
- `Meta pixel id "${id}" is not 15 or 16 digits, so it would report nowhere. Copy it from Events Manager.`
26
- );
27
- }
28
- }
29
- }
@@ -1,28 +0,0 @@
1
- import type { NonEmpty } from "../types.ts";
2
- import { invalidId, UUID, type Uuid } from "./ids.ts";
3
-
4
- /**
5
- * Snapchat's pixel. Marketing: it loads only once a visitor grants that.
6
- *
7
- * @see https://businesshelp.snapchat.com/s/article/pixel-direct-implementation
8
- * @see https://forbusiness.snapchat.com/en-US/blog/the-snap-pixel-how-it-works-and-how-to-install-it
9
- */
10
- export interface SnapPixelSettings {
11
- /** From Snap's Events Manager: a UUID. */
12
- readonly pixelIds: NonEmpty<Uuid>;
13
- }
14
-
15
- /** Fails the build on an id that is not a pixel id: it would report nowhere. */
16
- export function checkSnapPixel(
17
- snapchat: SnapPixelSettings | undefined,
18
- at: string
19
- ): void {
20
- for (const id of snapchat?.pixelIds ?? []) {
21
- if (!UUID.test(id)) {
22
- invalidId(
23
- at,
24
- `Snap pixel id "${id}" is not a UUID, so it would report nowhere. Copy it from Snap's Events Manager.`
25
- );
26
- }
27
- }
28
- }