Skip to content

Commit e39cfed

Browse files
bkboothclaude
andauthored
docs(pixel): Game Page integration guide, CSP docs, and CDN validation (#2847)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d781c61 commit e39cfed

2 files changed

Lines changed: 253 additions & 0 deletions

File tree

packages/audience/pixel/README.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# @imtbl/pixel — Immutable Tracking Pixel
2+
3+
A drop-in JavaScript snippet that captures device signals, page views, and attribution data for Immutable's events pipeline. Use it to measure campaign performance and attribute player acquisition across your marketing sites, landing pages, and web shops. Zero configuration beyond a publishable key.
4+
5+
## Quick Start
6+
7+
Paste this snippet into your site's `<head>` tag:
8+
9+
```html
10+
<script>
11+
(function(){
12+
var w=window,i="__imtbl";
13+
w[i]=w[i]||[];
14+
w[i].push(["init",{"key":"YOUR_PUBLISHABLE_KEY"}]);
15+
var s=document.createElement("script");s.async=1;
16+
s.src="https://cdn.immutable.com/pixel/v1/imtbl.js";
17+
document.head.appendChild(s);
18+
})();
19+
</script>
20+
```
21+
22+
Replace `YOUR_PUBLISHABLE_KEY` with your project's publishable key from [Immutable Hub](https://hub.immutable.com/).
23+
24+
The script loads asynchronously and does not block page rendering. The default consent level is `none` — the pixel loads but does not collect until consent is explicitly set (see [Consent Modes](#consent-modes)). To start collecting anonymous device signals immediately, add `"consent":"anonymous"` to the init options:
25+
26+
```diff
27+
- w[i].push(["init",{"key":"YOUR_PUBLISHABLE_KEY"}]);
28+
+ w[i].push(["init",{"key":"YOUR_PUBLISHABLE_KEY","consent":"anonymous"}]);
29+
```
30+
31+
## Consent Modes
32+
33+
The `consent` option controls what the pixel collects. **Default is `none`** (no events fire until consent is set).
34+
35+
| Level | What's collected | Cookies set | Use case |
36+
|-------|-----------------|-------------|----------|
37+
| `none` | Nothing — pixel loads but is inert | None | Before consent banner interaction |
38+
| `anonymous` | Device signals, attribution, page views, form submissions, link clicks (no PII) | `imtbl_anon_id`, `_imtbl_sid` | Anonymous analytics without PII |
39+
| `full` | Everything in `anonymous` + hashed email capture from form submissions (for identity matching) | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent for marketing/ads |
40+
41+
### Automatic consent detection
42+
43+
If your site uses a Consent Management Platform (CMP), the pixel can auto-detect consent state. Set `consentMode` to `'auto'` instead of setting `consent` directly:
44+
45+
```diff
46+
- w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]);
47+
+ w[i].push(["init",{"key":"YOUR_KEY","consentMode":"auto"}]);
48+
```
49+
50+
> **Note:** `consentMode` and `consent` are mutually exclusive — do not set both.
51+
52+
The pixel starts in `none` and checks for these CMP standards (in priority order):
53+
54+
1. [**Google Consent Mode v2**](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced) — reads `analytics_storage` and `ad_storage` from `window.dataLayer`
55+
2. [**IAB TCF v2**](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md) — reads purpose consents via `window.__tcfapi`
56+
57+
Once a CMP is detected, the pixel upgrades consent automatically and continues listening for changes (e.g. when a user updates their cookie preferences). If no CMP is detected after ~2.5 seconds, the pixel remains in `none` silently (there is no failure callback). If your CMP may not be present on every page, push a manual fallback on your own timeout:
58+
59+
```javascript
60+
setTimeout(function() {
61+
window.__imtbl.push(['consent', 'anonymous']);
62+
}, 3000);
63+
```
64+
65+
### Updating consent at runtime
66+
67+
If you are not using `consentMode: 'auto'`, you can set consent manually at any time:
68+
69+
```javascript
70+
// After cookie banner interaction — upgrade to full
71+
window.__imtbl.push(['consent', 'full']);
72+
73+
// Or downgrade (purges PII from queue)
74+
window.__imtbl.push(['consent', 'none']);
75+
```
76+
77+
## Auto-Tracked Events
78+
79+
All events fire automatically with no instrumentation required.
80+
81+
| Event | When it fires | Key properties |
82+
|-------|--------------|----------------|
83+
| `page` | Every page load | UTMs, click IDs (`gclid`, `fbclid`, `ttclid`, `msclkid`, `dclid`, `li_fat_id`), `referral_code`, `landing_page` |
84+
| `session_start` | New session (no active `_imtbl_sid` cookie) | `sessionId` |
85+
| `session_end` | Page unload (`visibilitychange` / `pagehide`) | `sessionId`, `duration` (seconds) |
86+
| `form_submitted` | HTML form submission | `formAction`, `formId`, `formName`, `fieldNames`. `emailHash` at `full` consent only. |
87+
| `link_clicked` | Outbound link click (external domains only) | `linkUrl`, `linkText`, `elementId`, `outbound: true` |
88+
89+
### Disabling specific auto-capture
90+
91+
```html
92+
<script>
93+
(function(){
94+
var w=window,i="__imtbl";
95+
w[i]=w[i]||[];
96+
w[i].push(["init",{
97+
"key":"YOUR_KEY",
98+
"consent":"anonymous",
99+
"autocapture":{"forms":false,"clicks":true}
100+
}]);
101+
var s=document.createElement("script");s.async=1;
102+
s.src="https://cdn.immutable.com/pixel/v1/imtbl.js";
103+
document.head.appendChild(s);
104+
})();
105+
</script>
106+
```
107+
108+
## Cookies
109+
110+
| Cookie | Lifetime | Purpose |
111+
|--------|----------|---------|
112+
| `imtbl_anon_id` | 2 years | Anonymous device ID (shared with web SDK) |
113+
| `_imtbl_sid` | 30 minutes (rolling) | Session ID — resets on inactivity |
114+
115+
Both cookies are first-party (`SameSite=Lax`, `Secure` on HTTPS).
116+
117+
## Content Security Policy (CSP)
118+
119+
If your site uses a Content-Security-Policy header, add these origins to the relevant directives:
120+
121+
```
122+
script-src ... https://cdn.immutable.com;
123+
connect-src ... https://api.immutable.com;
124+
```
125+
126+
These must be added alongside your existing policy values, not replace them.
127+
128+
For nonce-based CSP, add the nonce to the inline `<script>` tag in the snippet:
129+
130+
```html
131+
<script nonce="YOUR_NONCE">
132+
(function(){
133+
var w=window,i="__imtbl";
134+
w[i]=w[i]||[];
135+
w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]);
136+
var s=document.createElement("script");s.async=1;
137+
s.src="https://cdn.immutable.com/pixel/v1/imtbl.js";
138+
document.head.appendChild(s);
139+
})();
140+
</script>
141+
```
142+
143+
Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js`) is covered by the `script-src https://cdn.immutable.com` directive.
144+
145+
## Browser Support
146+
147+
| Browser | Minimum Version |
148+
|---------|----------------|
149+
| Chrome | 80+ |
150+
| Firefox | 78+ |
151+
| Safari | 14+ |
152+
| Edge | 80+ |
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env bash
2+
#
3+
# Validate the pixel CDN bundle is deployed and within budget.
4+
#
5+
# Usage:
6+
# ./scripts/validate-cdn.sh [URL]
7+
#
8+
# Defaults to the production CDN URL if no argument is provided.
9+
10+
set -euo pipefail
11+
12+
CDN_URL="${1:-https://cdn.immutable.com/pixel/v1/imtbl.js}"
13+
MAX_GZIP_BYTES=10240 # 10 KB — must match bundlebudget.json
14+
WARN_GZIP_BYTES=8192 # 8 KB
15+
16+
PASS=0
17+
FAIL=0
18+
19+
TMPFILE=$(mktemp)
20+
TMPHEADERS=$(mktemp)
21+
trap 'rm -f "$TMPFILE" "$TMPHEADERS"' EXIT
22+
23+
pass() { echo "$1"; PASS=$((PASS + 1)); }
24+
fail() { echo "$1"; FAIL=$((FAIL + 1)); }
25+
26+
echo "Validating pixel bundle: $CDN_URL"
27+
echo ""
28+
29+
# --- Fetch the bundle (single request for body + headers) ---
30+
HTTP_CODE=$(curl -s --connect-timeout 10 --max-time 30 -D "$TMPHEADERS" -o "$TMPFILE" -w '%{http_code}' "$CDN_URL")
31+
CONTENT_TYPE=$(grep -i '^content-type:' "$TMPHEADERS" | tr -d '\r' | awk '{print $2}')
32+
33+
# --- HTTP status ---
34+
echo "HTTP Response:"
35+
if [ "$HTTP_CODE" = "200" ]; then
36+
pass "Status: $HTTP_CODE"
37+
else
38+
fail "Status: $HTTP_CODE (expected 200)"
39+
fi
40+
41+
# --- Content-Type ---
42+
if echo "$CONTENT_TYPE" | grep -qi 'javascript'; then
43+
pass "Content-Type: $CONTENT_TYPE"
44+
else
45+
fail "Content-Type: $CONTENT_TYPE (expected application/javascript)"
46+
fi
47+
48+
# --- Bundle size ---
49+
RAW_BYTES=$(wc -c < "$TMPFILE" | tr -d ' ')
50+
GZIP_BYTES=$(gzip -c "$TMPFILE" | wc -c | tr -d ' ')
51+
52+
echo ""
53+
echo "Bundle Size:"
54+
echo " Raw: $RAW_BYTES bytes ($(awk -v b="$RAW_BYTES" 'BEGIN{printf "%.1f", b/1024}') KB)"
55+
echo " Gzip: $GZIP_BYTES bytes ($(awk -v b="$GZIP_BYTES" 'BEGIN{printf "%.1f", b/1024}') KB)"
56+
57+
if [ "$GZIP_BYTES" -le "$MAX_GZIP_BYTES" ]; then
58+
if [ "$GZIP_BYTES" -le "$WARN_GZIP_BYTES" ]; then
59+
pass "Under budget ($GZIP_BYTES / $MAX_GZIP_BYTES bytes gzipped)"
60+
else
61+
pass "Under max budget but above warning threshold ($GZIP_BYTES / $WARN_GZIP_BYTES warn, $MAX_GZIP_BYTES max)"
62+
fi
63+
else
64+
fail "Over budget! $GZIP_BYTES bytes gzipped exceeds $MAX_GZIP_BYTES limit"
65+
fi
66+
67+
# --- Content markers ---
68+
# These patterns are chosen to avoid false positives in minified code.
69+
echo ""
70+
echo "Content Checks:"
71+
if grep -q '__imtbl' "$TMPFILE"; then
72+
pass "Contains __imtbl global"
73+
else
74+
fail "Missing __imtbl global"
75+
fi
76+
77+
if grep -q '"pixel"' "$TMPFILE" || grep -q "'pixel'" "$TMPFILE"; then
78+
pass "Contains 'pixel' surface string literal"
79+
else
80+
fail "Missing 'pixel' surface string literal"
81+
fi
82+
83+
if grep -q 'session_start' "$TMPFILE"; then
84+
pass "Contains session_start event"
85+
else
86+
fail "Missing session_start event"
87+
fi
88+
89+
if grep -q 'form_submitted' "$TMPFILE"; then
90+
pass "Contains form_submitted event"
91+
else
92+
fail "Missing form_submitted event"
93+
fi
94+
95+
# --- Summary ---
96+
echo ""
97+
echo "Results: $PASS passed, $FAIL failed"
98+
99+
if [ "$FAIL" -gt 0 ]; then
100+
exit 1
101+
fi

0 commit comments

Comments
 (0)