/** * First-party artifact for /answers/posthog-cookieless-analytics. * * Claim under test: posthog-js, configured the way this site configures it * (persistence 'memory' + person_profiles 'never'), writes NOTHING to the * browser's terminal equipment. No cookie, no localStorage, no sessionStorage. * That is the fact the "no consent banner" position rests on, so it is worth * proving rather than asserting: an earlier posthog-js issue (#614) reported * that "cookieless" setups still dropped a cookie, so we measure, we do not trust. * * What it does: loads the real posthog-js browser build (the exact version this * repo ships) into a real http origin driven by headless Chromium, initialises * once with this site's config and once with the library default, fires one * $pageview in each, then reads every client-side store. It prints a comparison * and exits non-zero if the cookieless config leaks any storage. * * Run it yourself (no network to PostHog required, the storage behaviour is * decided client-side): * * npm i posthog-js@1.405.3 playwright * npx playwright install chromium * node posthog-cookieless-storage-check.mjs * * Measured on posthog-js 1.405.3 with Playwright/Chromium on 2026-08-06. * Storage behaviour is version-dependent: the script reads whatever your * installed posthog-js actually does, which is the point of shipping it. */ import { createServer } from 'node:http' import { readFileSync } from 'node:fs' import { createRequire } from 'node:module' import { chromium } from 'playwright' const require = createRequire(import.meta.url) // The standalone browser build that defines window.posthog. Resolved from the // installed package so the script tests the version you actually depend on. const pkgJson = require.resolve('posthog-js/package.json') const arrayBuild = pkgJson.replace(/package\.json$/, 'dist/array.full.js') const posthogSource = readFileSync(arrayBuild, 'utf-8') // This site's configuration, verbatim from src/lib/analytics/posthog.ts. const COOKIELESS = { api_host: 'http://127.0.0.1:0/ingest', // black-holed on purpose: no real ingestion persistence: 'memory', person_profiles: 'never', capture_pageview: false, capture_pageleave: false, disable_session_recording: true, } // The library default, for contrast: this is what writes to the device. const DEFAULT_CONFIG = { api_host: 'http://127.0.0.1:0/ingest', persistence: 'localStorage+cookie', } // A syntactically valid but inert project key. Storage behaviour does not depend // on the key being real, only on the persistence config. const KEY = 'phc_0000000000000000000000000000000000000000000' async function readStores(page) { return page.evaluate(() => { const ls = Object.keys(window.localStorage) const ss = Object.keys(window.sessionStorage) const cookies = document.cookie ? document.cookie.split('; ').map((c) => c.split('=')[0]) : [] return { cookies, localStorage: ls, sessionStorage: ss } }) } async function measure(browser, label, config) { const context = await browser.newContext() const page = await context.newPage() await page.goto('http://127.0.0.1:' + PORT + '/') await page.addScriptTag({ content: posthogSource }) await page.evaluate( ([key, cfg]) => { // eslint-disable-next-line no-undef window.posthog.init(key, cfg) // eslint-disable-next-line no-undef window.posthog.capture('$pageview') }, [KEY, config], ) // Give posthog a beat to write anything it means to write. await page.waitForTimeout(500) const stores = await readStores(page) await context.close() return { label, ...stores } } const PORT = 8731 const server = createServer((_req, res) => { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }) res.end('