-
Notifications
You must be signed in to change notification settings - Fork 38
docs(pixel): Game Page integration guide, CSP docs, and CDN validation #2847
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
bkbooth
wants to merge
3
commits into
main
Choose a base branch
from
claude/bold-euler
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+223
−0
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # @imtbl/pixel — Immutable Tracking Pixel | ||
|
|
||
| A drop-in JavaScript snippet that captures device signals, page views, and attribution data for Immutable's events pipeline. Zero configuration beyond a publishable key. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| Paste this snippet into your site's `<head>` tag: | ||
|
|
||
| ```html | ||
| <script> | ||
| (function(){ | ||
| var w=window,i="__imtbl"; | ||
| w[i]=w[i]||[]; | ||
| w[i].push(["init",{"key":"YOUR_PUBLISHABLE_KEY"}]); | ||
| var s=document.createElement("script");s.async=1; | ||
| s.src="https://cdn.immutable.com/pixel/v1/imtbl.js"; | ||
| document.head.appendChild(s); | ||
| })(); | ||
| </script> | ||
| ``` | ||
|
|
||
| Replace `YOUR_PUBLISHABLE_KEY` with your project's publishable key. | ||
|
|
||
| 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 object. | ||
|
|
||
| ## Consent Modes | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to include who we try to auto detect consent? and what to do if it fails? |
||
|
|
||
| The `consent` option controls what the pixel collects. **Default is `none`** (no events fire until consent is set). | ||
|
|
||
| | Level | What's collected | Cookies set | Use case | | ||
| |-------|-----------------|-------------|----------| | ||
| | `none` | Nothing — pixel loads but is inert | None | Before consent banner interaction | | ||
| | `anonymous` | Device signals, attribution, page views, form submissions, link clicks (no PII) | `imtbl_anon_id`, `_imtbl_sid` | Anonymous analytics without PII | | ||
| | `full` | Everything in `anonymous` + email hash from form submissions | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent | | ||
|
|
||
| ### Updating consent at runtime | ||
|
|
||
| ```javascript | ||
| // After cookie banner interaction — upgrade to full | ||
| window.__imtbl.push(['consent', 'full']); | ||
|
|
||
| // Or downgrade (purges PII from queue) | ||
| window.__imtbl.push(['consent', 'none']); | ||
| ``` | ||
|
|
||
| ## Auto-Tracked Events | ||
|
|
||
| All events fire automatically with no instrumentation required. | ||
|
|
||
| | Event | When it fires | Key properties | | ||
| |-------|--------------|----------------| | ||
| | `page` | Every page load | UTMs, click IDs (`gclid`, `fbclid`, `ttclid`, `msclkid`, `dclid`, `li_fat_id`), `referral_code`, `landing_page` | | ||
| | `session_start` | New session (no active `_imtbl_sid` cookie) | `sessionId` | | ||
| | `session_end` | Page unload (`visibilitychange` / `pagehide`) | `sessionId`, `duration` (seconds) | | ||
| | `form_submitted` | HTML form submission | `formAction`, `formId`, `formName`, `fieldNames`. `emailHash` at `full` consent only. | | ||
| | `link_clicked` | Outbound link click (external domains only) | `linkUrl`, `linkText`, `elementId`, `outbound: true` | | ||
|
|
||
| ### Disabling specific auto-capture | ||
|
|
||
| ```html | ||
| <script> | ||
| (function(){ | ||
| var w=window,i="__imtbl"; | ||
| w[i]=w[i]||[]; | ||
| w[i].push(["init",{ | ||
| "key":"YOUR_KEY", | ||
| "consent":"anonymous", | ||
| "autocapture":{"forms":false,"clicks":true} | ||
| }]); | ||
| var s=document.createElement("script");s.async=1; | ||
| s.src="https://cdn.immutable.com/pixel/v1/imtbl.js"; | ||
| document.head.appendChild(s); | ||
| })(); | ||
| </script> | ||
| ``` | ||
|
|
||
| ## Cookies | ||
|
|
||
| | Cookie | Lifetime | Purpose | | ||
| |--------|----------|---------| | ||
| | `imtbl_anon_id` | 2 years | Anonymous device ID (shared with web SDK) | | ||
| | `_imtbl_sid` | 30 minutes (rolling) | Session ID — resets on inactivity | | ||
|
|
||
| Both cookies are first-party (`SameSite=Lax`, `Secure` on HTTPS). | ||
|
|
||
| ## Content Security Policy (CSP) | ||
|
|
||
| If your site uses a Content-Security-Policy header, add these origins to the relevant directives: | ||
|
|
||
| ``` | ||
| script-src ... https://cdn.immutable.com; | ||
| connect-src ... https://api.immutable.com; | ||
| ``` | ||
|
|
||
| These must be added alongside your existing policy values, not replace them. | ||
|
|
||
| For nonce-based CSP, add the nonce to the inline `<script>` tag in the snippet: | ||
|
|
||
| ```html | ||
| <script nonce="YOUR_NONCE"> | ||
| (function(){ | ||
| var w=window,i="__imtbl"; | ||
| w[i]=w[i]||[]; | ||
| w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]); | ||
| var s=document.createElement("script");s.async=1; | ||
| s.src="https://cdn.immutable.com/pixel/v1/imtbl.js"; | ||
| document.head.appendChild(s); | ||
| })(); | ||
| </script> | ||
| ``` | ||
|
|
||
| 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. | ||
|
|
||
| ## Browser Support | ||
|
|
||
| | Browser | Minimum Version | | ||
| |---------|----------------| | ||
| | Chrome | 80+ | | ||
| | Firefox | 78+ | | ||
| | Safari | 14+ | | ||
| | Edge | 80+ | | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # Validate the pixel CDN bundle is deployed and within budget. | ||
| # | ||
| # Usage: | ||
| # ./scripts/validate-cdn.sh [URL] | ||
| # | ||
| # Defaults to the production CDN URL if no argument is provided. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| CDN_URL="${1:-https://cdn.immutable.com/pixel/v1/imtbl.js}" | ||
| MAX_GZIP_BYTES=10240 # 10 KB — must match bundlebudget.json | ||
| WARN_GZIP_BYTES=8192 # 8 KB | ||
|
|
||
| PASS=0 | ||
| FAIL=0 | ||
|
|
||
| TMPFILE=$(mktemp) | ||
| TMPHEADERS=$(mktemp) | ||
| trap 'rm -f "$TMPFILE" "$TMPHEADERS"' EXIT | ||
|
|
||
| pass() { echo " ✓ $1"; PASS=$((PASS + 1)); } | ||
| fail() { echo " ✗ $1"; FAIL=$((FAIL + 1)); } | ||
|
|
||
| echo "Validating pixel bundle: $CDN_URL" | ||
| echo "" | ||
|
|
||
| # --- Fetch the bundle (single request for body + headers) --- | ||
| HTTP_CODE=$(curl -s --connect-timeout 10 --max-time 30 -D "$TMPHEADERS" -o "$TMPFILE" -w '%{http_code}' "$CDN_URL") | ||
| CONTENT_TYPE=$(grep -i '^content-type:' "$TMPHEADERS" | tr -d '\r' | awk '{print $2}') | ||
|
|
||
| # --- HTTP status --- | ||
| echo "HTTP Response:" | ||
| if [ "$HTTP_CODE" = "200" ]; then | ||
| pass "Status: $HTTP_CODE" | ||
| else | ||
| fail "Status: $HTTP_CODE (expected 200)" | ||
| fi | ||
|
|
||
| # --- Content-Type --- | ||
| if echo "$CONTENT_TYPE" | grep -qi 'javascript'; then | ||
| pass "Content-Type: $CONTENT_TYPE" | ||
| else | ||
| fail "Content-Type: $CONTENT_TYPE (expected application/javascript)" | ||
| fi | ||
|
|
||
| # --- Bundle size --- | ||
| RAW_BYTES=$(wc -c < "$TMPFILE" | tr -d ' ') | ||
| GZIP_BYTES=$(gzip -c "$TMPFILE" | wc -c | tr -d ' ') | ||
|
|
||
| echo "" | ||
| echo "Bundle Size:" | ||
| echo " Raw: $RAW_BYTES bytes ($(awk -v b="$RAW_BYTES" 'BEGIN{printf "%.1f", b/1024}') KB)" | ||
| echo " Gzip: $GZIP_BYTES bytes ($(awk -v b="$GZIP_BYTES" 'BEGIN{printf "%.1f", b/1024}') KB)" | ||
|
|
||
| if [ "$GZIP_BYTES" -le "$MAX_GZIP_BYTES" ]; then | ||
| if [ "$GZIP_BYTES" -le "$WARN_GZIP_BYTES" ]; then | ||
| pass "Under budget ($GZIP_BYTES / $MAX_GZIP_BYTES bytes gzipped)" | ||
| else | ||
| pass "Under max budget but above warning threshold ($GZIP_BYTES / $WARN_GZIP_BYTES warn, $MAX_GZIP_BYTES max)" | ||
| fi | ||
| else | ||
| fail "Over budget! $GZIP_BYTES bytes gzipped exceeds $MAX_GZIP_BYTES limit" | ||
| fi | ||
|
|
||
| # --- Content markers --- | ||
| # These patterns are chosen to avoid false positives in minified code. | ||
| echo "" | ||
| echo "Content Checks:" | ||
| if grep -q '__imtbl' "$TMPFILE"; then | ||
| pass "Contains __imtbl global" | ||
| else | ||
| fail "Missing __imtbl global" | ||
| fi | ||
|
|
||
| if grep -q '"pixel"' "$TMPFILE" || grep -q "'pixel'" "$TMPFILE"; then | ||
| pass "Contains 'pixel' surface string literal" | ||
| else | ||
| fail "Missing 'pixel' surface string literal" | ||
| fi | ||
|
|
||
| if grep -q 'session_start' "$TMPFILE"; then | ||
| pass "Contains session_start event" | ||
| else | ||
| fail "Missing session_start event" | ||
| fi | ||
|
|
||
| if grep -q 'form_submitted' "$TMPFILE"; then | ||
| pass "Contains form_submitted event" | ||
| else | ||
| fail "Missing form_submitted event" | ||
| fi | ||
|
|
||
| # --- Summary --- | ||
| echo "" | ||
| echo "Results: $PASS passed, $FAIL failed" | ||
|
|
||
| if [ "$FAIL" -gt 0 ]; then | ||
| exit 1 | ||
| fi |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mention that you need to get it from Hub?