@escape-game-over/atlas 0.1.59 → 0.1.61
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 +6 -1
- package/src/analytics/clarity.ts +29 -0
- package/src/analytics/drip.ts +5 -1
- package/src/analytics/index.ts +8 -0
- package/src/analytics/meta-pixel.ts +7 -1
- package/src/analytics/snap-pixel.ts +6 -1
- package/src/analytics/tiktok-pixel.ts +7 -1
- package/src/astro/ConsentBanner.astro +19 -16
- package/src/astro/ConsentElement.astro +8 -2
- package/src/astro/GoogleMap.astro +5 -0
- package/src/astro/axon-pixel.ts +0 -11
- package/src/astro/clarity-event.ts +15 -0
- package/src/astro/clarity.ts +53 -0
- package/src/astro/client.ts +1 -0
- package/src/astro/consent-element.ts +3 -0
- package/src/astro/consent-gate.ts +21 -0
- package/src/astro/consent-vendors.ts +100 -0
- package/src/astro/drip.ts +0 -8
- package/src/astro/meta-pixel.ts +0 -15
- package/src/astro/snap-pixel.ts +0 -15
- package/src/astro/tiktok-pixel.ts +0 -15
- package/src/index.ts +1 -0
- package/src/astro/AxonPixel.astro +0 -18
- package/src/astro/Drip.astro +0 -18
- package/src/astro/MetaPixel.astro +0 -18
- package/src/astro/SnapPixel.astro +0 -18
- package/src/astro/TikTokPixel.astro +0 -18
package/package.json
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { warn } from "../warn.ts";
|
|
2
2
|
import { UUID } from "./snap-pixel.ts";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* AppLovin's Axon pixel. Marketing: it loads only once a visitor grants that.
|
|
6
|
+
*
|
|
7
|
+
* @see https://support.applovin.com/en/growth/promoting-your-websites/axon-pixel-integration/axon-pixel-native-js
|
|
8
|
+
* @see https://support.applovin.com/en/growth/promoting-your-websites/axon-pixel-integration/events-and-objects
|
|
9
|
+
*/
|
|
5
10
|
export interface AxonPixelSettings {
|
|
6
11
|
/** From the Axon dashboard: a UUID. */
|
|
7
12
|
readonly eventKey: string;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { warn } from "../warn.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
|
+
/** Warns about 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 || /^[a-z0-9]{10}$/.test(clarity.projectId)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
warn(
|
|
26
|
+
at,
|
|
27
|
+
`Clarity project id "${clarity.projectId}" is not 10 lowercase letters and digits, so it reports nowhere. Copy it from the Clarity project's settings.`
|
|
28
|
+
);
|
|
29
|
+
}
|
package/src/analytics/drip.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { warn } from "../warn.ts";
|
|
2
2
|
|
|
3
|
-
/**
|
|
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
|
+
*/
|
|
4
8
|
export interface DripSettings {
|
|
5
9
|
/** From Drip's Site Setup: digits, the `_dcs.account` of its snippet. */
|
|
6
10
|
readonly accountId: string;
|
package/src/analytics/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { MetaTag } from "../meta/tag.ts";
|
|
2
2
|
import type { HttpsUrl } from "../url.ts";
|
|
3
3
|
import { type AxonPixelSettings, checkAxonPixel } from "./axon-pixel.ts";
|
|
4
|
+
import { type ClaritySettings, checkClarity } from "./clarity.ts";
|
|
4
5
|
import { checkDrip, type DripSettings } from "./drip.ts";
|
|
5
6
|
import { type GoogleSettings, googleEmits, googleScripts } from "./google.ts";
|
|
6
7
|
import { checkMetaPixel, type MetaPixelSettings } from "./meta-pixel.ts";
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
*/
|
|
30
31
|
|
|
31
32
|
export type { AxonPixelSettings } from "./axon-pixel.ts";
|
|
33
|
+
export type { ClaritySettings } from "./clarity.ts";
|
|
32
34
|
export type { DripSettings } from "./drip.ts";
|
|
33
35
|
export {
|
|
34
36
|
CONSENT_UPDATE_GLOBAL,
|
|
@@ -60,6 +62,8 @@ export interface AnalyticsSettings {
|
|
|
60
62
|
readonly axon?: AxonPixelSettings;
|
|
61
63
|
/** Loaded by `ConsentBanner`, after marketing is granted. */
|
|
62
64
|
readonly drip?: DripSettings;
|
|
65
|
+
/** Loaded by `ConsentBanner` for every visitor, cookieless until analytics is granted. */
|
|
66
|
+
readonly clarity?: ClaritySettings;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
/**
|
|
@@ -85,6 +89,7 @@ export function analyticsScripts(
|
|
|
85
89
|
checkSnapPixel(analytics.snapchat, origin);
|
|
86
90
|
checkAxonPixel(analytics.axon, origin);
|
|
87
91
|
checkDrip(analytics.drip, origin);
|
|
92
|
+
checkClarity(analytics.clarity, origin);
|
|
88
93
|
const google = googleScripts(analytics.google, origin);
|
|
89
94
|
return {
|
|
90
95
|
head: [...google.head, ...umamiScripts(analytics.umami)],
|
|
@@ -133,6 +138,9 @@ export function consentVendors(
|
|
|
133
138
|
axon: analytics.axon !== undefined,
|
|
134
139
|
// No consent mode at all.
|
|
135
140
|
drip: analytics.drip !== undefined,
|
|
141
|
+
// Cookieless without consent, but the answer is what switches its
|
|
142
|
+
// cookies on, so it needs the banner.
|
|
143
|
+
clarity: analytics.clarity !== undefined,
|
|
136
144
|
};
|
|
137
145
|
|
|
138
146
|
return (Object.keys(VENDORS) as (keyof AnalyticsSettings)[]).filter(
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { NonEmpty } from "../types.ts";
|
|
2
2
|
import { warn } from "../warn.ts";
|
|
3
3
|
|
|
4
|
-
/**
|
|
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
|
+
*/
|
|
5
11
|
export interface MetaPixelSettings {
|
|
6
12
|
/** From Events Manager: 15 or 16 digits. Several share one script. */
|
|
7
13
|
readonly pixelIds: NonEmpty<string>;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { NonEmpty } from "../types.ts";
|
|
2
2
|
import { warn } from "../warn.ts";
|
|
3
3
|
|
|
4
|
-
/**
|
|
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
|
+
*/
|
|
5
10
|
export interface SnapPixelSettings {
|
|
6
11
|
/** From Snap's Events Manager: a UUID. */
|
|
7
12
|
readonly pixelIds: NonEmpty<string>;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { NonEmpty } from "../types.ts";
|
|
2
2
|
import { warn } from "../warn.ts";
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* TikTok's pixel. Marketing: it loads only once a visitor grants that.
|
|
6
|
+
*
|
|
7
|
+
* @see https://ads.tiktok.com/help/article/get-started-pixel
|
|
8
|
+
* @see https://ads.tiktok.com/help/article/standard-events-parameters
|
|
9
|
+
* @see https://ads.tiktok.com/help/article/how-to-use-cookies-with-tiktok-pixel
|
|
10
|
+
*/
|
|
5
11
|
export interface TikTokPixelSettings {
|
|
6
12
|
/** From TikTok Events Manager: 20 capital letters and digits. */
|
|
7
13
|
readonly pixelIds: NonEmpty<string>;
|
|
@@ -5,34 +5,37 @@
|
|
|
5
5
|
* buttons.
|
|
6
6
|
*
|
|
7
7
|
* Renders nothing — and ships no script — when this site's analytics need no
|
|
8
|
-
* permission.
|
|
8
|
+
* permission. 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
|
+
*
|
|
11
|
+
* It also starts the vendors that wait on the answer — the Meta, TikTok, Snap
|
|
12
|
+
* and Axon pixels, Drip and Clarity — fetching each one's code only once its
|
|
13
|
+
* category is granted.
|
|
10
14
|
*/
|
|
11
15
|
import type { HTMLAttributes } from "astro/types";
|
|
12
16
|
import { type AnalyticsSettings, consentRequired } from "../analytics/index.ts";
|
|
13
|
-
import AxonPixel from "./AxonPixel.astro";
|
|
14
17
|
import ConsentElement from "./ConsentElement.astro";
|
|
15
|
-
import
|
|
16
|
-
import MetaPixel from "./MetaPixel.astro";
|
|
17
|
-
import SnapPixel from "./SnapPixel.astro";
|
|
18
|
-
import TikTokPixel from "./TikTokPixel.astro";
|
|
18
|
+
import type { ConsentVendors } from "./consent-vendors.ts";
|
|
19
19
|
|
|
20
20
|
type Props = HTMLAttributes<"div"> & {
|
|
21
21
|
readonly analytics: AnalyticsSettings | undefined;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
const { analytics, ...attrs } = Astro.props;
|
|
25
|
+
const vendors: ConsentVendors = {
|
|
26
|
+
meta: analytics?.meta,
|
|
27
|
+
tiktok: analytics?.tiktok,
|
|
28
|
+
snapchat: analytics?.snapchat,
|
|
29
|
+
axon: analytics?.axon,
|
|
30
|
+
drip: analytics?.drip,
|
|
31
|
+
clarity: analytics?.clarity,
|
|
32
|
+
};
|
|
25
33
|
---
|
|
26
34
|
|
|
27
35
|
{consentRequired(analytics) && (
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{analytics?.tiktok && <TikTokPixel settings={analytics.tiktok} />}
|
|
34
|
-
{analytics?.snapchat && <SnapPixel settings={analytics.snapchat} />}
|
|
35
|
-
{analytics?.axon && <AxonPixel settings={analytics.axon} />}
|
|
36
|
-
{analytics?.drip && <Drip settings={analytics.drip} />}
|
|
37
|
-
</>
|
|
36
|
+
<ConsentElement
|
|
37
|
+
{...attrs}
|
|
38
|
+
vendors={vendors}>
|
|
39
|
+
<slot />
|
|
40
|
+
</ConsentElement>
|
|
38
41
|
)}
|
|
@@ -3,15 +3,21 @@
|
|
|
3
3
|
import type { HTMLAttributes } from "astro/types";
|
|
4
4
|
import AtlasElement from "./AtlasElement.astro";
|
|
5
5
|
import { consentBanner } from "./consent-element.ts";
|
|
6
|
+
import type { ConsentVendors } from "./consent-vendors.ts";
|
|
6
7
|
|
|
7
|
-
type Props = HTMLAttributes<"div"
|
|
8
|
+
type Props = HTMLAttributes<"div"> & {
|
|
9
|
+
readonly vendors: ConsentVendors;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { vendors, ...attrs } = Astro.props;
|
|
8
13
|
---
|
|
9
14
|
|
|
10
15
|
<script src="./consent-element.ts" />
|
|
11
16
|
|
|
12
17
|
<AtlasElement
|
|
13
18
|
of={consentBanner}
|
|
14
|
-
{...
|
|
19
|
+
{...attrs}
|
|
20
|
+
data-vendors={JSON.stringify(vendors)}
|
|
15
21
|
hidden
|
|
16
22
|
role="dialog"
|
|
17
23
|
aria-modal="false"
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Give the element its size; the map fills it.
|
|
13
13
|
*
|
|
14
|
+
* @see https://developers.google.com/maps/documentation/javascript/load-maps-js-api
|
|
15
|
+
* @see https://developers.google.com/maps/documentation/javascript/advanced-markers/overview
|
|
16
|
+
* @see https://developers.google.com/maps/documentation/get-map-id
|
|
17
|
+
* @see https://developers.google.com/maps/billing-and-pricing/pricing
|
|
18
|
+
*
|
|
14
19
|
* ```astro
|
|
15
20
|
* <GoogleMap apiKey={key} mapId={id} colorScheme="DARK" places={[{ at, label, href }]} class="block h-96">
|
|
16
21
|
* <button type="button">Show map</button>
|
package/src/astro/axon-pixel.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
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
3
|
|
|
7
4
|
/** Axon's `axon`: a queue until its scripts arrive and replay it. */
|
|
8
5
|
interface Axon {
|
|
@@ -62,11 +59,3 @@ export function axonPixel(eventKey: string): ConsentGated {
|
|
|
62
59
|
},
|
|
63
60
|
};
|
|
64
61
|
}
|
|
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
|
-
);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** What `clarity.ts` defines once the page has loaded. */
|
|
2
|
+
interface ClarityWindow {
|
|
3
|
+
clarity?: (...args: unknown[]) => void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Marks the current Clarity recording with a named event — a form sent, a
|
|
8
|
+
* booking opened — so recordings can be filtered by it.
|
|
9
|
+
*
|
|
10
|
+
* Sent with or without consent: Clarity records either way, and without it
|
|
11
|
+
* sets no cookie. Dropped before Clarity loads, or where it is not configured.
|
|
12
|
+
*/
|
|
13
|
+
export function clarityEvent(name: string): void {
|
|
14
|
+
(window as unknown as ClarityWindow).clarity?.("event", name);
|
|
15
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { appendScript } from "./append-script.ts";
|
|
2
|
+
import type { ConsentChoices } from "./consent.ts";
|
|
3
|
+
|
|
4
|
+
/** Clarity's `clarity`: a queue until its script arrives and replays it. */
|
|
5
|
+
interface Clarity {
|
|
6
|
+
(...args: unknown[]): void;
|
|
7
|
+
q?: unknown[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ClarityWindow {
|
|
11
|
+
clarity?: Clarity;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Clarity's own base code, as it defines `clarity` before the script loads. */
|
|
15
|
+
function defineClarity(): Clarity {
|
|
16
|
+
const global = window as unknown as ClarityWindow;
|
|
17
|
+
if (global.clarity !== undefined) return global.clarity;
|
|
18
|
+
// `arguments` is the list of whatever a call passed, which every regular
|
|
19
|
+
// `function` gets without declaring it (an arrow function does not). The
|
|
20
|
+
// queue stores that object rather than an array because Clarity's official
|
|
21
|
+
// snippet does: it is the shape Clarity promises its script will replay.
|
|
22
|
+
const clarity = function () {
|
|
23
|
+
// biome-ignore lint/complexity/noArguments: see above.
|
|
24
|
+
const args = arguments;
|
|
25
|
+
clarity.q ??= [];
|
|
26
|
+
clarity.q.push(args);
|
|
27
|
+
} as Clarity;
|
|
28
|
+
global.clarity = clarity;
|
|
29
|
+
return clarity;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Clarity's consent call, in its own spelling. */
|
|
33
|
+
const consentv2 = (choices: ConsentChoices) => ({
|
|
34
|
+
ad_Storage: choices.marketing,
|
|
35
|
+
analytics_Storage: choices.analytics,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Clarity, loaded with the first answer passed to what this returns, which
|
|
40
|
+
* `startVendors` calls with the answer so far and then every new one. Stated
|
|
41
|
+
* even when denied: a Clarity project set to use cookies by default would
|
|
42
|
+
* otherwise set them before anyone was asked.
|
|
43
|
+
*/
|
|
44
|
+
export function clarity(projectId: string): (choices: ConsentChoices) => void {
|
|
45
|
+
let loaded = false;
|
|
46
|
+
return (choices) => {
|
|
47
|
+
const queue = defineClarity();
|
|
48
|
+
queue("consentv2", consentv2(choices));
|
|
49
|
+
if (loaded) return;
|
|
50
|
+
loaded = true;
|
|
51
|
+
appendScript(`https://www.clarity.ms/tag/${projectId}`);
|
|
52
|
+
};
|
|
53
|
+
}
|
package/src/astro/client.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./attribution.ts";
|
|
|
13
13
|
export * from "./axon-event.ts";
|
|
14
14
|
export * from "./background-video.ts";
|
|
15
15
|
export * from "./carousel.ts";
|
|
16
|
+
export * from "./clarity-event.ts";
|
|
16
17
|
// Reading only: a form that reports what the visitor agreed to. Recording an
|
|
17
18
|
// answer stays with the banner.
|
|
18
19
|
export { type ConsentRecord, readConsent } from "./consent.ts";
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
readConsent,
|
|
8
8
|
recordConsent,
|
|
9
9
|
} from "./consent.ts";
|
|
10
|
+
import { startVendors } from "./consent-vendors.ts";
|
|
10
11
|
import { element } from "./element.ts";
|
|
11
12
|
import { data, ref, refs } from "./ref.ts";
|
|
12
13
|
|
|
@@ -38,6 +39,8 @@ import { data, ref, refs } from "./ref.ts";
|
|
|
38
39
|
* withdraw.
|
|
39
40
|
*/
|
|
40
41
|
export const consentBanner = element(CONSENT_TAG, ({ root, signal }) => {
|
|
42
|
+
startVendors(JSON.parse(data(root, "vendors")), signal);
|
|
43
|
+
|
|
41
44
|
const boxes = refs<HTMLInputElement>(root, "[data-consent-category]").map(
|
|
42
45
|
(box) => {
|
|
43
46
|
const category = data(box, "consent-category");
|
|
@@ -46,3 +46,24 @@ export function whenGranted(
|
|
|
46
46
|
{ signal }
|
|
47
47
|
);
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* For a vendor with a cookieless mode, which loads whatever the answer: after
|
|
52
|
+
* `load`, `apply` gets the answer so far (everything denied until one is
|
|
53
|
+
* given), then every answer given on this page.
|
|
54
|
+
*/
|
|
55
|
+
export function withConsent(
|
|
56
|
+
apply: (choices: ConsentChoices) => void,
|
|
57
|
+
signal?: AbortSignal
|
|
58
|
+
): void {
|
|
59
|
+
const start = () => {
|
|
60
|
+
apply(readConsent() ?? { analytics: "denied", marketing: "denied" });
|
|
61
|
+
addEventListener(
|
|
62
|
+
CONSENT_EVENT,
|
|
63
|
+
(event) => apply((event as CustomEvent<ConsentChoices>).detail),
|
|
64
|
+
{ signal }
|
|
65
|
+
);
|
|
66
|
+
};
|
|
67
|
+
if (document.readyState === "complete") start();
|
|
68
|
+
else addEventListener("load", start, { once: true, signal });
|
|
69
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { AnalyticsSettings } from "../analytics/index.ts";
|
|
2
|
+
import type { ConsentChoices } from "./consent.ts";
|
|
3
|
+
import { type ConsentGated, whenGranted, withConsent } from "./consent-gate.ts";
|
|
4
|
+
import { reportDevError } from "./dev-log.ts";
|
|
5
|
+
|
|
6
|
+
/** The vendors the banner starts: the analytics settings loaded in the browser. */
|
|
7
|
+
export type ConsentVendors = Pick<
|
|
8
|
+
AnalyticsSettings,
|
|
9
|
+
"meta" | "tiktok" | "snapchat" | "axon" | "drip" | "clarity"
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A vendor whose code is fetched on its first grant, not with the page. Calls
|
|
14
|
+
* wait on the one import, so they reach the vendor in the order they were made.
|
|
15
|
+
*/
|
|
16
|
+
function lazy(load: () => Promise<ConsentGated>): ConsentGated {
|
|
17
|
+
let vendor: Promise<ConsentGated | undefined> | undefined;
|
|
18
|
+
const loaded = () => {
|
|
19
|
+
vendor ??= load().catch((error) => {
|
|
20
|
+
reportDevError("consent vendors", error);
|
|
21
|
+
return undefined;
|
|
22
|
+
});
|
|
23
|
+
return vendor;
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
grant: () => {
|
|
27
|
+
loaded().then((ready) => ready?.grant());
|
|
28
|
+
},
|
|
29
|
+
revoke: () => {
|
|
30
|
+
vendor?.then((ready) => ready?.revoke?.());
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Starts every configured vendor. Nothing vendor-specific is downloaded until
|
|
37
|
+
* its consent category is granted — except Clarity, which runs cookieless
|
|
38
|
+
* without consent and so loads with the page.
|
|
39
|
+
*/
|
|
40
|
+
export function startVendors(
|
|
41
|
+
vendors: ConsentVendors,
|
|
42
|
+
signal?: AbortSignal
|
|
43
|
+
): void {
|
|
44
|
+
const { meta, tiktok, snapchat, axon, drip, clarity } = vendors;
|
|
45
|
+
if (meta !== undefined) {
|
|
46
|
+
whenGranted(
|
|
47
|
+
"marketing",
|
|
48
|
+
lazy(async () =>
|
|
49
|
+
(await import("./meta-pixel.ts")).metaPixel(meta.pixelIds)
|
|
50
|
+
),
|
|
51
|
+
signal
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (tiktok !== undefined) {
|
|
55
|
+
whenGranted(
|
|
56
|
+
"marketing",
|
|
57
|
+
lazy(async () =>
|
|
58
|
+
(await import("./tiktok-pixel.ts")).tiktokPixel(tiktok.pixelIds)
|
|
59
|
+
),
|
|
60
|
+
signal
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (snapchat !== undefined) {
|
|
64
|
+
whenGranted(
|
|
65
|
+
"marketing",
|
|
66
|
+
lazy(async () =>
|
|
67
|
+
(await import("./snap-pixel.ts")).snapPixel(snapchat.pixelIds)
|
|
68
|
+
),
|
|
69
|
+
signal
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (axon !== undefined) {
|
|
73
|
+
whenGranted(
|
|
74
|
+
"marketing",
|
|
75
|
+
lazy(async () =>
|
|
76
|
+
(await import("./axon-pixel.ts")).axonPixel(axon.eventKey)
|
|
77
|
+
),
|
|
78
|
+
signal
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (drip !== undefined) {
|
|
82
|
+
whenGranted(
|
|
83
|
+
"marketing",
|
|
84
|
+
lazy(async () => (await import("./drip.ts")).drip(drip.accountId)),
|
|
85
|
+
signal
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (clarity !== undefined) {
|
|
89
|
+
let apply: Promise<((choices: ConsentChoices) => void) | undefined>;
|
|
90
|
+
withConsent((choices) => {
|
|
91
|
+
apply ??= import("./clarity.ts")
|
|
92
|
+
.then((module) => module.clarity(clarity.projectId))
|
|
93
|
+
.catch((error) => {
|
|
94
|
+
reportDevError("consent vendors", error);
|
|
95
|
+
return undefined;
|
|
96
|
+
});
|
|
97
|
+
apply.then((ready) => ready?.(choices));
|
|
98
|
+
}, signal);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/astro/drip.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
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
3
|
|
|
7
4
|
/** Drip's globals: a queue of calls, and the account its script reports to. */
|
|
8
5
|
interface DripWindow {
|
|
@@ -30,8 +27,3 @@ export function drip(accountId: string): ConsentGated {
|
|
|
30
27
|
},
|
|
31
28
|
};
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
/** Internal to `Drip.astro`. */
|
|
35
|
-
export const dripElement = element("atlas-drip", ({ root, signal }) => {
|
|
36
|
-
whenGranted("marketing", drip(data(root, "account-id")), signal);
|
|
37
|
-
});
|
package/src/astro/meta-pixel.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
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
3
|
|
|
7
4
|
/** Meta's `fbq`: a queue until `fbevents.js` arrives and replays it. */
|
|
8
5
|
interface Fbq {
|
|
@@ -64,15 +61,3 @@ export function metaPixel(pixelIds: readonly string[]): ConsentGated {
|
|
|
64
61
|
},
|
|
65
62
|
};
|
|
66
63
|
}
|
|
67
|
-
|
|
68
|
-
/** Internal to `MetaPixel.astro`. */
|
|
69
|
-
export const metaPixelElement = element(
|
|
70
|
-
"atlas-meta-pixel",
|
|
71
|
-
({ root, signal }) => {
|
|
72
|
-
whenGranted(
|
|
73
|
-
"marketing",
|
|
74
|
-
metaPixel(JSON.parse(data(root, "pixel-ids"))),
|
|
75
|
-
signal
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
);
|
package/src/astro/snap-pixel.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
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
3
|
|
|
7
4
|
/** Snap's `snaptr`: a queue until `scevent.min.js` arrives and replays it. */
|
|
8
5
|
interface Snaptr {
|
|
@@ -55,15 +52,3 @@ export function snapPixel(pixelIds: readonly string[]): ConsentGated {
|
|
|
55
52
|
},
|
|
56
53
|
};
|
|
57
54
|
}
|
|
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
|
-
);
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { appendScript } from "./append-script.ts";
|
|
2
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
3
|
|
|
7
4
|
/** TikTok's `ttq`: a queue of calls until `events.js` arrives and replays it. */
|
|
8
5
|
interface Ttq extends Array<unknown> {
|
|
@@ -98,15 +95,3 @@ export function tiktokPixel(pixelIds: readonly string[]): ConsentGated {
|
|
|
98
95
|
},
|
|
99
96
|
};
|
|
100
97
|
}
|
|
101
|
-
|
|
102
|
-
/** Internal to `TikTokPixel.astro`. */
|
|
103
|
-
export const tiktokPixelElement = element(
|
|
104
|
-
"atlas-tiktok-pixel",
|
|
105
|
-
({ root, signal }) => {
|
|
106
|
-
whenGranted(
|
|
107
|
-
"marketing",
|
|
108
|
-
tiktokPixel(JSON.parse(data(root, "pixel-ids"))),
|
|
109
|
-
signal
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
);
|
package/src/index.ts
CHANGED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
/>
|
package/src/astro/Drip.astro
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so Drip's script ships only where it is configured. */
|
|
3
|
-
import type { DripSettings } from "../analytics/drip.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { dripElement } from "./drip.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: DripSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./drip.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={dripElement}
|
|
16
|
-
data-account-id={Astro.props.settings.accountId}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
-
import type { MetaPixelSettings } from "../analytics/meta-pixel.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { metaPixelElement } from "./meta-pixel.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: MetaPixelSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./meta-pixel.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={metaPixelElement}
|
|
16
|
-
data-pixel-ids={JSON.stringify(Astro.props.settings.pixelIds)}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
/>
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/** Internal to `ConsentBanner.astro`, so the pixel's script ships only where it is configured. */
|
|
3
|
-
import type { TikTokPixelSettings } from "../analytics/tiktok-pixel.ts";
|
|
4
|
-
import AtlasElement from "./AtlasElement.astro";
|
|
5
|
-
import { tiktokPixelElement } from "./tiktok-pixel.ts";
|
|
6
|
-
|
|
7
|
-
interface Props {
|
|
8
|
-
readonly settings: TikTokPixelSettings;
|
|
9
|
-
}
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
<script src="./tiktok-pixel.ts" />
|
|
13
|
-
|
|
14
|
-
<AtlasElement
|
|
15
|
-
of={tiktokPixelElement}
|
|
16
|
-
data-pixel-ids={JSON.stringify(Astro.props.settings.pixelIds)}
|
|
17
|
-
hidden
|
|
18
|
-
/>
|