@escape-game-over/atlas 0.1.56 → 0.1.58
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/axon-pixel.ts +20 -0
- package/src/analytics/index.ts +14 -0
- package/src/analytics/snap-pixel.ts +26 -0
- package/src/astro/AxonPixel.astro +18 -0
- package/src/astro/ConsentBanner.astro +5 -1
- package/src/astro/SnapPixel.astro +18 -0
- package/src/astro/axon-event.ts +34 -0
- package/src/astro/axon-pixel.ts +72 -0
- package/src/astro/client.ts +2 -0
- package/src/astro/meta-pixel.ts +8 -4
- package/src/astro/snap-event.ts +59 -0
- package/src/astro/snap-pixel.ts +69 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { warn } from "../warn.ts";
|
|
2
|
+
import { UUID } from "./snap-pixel.ts";
|
|
3
|
+
|
|
4
|
+
/** AppLovin's Axon pixel. Marketing: it loads only once a visitor grants that. */
|
|
5
|
+
export interface AxonPixelSettings {
|
|
6
|
+
/** From the Axon dashboard: a UUID. */
|
|
7
|
+
readonly eventKey: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Warns about a key that is not an event key: it would report nowhere. */
|
|
11
|
+
export function checkAxonPixel(
|
|
12
|
+
axon: AxonPixelSettings | undefined,
|
|
13
|
+
at: string
|
|
14
|
+
): void {
|
|
15
|
+
if (axon === undefined || UUID.test(axon.eventKey)) return;
|
|
16
|
+
warn(
|
|
17
|
+
at,
|
|
18
|
+
`Axon event key "${axon.eventKey}" is not a UUID, so it reports nowhere. Copy it from the Axon dashboard.`
|
|
19
|
+
);
|
|
20
|
+
}
|
package/src/analytics/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
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";
|
|
3
4
|
import { type GoogleSettings, googleEmits, googleScripts } from "./google.ts";
|
|
4
5
|
import { checkMetaPixel, type MetaPixelSettings } from "./meta-pixel.ts";
|
|
6
|
+
import { checkSnapPixel, type SnapPixelSettings } from "./snap-pixel.ts";
|
|
5
7
|
import type { AnalyticsTags } from "./tags.ts";
|
|
6
8
|
import { checkTikTokPixel, type TikTokPixelSettings } from "./tiktok-pixel.ts";
|
|
7
9
|
import {
|
|
@@ -25,6 +27,7 @@ import {
|
|
|
25
27
|
* project states ids; lib decides everything that follows from them.
|
|
26
28
|
*/
|
|
27
29
|
|
|
30
|
+
export type { AxonPixelSettings } from "./axon-pixel.ts";
|
|
28
31
|
export {
|
|
29
32
|
CONSENT_UPDATE_GLOBAL,
|
|
30
33
|
type ConsentDefaults,
|
|
@@ -32,6 +35,7 @@ export {
|
|
|
32
35
|
type GoogleSettings,
|
|
33
36
|
} from "./google.ts";
|
|
34
37
|
export type { MetaPixelSettings } from "./meta-pixel.ts";
|
|
38
|
+
export type { SnapPixelSettings } from "./snap-pixel.ts";
|
|
35
39
|
export type { AnalyticsTags } from "./tags.ts";
|
|
36
40
|
export type { TikTokPixelSettings } from "./tiktok-pixel.ts";
|
|
37
41
|
export type {
|
|
@@ -48,6 +52,10 @@ export interface AnalyticsSettings {
|
|
|
48
52
|
readonly meta?: MetaPixelSettings;
|
|
49
53
|
/** Loaded by `ConsentBanner`, after marketing is granted. */
|
|
50
54
|
readonly tiktok?: TikTokPixelSettings;
|
|
55
|
+
/** Loaded by `ConsentBanner`, after marketing is granted. */
|
|
56
|
+
readonly snapchat?: SnapPixelSettings;
|
|
57
|
+
/** Loaded by `ConsentBanner`, after marketing is granted. */
|
|
58
|
+
readonly axon?: AxonPixelSettings;
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
@@ -70,6 +78,8 @@ export function analyticsScripts(
|
|
|
70
78
|
checkUmamiDomains(analytics.umami, origin);
|
|
71
79
|
checkMetaPixel(analytics.meta, origin);
|
|
72
80
|
checkTikTokPixel(analytics.tiktok, origin);
|
|
81
|
+
checkSnapPixel(analytics.snapchat, origin);
|
|
82
|
+
checkAxonPixel(analytics.axon, origin);
|
|
73
83
|
const google = googleScripts(analytics.google, origin);
|
|
74
84
|
return {
|
|
75
85
|
head: [...google.head, ...umamiScripts(analytics.umami)],
|
|
@@ -112,6 +122,10 @@ export function consentVendors(
|
|
|
112
122
|
meta: analytics.meta !== undefined,
|
|
113
123
|
// Its Cookie Consent Mode holds events back; it is not cookieless.
|
|
114
124
|
tiktok: analytics.tiktok !== undefined,
|
|
125
|
+
// No consent mode at all.
|
|
126
|
+
snapchat: analytics.snapchat !== undefined,
|
|
127
|
+
// No consent mode at all.
|
|
128
|
+
axon: analytics.axon !== undefined,
|
|
115
129
|
};
|
|
116
130
|
|
|
117
131
|
return (Object.keys(VENDORS) as (keyof AnalyticsSettings)[]).filter(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NonEmpty } from "../types.ts";
|
|
2
|
+
import { warn } from "../warn.ts";
|
|
3
|
+
|
|
4
|
+
/** Snapchat's pixel. Marketing: it loads only once a visitor grants that. */
|
|
5
|
+
export interface SnapPixelSettings {
|
|
6
|
+
/** From Snap's Events Manager: a UUID. */
|
|
7
|
+
readonly pixelIds: NonEmpty<string>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const UUID =
|
|
11
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
12
|
+
|
|
13
|
+
/** Warns about an id that is not a pixel id: it would report nowhere. */
|
|
14
|
+
export function checkSnapPixel(
|
|
15
|
+
snapchat: SnapPixelSettings | undefined,
|
|
16
|
+
at: string
|
|
17
|
+
): void {
|
|
18
|
+
for (const id of snapchat?.pixelIds ?? []) {
|
|
19
|
+
if (!UUID.test(id)) {
|
|
20
|
+
warn(
|
|
21
|
+
at,
|
|
22
|
+
`Snap pixel id "${id}" is not a UUID, so it reports nowhere. Copy it from Snap's Events Manager.`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
+
import type { AxonPixelSettings } from "../analytics/axon-pixel.ts";
|
|
4
|
+
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
+
import { axonPixelElement } from "./axon-pixel.ts";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
readonly settings: AxonPixelSettings;
|
|
9
|
+
}
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<script src="./axon-pixel.ts" />
|
|
13
|
+
|
|
14
|
+
<AtlasElement
|
|
15
|
+
of={axonPixelElement}
|
|
16
|
+
data-event-key={Astro.props.settings.eventKey}
|
|
17
|
+
hidden
|
|
18
|
+
/>
|
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* buttons.
|
|
6
6
|
*
|
|
7
7
|
* Renders nothing — and ships no script — when this site's analytics need no
|
|
8
|
-
* permission. Also loads the vendors that wait for consent, such as Meta
|
|
8
|
+
* permission. Also loads the vendors that wait for consent, such as the Meta, TikTok, Snap and Axon pixels. Otherwise the banner starts hidden and shows only to a visitor
|
|
9
9
|
* with no answer on record, or when a `data-consent-reopen` control is pressed.
|
|
10
10
|
*/
|
|
11
11
|
import type { HTMLAttributes } from "astro/types";
|
|
12
12
|
import { type AnalyticsSettings, consentRequired } from "../analytics/index.ts";
|
|
13
|
+
import AxonPixel from "./AxonPixel.astro";
|
|
13
14
|
import ConsentElement from "./ConsentElement.astro";
|
|
14
15
|
import MetaPixel from "./MetaPixel.astro";
|
|
16
|
+
import SnapPixel from "./SnapPixel.astro";
|
|
15
17
|
import TikTokPixel from "./TikTokPixel.astro";
|
|
16
18
|
|
|
17
19
|
type Props = HTMLAttributes<"div"> & {
|
|
@@ -28,5 +30,7 @@ const { analytics, ...attrs } = Astro.props;
|
|
|
28
30
|
</ConsentElement>
|
|
29
31
|
{analytics?.meta && <MetaPixel settings={analytics.meta} />}
|
|
30
32
|
{analytics?.tiktok && <TikTokPixel settings={analytics.tiktok} />}
|
|
33
|
+
{analytics?.snapchat && <SnapPixel settings={analytics.snapchat} />}
|
|
34
|
+
{analytics?.axon && <AxonPixel settings={analytics.axon} />}
|
|
31
35
|
</>
|
|
32
36
|
)}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
+
import type { SnapPixelSettings } from "../analytics/snap-pixel.ts";
|
|
4
|
+
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
+
import { snapPixelElement } from "./snap-pixel.ts";
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
readonly settings: SnapPixelSettings;
|
|
9
|
+
}
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<script src="./snap-pixel.ts" />
|
|
13
|
+
|
|
14
|
+
<AtlasElement
|
|
15
|
+
of={snapPixelElement}
|
|
16
|
+
data-pixel-ids={JSON.stringify(Astro.props.settings.pixelIds)}
|
|
17
|
+
hidden
|
|
18
|
+
/>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { CurrencyCode } from "../money.ts";
|
|
2
|
+
import { readConsent } from "./consent.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Axon's events that a site without a cart sends. The cart and purchase events
|
|
6
|
+
* are left out: Axon requires line items for them, and the visitor's email or
|
|
7
|
+
* phone for `purchase`.
|
|
8
|
+
*/
|
|
9
|
+
export type AxonEvent = "generate_lead" | "sign_up" | "login" | "subscribe";
|
|
10
|
+
|
|
11
|
+
/** What the visitor chose, never who they are. */
|
|
12
|
+
export interface AxonEventData {
|
|
13
|
+
readonly value?: number;
|
|
14
|
+
readonly currency?: CurrencyCode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** What `axon-pixel.ts` defines once marketing is granted. */
|
|
18
|
+
interface AxonWindow {
|
|
19
|
+
axon?: (...args: unknown[]) => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Records an event with Axon's pixel — a lead sent, an account made.
|
|
24
|
+
*
|
|
25
|
+
* Dropped unless the pixel loaded and marketing is still granted: Axon has no
|
|
26
|
+
* consent call of its own, so the check is here. An analytics call must never
|
|
27
|
+
* be the reason a page misbehaves, so a dropped event is silent.
|
|
28
|
+
*/
|
|
29
|
+
export function axonEvent(event: AxonEvent, data?: AxonEventData): void {
|
|
30
|
+
if (readConsent()?.marketing !== "granted") return;
|
|
31
|
+
const axon = (window as unknown as AxonWindow).axon;
|
|
32
|
+
if (data === undefined) axon?.("track", event);
|
|
33
|
+
else axon?.("track", event, data);
|
|
34
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { appendScript } from "./append-script.ts";
|
|
2
|
+
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
+
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
+
import { element } from "./element.ts";
|
|
5
|
+
import { data } from "./ref.ts";
|
|
6
|
+
|
|
7
|
+
/** Axon's `axon`: a queue until its scripts arrive and replay it. */
|
|
8
|
+
interface Axon {
|
|
9
|
+
(...args: unknown[]): void;
|
|
10
|
+
performOperation?: (...args: unknown[]) => void;
|
|
11
|
+
operationQueue: unknown[];
|
|
12
|
+
ts: number;
|
|
13
|
+
eventKey: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PixelWindow {
|
|
17
|
+
axon?: Axon;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SCRIPTS = [
|
|
21
|
+
"https://s.axon.ai/pixel.js",
|
|
22
|
+
"https://res4.applovin.com/p/l/loader.iife.js",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
/** Axon's own base code, as it defines `axon` before the scripts load. */
|
|
26
|
+
function defineAxon(eventKey: string): Axon {
|
|
27
|
+
const global = window as unknown as PixelWindow;
|
|
28
|
+
if (global.axon !== undefined) return global.axon;
|
|
29
|
+
// `arguments` is the list of whatever a call passed, which every regular
|
|
30
|
+
// `function` gets without declaring it (an arrow function does not). The
|
|
31
|
+
// queue stores that object rather than an array because Axon's official
|
|
32
|
+
// snippet does: it is the shape Axon promises its script will replay.
|
|
33
|
+
const axon = function () {
|
|
34
|
+
// biome-ignore lint/complexity/noArguments: see above.
|
|
35
|
+
const args = arguments;
|
|
36
|
+
if (axon.performOperation)
|
|
37
|
+
Reflect.apply(axon.performOperation, axon, args);
|
|
38
|
+
else axon.operationQueue.push(args);
|
|
39
|
+
} as Axon;
|
|
40
|
+
axon.operationQueue = [];
|
|
41
|
+
axon.ts = Date.now();
|
|
42
|
+
axon.eventKey = eventKey;
|
|
43
|
+
global.axon = axon;
|
|
44
|
+
for (const src of SCRIPTS) appendScript(src);
|
|
45
|
+
return axon;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The pixel, loaded on the first grant. Axon has no consent call, so a
|
|
50
|
+
* withdrawal cannot pause what already loaded: `axonEvent` stops sending, and
|
|
51
|
+
* the next page does not load it.
|
|
52
|
+
*/
|
|
53
|
+
export function axonPixel(eventKey: string): ConsentGated {
|
|
54
|
+
let loaded = false;
|
|
55
|
+
return {
|
|
56
|
+
grant: () => {
|
|
57
|
+
if (loaded) return;
|
|
58
|
+
loaded = true;
|
|
59
|
+
const axon = defineAxon(eventKey);
|
|
60
|
+
axon("init");
|
|
61
|
+
axon("track", "page_view");
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Internal to `AxonPixel.astro`. */
|
|
67
|
+
export const axonPixelElement = element(
|
|
68
|
+
"atlas-axon-pixel",
|
|
69
|
+
({ root, signal }) => {
|
|
70
|
+
whenGranted("marketing", axonPixel(data(root, "event-key")), signal);
|
|
71
|
+
}
|
|
72
|
+
);
|
package/src/astro/client.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export * from "./attribution.ts";
|
|
13
|
+
export * from "./axon-event.ts";
|
|
13
14
|
export * from "./background-video.ts";
|
|
14
15
|
export * from "./carousel.ts";
|
|
15
16
|
// Reading only: a form that reports what the visitor agreed to. Recording an
|
|
@@ -25,6 +26,7 @@ export * from "./filters.ts";
|
|
|
25
26
|
export * from "./filters-view.ts";
|
|
26
27
|
export * from "./meta-event.ts";
|
|
27
28
|
export { data, ref, refs } from "./ref.ts";
|
|
29
|
+
export * from "./snap-event.ts";
|
|
28
30
|
export * from "./tiktok-event.ts";
|
|
29
31
|
export * from "./umami-event.ts";
|
|
30
32
|
export * from "./youtube.ts";
|
package/src/astro/meta-pixel.ts
CHANGED
|
@@ -25,11 +25,15 @@ const SCRIPT = "https://connect.facebook.net/en_US/fbevents.js";
|
|
|
25
25
|
function defineFbq(): Fbq {
|
|
26
26
|
const global = window as unknown as PixelWindow;
|
|
27
27
|
if (global.fbq !== undefined) return global.fbq;
|
|
28
|
+
// `arguments` is the list of whatever a call passed, which every regular
|
|
29
|
+
// `function` gets without declaring it (an arrow function does not). The
|
|
30
|
+
// queue stores that object rather than an array because Meta's official
|
|
31
|
+
// snippet does: it is the shape Meta promises its script will replay.
|
|
28
32
|
const fbq = function () {
|
|
29
|
-
// biome-ignore lint/complexity/noArguments:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
else fbq.queue.push(
|
|
33
|
+
// biome-ignore lint/complexity/noArguments: see above.
|
|
34
|
+
const args = arguments;
|
|
35
|
+
if (fbq.callMethod) Reflect.apply(fbq.callMethod, fbq, args);
|
|
36
|
+
else fbq.queue.push(args);
|
|
33
37
|
} as Fbq;
|
|
34
38
|
fbq.push = fbq;
|
|
35
39
|
fbq.loaded = true;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { CurrencyCode } from "../money.ts";
|
|
2
|
+
import { readConsent } from "./consent.ts";
|
|
3
|
+
|
|
4
|
+
/** Snap's standard events that a website sends. */
|
|
5
|
+
export type SnapStandardEvent =
|
|
6
|
+
| "VIEW_CONTENT"
|
|
7
|
+
| "LIST_VIEW"
|
|
8
|
+
| "SEARCH"
|
|
9
|
+
| "ADD_CART"
|
|
10
|
+
| "ADD_TO_WISHLIST"
|
|
11
|
+
| "START_CHECKOUT"
|
|
12
|
+
| "ADD_BILLING"
|
|
13
|
+
| "PURCHASE"
|
|
14
|
+
| "SIGN_UP"
|
|
15
|
+
| "SUBSCRIBE"
|
|
16
|
+
| "LOGIN"
|
|
17
|
+
| "SAVE"
|
|
18
|
+
| "SHARE"
|
|
19
|
+
| "RESERVE"
|
|
20
|
+
| "START_TRIAL";
|
|
21
|
+
|
|
22
|
+
/** Snap's standard parameters. What the visitor chose, never who they are. */
|
|
23
|
+
export interface SnapEventData {
|
|
24
|
+
readonly price?: number;
|
|
25
|
+
readonly currency?: CurrencyCode;
|
|
26
|
+
readonly item_ids?: readonly string[];
|
|
27
|
+
readonly item_category?: string;
|
|
28
|
+
readonly number_items?: number;
|
|
29
|
+
readonly transaction_id?: string;
|
|
30
|
+
readonly search_string?: string;
|
|
31
|
+
readonly description?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `PURCHASE` needs a price and currency for Snap to report a return on spend. */
|
|
35
|
+
type SnapEventArgs<E extends SnapStandardEvent> = E extends "PURCHASE"
|
|
36
|
+
? [data: SnapEventData & { price: number; currency: CurrencyCode }]
|
|
37
|
+
: [data?: SnapEventData];
|
|
38
|
+
|
|
39
|
+
/** What `snap-pixel.ts` defines once marketing is granted. */
|
|
40
|
+
interface SnapWindow {
|
|
41
|
+
snaptr?: (...args: unknown[]) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Records a standard event with Snap's pixel — a lead sent, a booking made.
|
|
46
|
+
*
|
|
47
|
+
* Dropped unless the pixel loaded and marketing is still granted: Snap has no
|
|
48
|
+
* consent call of its own, so the check is here. An analytics call must never
|
|
49
|
+
* be the reason a page misbehaves, so a dropped event is silent.
|
|
50
|
+
*/
|
|
51
|
+
export function snapEvent<E extends SnapStandardEvent>(
|
|
52
|
+
event: E,
|
|
53
|
+
...[data]: SnapEventArgs<E>
|
|
54
|
+
): void {
|
|
55
|
+
if (readConsent()?.marketing !== "granted") return;
|
|
56
|
+
const snaptr = (window as unknown as SnapWindow).snaptr;
|
|
57
|
+
if (data === undefined) snaptr?.("track", event);
|
|
58
|
+
else snaptr?.("track", event, data);
|
|
59
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { appendScript } from "./append-script.ts";
|
|
2
|
+
import type { ConsentGated } from "./consent-gate.ts";
|
|
3
|
+
import { whenGranted } from "./consent-gate.ts";
|
|
4
|
+
import { element } from "./element.ts";
|
|
5
|
+
import { data } from "./ref.ts";
|
|
6
|
+
|
|
7
|
+
/** Snap's `snaptr`: a queue until `scevent.min.js` arrives and replays it. */
|
|
8
|
+
interface Snaptr {
|
|
9
|
+
(...args: unknown[]): void;
|
|
10
|
+
handleRequest?: (...args: unknown[]) => void;
|
|
11
|
+
queue: unknown[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface PixelWindow {
|
|
15
|
+
snaptr?: Snaptr;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const SCRIPT = "https://sc-static.net/scevent.min.js";
|
|
19
|
+
|
|
20
|
+
/** Snap's own base code, as it defines `snaptr` before the script loads. */
|
|
21
|
+
function defineSnaptr(): Snaptr {
|
|
22
|
+
const global = window as unknown as PixelWindow;
|
|
23
|
+
if (global.snaptr !== undefined) return global.snaptr;
|
|
24
|
+
// `arguments` is the list of whatever a call passed, which every regular
|
|
25
|
+
// `function` gets without declaring it (an arrow function does not). The
|
|
26
|
+
// queue stores that object rather than an array because Snap's official
|
|
27
|
+
// snippet does: it is the shape Snap promises its script will replay.
|
|
28
|
+
const snaptr = function () {
|
|
29
|
+
// biome-ignore lint/complexity/noArguments: see above.
|
|
30
|
+
const args = arguments;
|
|
31
|
+
if (snaptr.handleRequest)
|
|
32
|
+
Reflect.apply(snaptr.handleRequest, snaptr, args);
|
|
33
|
+
else snaptr.queue.push(args);
|
|
34
|
+
} as Snaptr;
|
|
35
|
+
snaptr.queue = [];
|
|
36
|
+
global.snaptr = snaptr;
|
|
37
|
+
return snaptr;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The pixel, loaded on the first grant. Snap has no consent call, so a
|
|
42
|
+
* withdrawal cannot pause what already loaded: `snapEvent` stops sending, and
|
|
43
|
+
* the next page does not load it.
|
|
44
|
+
*/
|
|
45
|
+
export function snapPixel(pixelIds: readonly string[]): ConsentGated {
|
|
46
|
+
let loaded = false;
|
|
47
|
+
return {
|
|
48
|
+
grant: () => {
|
|
49
|
+
if (loaded) return;
|
|
50
|
+
loaded = true;
|
|
51
|
+
const snaptr = defineSnaptr();
|
|
52
|
+
for (const id of pixelIds) snaptr("init", id);
|
|
53
|
+
snaptr("track", "PAGE_VIEW");
|
|
54
|
+
appendScript(SCRIPT);
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Internal to `SnapPixel.astro`. */
|
|
60
|
+
export const snapPixelElement = element(
|
|
61
|
+
"atlas-snap-pixel",
|
|
62
|
+
({ root, signal }) => {
|
|
63
|
+
whenGranted(
|
|
64
|
+
"marketing",
|
|
65
|
+
snapPixel(JSON.parse(data(root, "pixel-ids"))),
|
|
66
|
+
signal
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ export {
|
|
|
22
22
|
export {
|
|
23
23
|
type AnalyticsSettings,
|
|
24
24
|
type AnalyticsTags,
|
|
25
|
+
type AxonPixelSettings,
|
|
25
26
|
CONSENT_UPDATE_GLOBAL,
|
|
26
27
|
type ConsentDefaults,
|
|
27
28
|
type ConsentState,
|
|
@@ -29,6 +30,7 @@ export {
|
|
|
29
30
|
consentVendors,
|
|
30
31
|
type GoogleSettings,
|
|
31
32
|
type MetaPixelSettings,
|
|
33
|
+
type SnapPixelSettings,
|
|
32
34
|
type TikTokPixelSettings,
|
|
33
35
|
type UmamiReplay,
|
|
34
36
|
type UmamiSettings,
|