@escape-game-over/atlas 0.1.43 → 0.1.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/analytics/google.ts +33 -25
- package/src/astro/attribution.ts +2 -1
- package/src/astro/consent.ts +12 -2
- package/src/astro/remember-campaign.ts +12 -1
- package/src/index.ts +1 -0
package/package.json
CHANGED
package/src/analytics/google.ts
CHANGED
|
@@ -345,7 +345,7 @@ export function googleEmits(
|
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
/**
|
|
348
|
-
* Google's tags: a connection and one inline block
|
|
348
|
+
* Google's tags: a connection and one inline block in the head,
|
|
349
349
|
* Tag Manager's `<noscript>` fallback in the body.
|
|
350
350
|
*
|
|
351
351
|
* One block rather than several because `dataLayer` is a queue — consent, the
|
|
@@ -424,6 +424,7 @@ export function googleScripts(
|
|
|
424
424
|
: "",
|
|
425
425
|
tags.length > 0 ? "gtag('js',new Date());" : "",
|
|
426
426
|
...tags.map((id) => `gtag('config',${literal(id)});`),
|
|
427
|
+
loader(tags[0]),
|
|
427
428
|
// Tag Manager's own loader, once per container. It appends its script
|
|
428
429
|
// itself, so it runs after the consent calls already queued above.
|
|
429
430
|
...containers.map(
|
|
@@ -432,35 +433,14 @@ export function googleScripts(
|
|
|
432
433
|
),
|
|
433
434
|
].join("");
|
|
434
435
|
|
|
435
|
-
// The library is fetched once, for the first id, and every id gets its own
|
|
436
|
-
// `config` above. That is Google's documented arrangement rather than a
|
|
437
|
-
// shortcut: *"A single Google tag can have multiple tag IDs"*, and their
|
|
438
|
-
// own example loads `gtag/js?id=G-XXXXXX` once and then configures
|
|
439
|
-
// `GT-XXXXXX` and `DC-ZZZZZZ` against it. The `?id=` only bootstraps the
|
|
440
|
-
// library; the `config` calls are what register a destination.
|
|
441
|
-
//
|
|
442
|
-
// Loading it per id would fetch the same script several times and re-run
|
|
443
|
-
// its bootstrap — more bytes for nothing, and a second copy of a global.
|
|
444
|
-
//
|
|
445
|
-
// <https://developers.google.com/tag-platform/gtagjs/configure>
|
|
446
|
-
const first = tags[0];
|
|
447
436
|
return {
|
|
448
437
|
head: [
|
|
449
438
|
// Ahead of the inline block, which is the only position that buys
|
|
450
|
-
// anything:
|
|
451
|
-
//
|
|
452
|
-
//
|
|
439
|
+
// anything: both loaders' URLs are written *by* that block, so this
|
|
440
|
+
// is the only mention of the origin the browser can act on before
|
|
441
|
+
// the script has run.
|
|
453
442
|
preconnect(TAG_ORIGIN),
|
|
454
443
|
{ kind: "script", content: inline },
|
|
455
|
-
...(first === undefined
|
|
456
|
-
? []
|
|
457
|
-
: [
|
|
458
|
-
{
|
|
459
|
-
kind: "externalScript" as const,
|
|
460
|
-
src: `${TAG_ORIGIN}/gtag/js?id=${encodeURIComponent(first)}`,
|
|
461
|
-
attrs: {},
|
|
462
|
-
},
|
|
463
|
-
]),
|
|
464
444
|
],
|
|
465
445
|
// One per container, and only for Tag Manager: GA4 has no such
|
|
466
446
|
// fallback, because `gtag.js` is the only way it collects anything.
|
|
@@ -470,3 +450,31 @@ export function googleScripts(
|
|
|
470
450
|
})),
|
|
471
451
|
};
|
|
472
452
|
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* `gtag.js`, appended once the page has loaded.
|
|
456
|
+
*
|
|
457
|
+
* Its parse and first run — including a forced reflow of its own — otherwise
|
|
458
|
+
* compete with the page for the load window. Nothing is lost by waiting:
|
|
459
|
+
* `dataLayer` is a queue, and every call above is replayed when it arrives.
|
|
460
|
+
* What waiting costs is a visitor who leaves before `load`, who is not counted.
|
|
461
|
+
*
|
|
462
|
+
* The library is fetched once, for the first id, and every id gets its own
|
|
463
|
+
* `config` above. That is Google's documented arrangement rather than a
|
|
464
|
+
* shortcut: *"A single Google tag can have multiple tag IDs"*, and their
|
|
465
|
+
* own example loads `gtag/js?id=G-XXXXXX` once and then configures
|
|
466
|
+
* `GT-XXXXXX` and `DC-ZZZZZZ` against it. The `?id=` only bootstraps the
|
|
467
|
+
* library; the `config` calls are what register a destination.
|
|
468
|
+
*
|
|
469
|
+
* Loading it per id would fetch the same script several times and re-run
|
|
470
|
+
* its bootstrap — more bytes for nothing, and a second copy of a global.
|
|
471
|
+
*
|
|
472
|
+
* <https://developers.google.com/tag-platform/gtagjs/configure>
|
|
473
|
+
*/
|
|
474
|
+
function loader(first: string | undefined): string {
|
|
475
|
+
if (first === undefined) return "";
|
|
476
|
+
const src = literal(
|
|
477
|
+
`${TAG_ORIGIN}/gtag/js?id=${encodeURIComponent(first)}`
|
|
478
|
+
);
|
|
479
|
+
return `(function(){function l(){var s=document.createElement('script');s.async=true;s.src=${src};document.head.appendChild(s)}document.readyState==='complete'?l():addEventListener('load',l)})();`;
|
|
480
|
+
}
|
package/src/astro/attribution.ts
CHANGED
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* - **Campaign tags** (`utm_*`) are labels a campaign shares with everyone who
|
|
12
12
|
* clicked it, not an identifier. They are kept for the tab in
|
|
13
13
|
* `sessionStorage`, so a visitor who lands on one page and fills a form on
|
|
14
|
-
* another still reports them. `rememberCampaign()`
|
|
14
|
+
* another still reports them. `rememberCampaign()` does that; `Document`'s
|
|
15
|
+
* `rememberCampaign` runs it on every page, with analytics consent only.
|
|
15
16
|
* - **The ad click id** (`gclid`, `gbraid`, `wbraid`) identifies one click, and
|
|
16
17
|
* is never stored here. Google keeps it under the visitor's consent: on the
|
|
17
18
|
* URL while marketing is denied — `urlPassthrough` in `GoogleSettings` — and
|
package/src/astro/consent.ts
CHANGED
|
@@ -80,8 +80,15 @@ export function readConsent(): ConsentRecord | undefined {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
83
|
+
* The event `recordConsent` fires on `window` with the new answer as `detail`,
|
|
84
|
+
* for anything on the page that waits on a category — without the banner
|
|
85
|
+
* having to know it exists.
|
|
86
|
+
*/
|
|
87
|
+
export const CONSENT_EVENT = "atlas:consent";
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remembers an answer, dated now, tells Google about it, and announces it — one
|
|
91
|
+
* call, so the three cannot come apart.
|
|
85
92
|
*/
|
|
86
93
|
export function recordConsent(choices: ConsentChoices): void {
|
|
87
94
|
const record: ConsentRecord = { ...choices, at: new Date().toISOString() };
|
|
@@ -92,6 +99,9 @@ export function recordConsent(choices: ConsentChoices): void {
|
|
|
92
99
|
// one — the choice still applies to this page.
|
|
93
100
|
}
|
|
94
101
|
applyConsent(choices);
|
|
102
|
+
window.dispatchEvent?.(
|
|
103
|
+
new CustomEvent<ConsentChoices>(CONSENT_EVENT, { detail: choices })
|
|
104
|
+
);
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
/**
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
// Every page: a visit can land anywhere before it reaches a form.
|
|
2
|
+
//
|
|
3
|
+
// Only with analytics consent. Keeping campaign tags is measurement, and EU
|
|
4
|
+
// rules ask consent for anything stored on a visitor's device that the site
|
|
5
|
+
// does not need to work — not only cookies. So the tags are kept now if the
|
|
6
|
+
// visitor already agreed, or the moment they agree on this page.
|
|
2
7
|
import { rememberCampaign } from "./attribution.ts";
|
|
8
|
+
import { CONSENT_EVENT, type ConsentChoices, readConsent } from "./consent.ts";
|
|
3
9
|
|
|
4
|
-
rememberCampaign();
|
|
10
|
+
if (readConsent()?.analytics === "granted") rememberCampaign();
|
|
11
|
+
|
|
12
|
+
addEventListener(CONSENT_EVENT, (event) => {
|
|
13
|
+
const choices = (event as CustomEvent<ConsentChoices>).detail;
|
|
14
|
+
if (choices.analytics === "granted") rememberCampaign();
|
|
15
|
+
});
|