From a447c62e193a834c81573181cb409e21f8a88c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 27 May 2026 13:06:02 +0200 Subject: [PATCH] Wire PostHog into the cookie-banner React example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a deferred PostHog wiring under examples/cookie-banner-react that boots posthog.init() inside the probo-ready handler and derives cookieless_mode and opt_out_capturing_by_default from the consent snapshot for the category flagged with posthog_consent. Driving the init args off the snapshot rather than consent_mode plugs two cases the simpler "consent_mode alone" rule got wrong: * OPT_OUT regulation, returning rejector: init would have booted in on_reject + capture-on, fired a $pageview synchronously, and only then called opt_out_capturing(). That single captured pageview (and the posthog cookie) leaked on every page load. * OPT_IN regulation, returning acceptor: init would have forced "always" + opt-out, costing the visitor cookies and a one-tick capture delay even though they had already consented. The snapshot already encodes the regulation default (buildDefaultConsentData on the cookie-banner client returns true for non-necessary categories under OPT_OUT and false under OPT_IN) and any persisted answer from a prior visit, so a single boolean drives both init args. Re-export the public domain types (BannerConfig, Category, Regulation, ConsentAction, ConsentRecord, CookieItem, VisitorConsent) from @probo/cookie-banner so the example can type the probo-ready event detail without duck-typing it. Adopt the PUBLIC_ env prefix in Vite so the example reads the same env var names (PUBLIC_COOKIE_BANNER_ID, PUBLIC_COOKIE_BANNER_API_BASE_URL, PUBLIC_POSTHOG_API_KEY) already used on getprobo.com, and add a matching .env.example. Ignore *.tsbuildinfo at the repo root; TypeScript's incremental cache is machine-local and does not belong in the tree. Signed-off-by: Émile Ré --- .gitignore | 1 + examples/cookie-banner-react/.env.example | 3 + examples/cookie-banner-react/package.json | 1 + .../src/hooks/useConfig.ts | 6 +- .../cookie-banner-react/src/lib/posthog.ts | 195 ++++++++ .../src/tabs/ThemedBannerTab.tsx | 43 +- .../src/tabs/_components/PosthogPanel.tsx | 92 ++++ .../cookie-banner-react/src/vite-env.d.ts | 11 + examples/cookie-banner-react/vite.config.ts | 5 + package-lock.json | 417 +++++++++++++++++- packages/cookie-banner/src/index.ts | 9 + 11 files changed, 774 insertions(+), 9 deletions(-) create mode 100644 examples/cookie-banner-react/.env.example create mode 100644 examples/cookie-banner-react/src/lib/posthog.ts create mode 100644 examples/cookie-banner-react/src/tabs/_components/PosthogPanel.tsx create mode 100644 examples/cookie-banner-react/src/vite-env.d.ts diff --git a/.gitignore b/.gitignore index 2981171e1..ba170da14 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ sbom-docker.json *.pem *.crt *.key +*.tsbuildinfo compose/keycloak/probo-realm.json .sandbox.env diff --git a/examples/cookie-banner-react/.env.example b/examples/cookie-banner-react/.env.example new file mode 100644 index 000000000..badc5cb13 --- /dev/null +++ b/examples/cookie-banner-react/.env.example @@ -0,0 +1,3 @@ +PUBLIC_COOKIE_BANNER_ID= +PUBLIC_COOKIE_BANNER_API_BASE_URL=http://localhost:8080/api/cookie-banner/v1 +PUBLIC_POSTHOG_API_KEY= diff --git a/examples/cookie-banner-react/package.json b/examples/cookie-banner-react/package.json index 17201dd2f..66a8df571 100644 --- a/examples/cookie-banner-react/package.json +++ b/examples/cookie-banner-react/package.json @@ -9,6 +9,7 @@ }, "dependencies": { "@probo/cookie-banner": "*", + "posthog-js": "^1.376.2", "react": "^19.2.1", "react-dom": "^19.2.1" }, diff --git a/examples/cookie-banner-react/src/hooks/useConfig.ts b/examples/cookie-banner-react/src/hooks/useConfig.ts index 7dfac8a8a..bf971c6ca 100644 --- a/examples/cookie-banner-react/src/hooks/useConfig.ts +++ b/examples/cookie-banner-react/src/hooks/useConfig.ts @@ -7,9 +7,11 @@ export interface Config { const STORAGE_KEY = "probo-example-config"; +// Seed defaults from Vite env (loaded from ../../../getprobo.com/.env) so the +// example is usable without any manual setup when those vars are present. const defaultConfig: Config = { - bannerId: "", - baseUrl: "", + bannerId: import.meta.env.PUBLIC_COOKIE_BANNER_ID ?? "", + baseUrl: import.meta.env.PUBLIC_COOKIE_BANNER_API_BASE_URL ?? "", }; function getSnapshot(): Config { diff --git a/examples/cookie-banner-react/src/lib/posthog.ts b/examples/cookie-banner-react/src/lib/posthog.ts new file mode 100644 index 000000000..a11b67fc0 --- /dev/null +++ b/examples/cookie-banner-react/src/lib/posthog.ts @@ -0,0 +1,195 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import posthog from "posthog-js"; +import type { BannerConfig } from "@probo/cookie-banner"; +import { getConsent, type ConsentData } from "@probo/cookie-banner/consent"; + +export type ConsentMode = BannerConfig["consent_mode"]; + +/** + * Fallback category slug to gate PostHog when the banner config does not flag + * any category with `posthog_consent: true`. Most Probo banners ship with an + * "analytics" category, hence this default. + */ +const FALLBACK_CATEGORY_SLUG = "analytics"; + +let subscribed = false; +let initialized = false; +let unsubscribeConsent: (() => void) | null = null; +let categorySlug: string = FALLBACK_CATEGORY_SLUG; +let consentMode: ConsentMode | null = null; +const statusListeners = new Set<() => void>(); + +export interface PosthogStatus { + initialized: boolean; + consentMode: ConsentMode | null; + optedIn: boolean; + optedOut: boolean; + distinctId: string | null; +} + +// Cached snapshot. `useSyncExternalStore` compares references with `Object.is`, +// so `getPosthogStatus()` must return a stable reference until something +// actually changes — otherwise React loops itself into a stack overflow. +let cachedStatus: PosthogStatus = { + initialized: false, + consentMode: null, + optedIn: false, + optedOut: false, + distinctId: null, +}; + +/** + * Wire up the consent subscription so that any future opt-in / opt-out + * decisions are mirrored to PostHog. + * + * Note: this does NOT call `posthog.init()`. We can't choose `cookieless_mode` + * until the banner config tells us the regulation's `consent_mode`, so the + * actual SDK init is deferred to {@link configurePosthogFromBanner}, which + * the `probo-ready` event handler should invoke with the banner config. + * + * Safe to call multiple times; only the first call wires the subscription. + */ +export function initPosthog(): void { + if (subscribed) return; + + if (!import.meta.env.PUBLIC_POSTHOG_API_KEY) { + console.warn( + "[posthog] PUBLIC_POSTHOG_API_KEY is not set; skipping PostHog init. " + + "Copy .env.example to .env in examples/cookie-banner-react/ and fill it in.", + ); + return; + } + + subscribed = true; + + const consent = getConsent(); + unsubscribeConsent = consent.subscribe((data: ConsentData) => { + syncCapturing(data); + refreshStatus(); + }); +} + +/** + * Initialize PostHog (on first call) and route opt-in / opt-out decisions + * through the category flagged with `posthog_consent: true` in the banner + * config. Call this from the `probo-ready` event handler. + * + * The init options are derived from the current consent snapshot, which the + * banner client has already populated with either the persisted answer (cookie + * or API) or the per-regulation default (`true` for non-necessary categories + * under `OPT_OUT`, `false` under `OPT_IN`): + * - Analytics allowed → cookies and capture on from the start; the + * subscription downgrades to cookieless if the visitor later rejects. + * - Analytics denied → fully cookieless and opted-out; the subscription opts + * in (still cookieless for the rest of the session) if the visitor later + * accepts. + * + * Driving the init off the snapshot rather than `consent_mode` plugs the race + * where `posthog.init()` fires a `$pageview` (and sets the posthog cookie) + * before we can call `opt_out_capturing()` for an `OPT_OUT` visitor who + * already rejected on a prior visit. + * + * Subsequent calls leave PostHog initialized and just refresh the category + * slug / consent mode in the cached status snapshot. + */ +export function configurePosthogFromBanner(config: BannerConfig): void { + const flagged = config.categories.find((c) => c.posthog_consent); + const slug = flagged?.slug ?? FALLBACK_CATEGORY_SLUG; + const consent = getConsent(); + + if (!initialized) { + const apiKey = import.meta.env.PUBLIC_POSTHOG_API_KEY; + if (!apiKey) return; + + const analyticsAllowed = consent.getAll()[slug] === true; + posthog.init(apiKey, { + api_host: "https://t.probo.com", + ui_host: "https://us.posthog.com", + cookieless_mode: analyticsAllowed ? "on_reject" : "always", + opt_out_capturing_by_default: !analyticsAllowed, + person_profiles: "identified_only", + respect_dnt: true, + debug: import.meta.env.DEV, + }); + initialized = true; + } + + consentMode = config.consent_mode; + categorySlug = slug; + + syncCapturing(consent.getAll()); + refreshStatus(); +} + +/** Tear down the consent subscription. Does not un-initialize PostHog itself. */ +export function teardownPosthog(): void { + if (unsubscribeConsent) { + unsubscribeConsent(); + unsubscribeConsent = null; + } + subscribed = false; +} + +/** Subscribe to PostHog status changes (init / opt-in / opt-out / category). */ +export function subscribePosthogStatus(cb: () => void): () => void { + statusListeners.add(cb); + return () => statusListeners.delete(cb); +} + +export function getPosthogStatus(): PosthogStatus { + return cachedStatus; +} + +function syncCapturing(data: ConsentData): void { + if (!initialized) return; + if (data[categorySlug]) { + posthog.opt_in_capturing(); + } else { + posthog.opt_out_capturing(); + } +} + +function refreshStatus(): void { + const next: PosthogStatus = initialized + ? { + initialized: true, + consentMode, + optedIn: posthog.has_opted_in_capturing(), + optedOut: posthog.has_opted_out_capturing(), + distinctId: posthog.get_distinct_id?.() ?? null, + } + : { + initialized: false, + consentMode, + optedIn: false, + optedOut: false, + distinctId: null, + }; + + if (statusEqual(cachedStatus, next)) return; + cachedStatus = next; + for (const cb of statusListeners) cb(); +} + +function statusEqual(a: PosthogStatus, b: PosthogStatus): boolean { + return ( + a.initialized === b.initialized && + a.consentMode === b.consentMode && + a.optedIn === b.optedIn && + a.optedOut === b.optedOut && + a.distinctId === b.distinctId + ); +} diff --git a/examples/cookie-banner-react/src/tabs/ThemedBannerTab.tsx b/examples/cookie-banner-react/src/tabs/ThemedBannerTab.tsx index e52895381..c90e367c6 100644 --- a/examples/cookie-banner-react/src/tabs/ThemedBannerTab.tsx +++ b/examples/cookie-banner-react/src/tabs/ThemedBannerTab.tsx @@ -1,6 +1,15 @@ -import { useCallback, useEffect, useRef } from "react"; -import { registerCookieBanner } from "@probo/cookie-banner"; +import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; +import posthog from "posthog-js"; +import { registerCookieBanner, type BannerConfig } from "@probo/cookie-banner"; import { useConfig } from "../hooks/useConfig"; +import { + configurePosthogFromBanner, + getPosthogStatus, + initPosthog, + subscribePosthogStatus, + type PosthogStatus, +} from "../lib/posthog"; +import { PosthogPanel } from "./_components/PosthogPanel"; import type { EventEntry } from "../App"; let registered = false; @@ -13,12 +22,15 @@ interface ThemedBannerTabProps { export function ThemedBannerTab({ events, pushEvent }: ThemedBannerTabProps) { const [config] = useConfig(); const elRef = useRef(null); + const posthogStatus = usePosthogStatus(); + const [manualPing, setManualPing] = useState(null); useEffect(() => { if (!registered) { registerCookieBanner(); registered = true; } + initPosthog(); }, []); const attachListeners = useCallback( @@ -26,9 +38,15 @@ export function ThemedBannerTab({ events, pushEvent }: ThemedBannerTabProps) { elRef.current = el; if (!el) return; - el.addEventListener("probo-ready", (e: Event) => - pushEvent("probo-ready", (e as CustomEvent).detail), - ); + el.addEventListener("probo-ready", (e: Event) => { + const detail = (e as CustomEvent).detail as { + config?: BannerConfig; + }; + if (detail?.config) { + configurePosthogFromBanner(detail.config); + } + pushEvent("probo-ready", (e as CustomEvent).detail); + }); el.addEventListener("probo-consent", (e: Event) => pushEvent("probo-consent", (e as CustomEvent).detail), ); @@ -36,6 +54,11 @@ export function ThemedBannerTab({ events, pushEvent }: ThemedBannerTabProps) { [pushEvent], ); + const sendPing = useCallback(() => { + posthog.capture("themed_tab_manual_ping", { source: "example" }); + setManualPing(new Date().toISOString()); + }, []); + if (!config.bannerId || !config.baseUrl) { return (
@@ -56,6 +79,12 @@ export function ThemedBannerTab({ events, pushEvent }: ThemedBannerTabProps) { bottom-right corner.

+ + ); } + +function usePosthogStatus(): PosthogStatus { + return useSyncExternalStore(subscribePosthogStatus, getPosthogStatus, getPosthogStatus); +} diff --git a/examples/cookie-banner-react/src/tabs/_components/PosthogPanel.tsx b/examples/cookie-banner-react/src/tabs/_components/PosthogPanel.tsx new file mode 100644 index 000000000..8e09bfe31 --- /dev/null +++ b/examples/cookie-banner-react/src/tabs/_components/PosthogPanel.tsx @@ -0,0 +1,92 @@ +import type { PosthogStatus } from "../../lib/posthog"; + +interface PosthogPanelProps { + status: PosthogStatus; + manualPing: string | null; + onSendPing: () => void; +} + +export function PosthogPanel({ status, manualPing, onSendPing }: PosthogPanelProps) { + return ( +
+

PostHog

+

+ Init is deferred until the banner config arrives, then{" "} + cookieless_mode and{" "} + opt_out_capturing_by_default are derived from the consent + snapshot for the category flagged with posthog_consent. + The snapshot already encodes both the regulation's default + (OPT_IN → off, OPT_OUT → on) and any + persisted answer from a prior visit, so a returning visitor who + accepted boots straight into "on_reject" +{" "} + false while one who rejected (or a fresh{" "} + OPT_IN visitor) boots into "always" +{" "} + true. The getConsent().subscribe() callback + then flips opt_in_capturing() /{" "} + opt_out_capturing() on subsequent banner actions. +

+ + + + + + + + +
+ + {manualPing && ( + + last sent: {manualPing} (only delivered when opted in) + + )} +
+ ); +} + +interface RowProps { + label: string; + value: string; + ok?: boolean; + warn?: boolean; +} + +function Row({ label, value, ok, warn }: RowProps) { + const color = ok ? "green" : warn ? "tomato" : undefined; + return ( + + {label} + + {value} + + + ); +} diff --git a/examples/cookie-banner-react/src/vite-env.d.ts b/examples/cookie-banner-react/src/vite-env.d.ts new file mode 100644 index 000000000..43e92a112 --- /dev/null +++ b/examples/cookie-banner-react/src/vite-env.d.ts @@ -0,0 +1,11 @@ +/// + +interface ImportMetaEnv { + readonly PUBLIC_COOKIE_BANNER_ID?: string; + readonly PUBLIC_COOKIE_BANNER_API_BASE_URL?: string; + readonly PUBLIC_POSTHOG_API_KEY?: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} diff --git a/examples/cookie-banner-react/vite.config.ts b/examples/cookie-banner-react/vite.config.ts index a2889c22f..3a7029c5f 100644 --- a/examples/cookie-banner-react/vite.config.ts +++ b/examples/cookie-banner-react/vite.config.ts @@ -3,6 +3,11 @@ import react from "@vitejs/plugin-react"; export default defineConfig({ plugins: [react()], + // Use the PUBLIC_ prefix so the example accepts the same env var names + // (PUBLIC_COOKIE_BANNER_ID, PUBLIC_COOKIE_BANNER_API_BASE_URL, + // PUBLIC_POSTHOG_API_KEY) used by getprobo.com. Copy .env.example to .env + // and fill in your values. + envPrefix: "PUBLIC_", server: { port: 5180, }, diff --git a/package-lock.json b/package-lock.json index 45c95b95c..37b666195 100644 --- a/package-lock.json +++ b/package-lock.json @@ -115,6 +115,7 @@ "version": "0.0.0", "dependencies": { "@probo/cookie-banner": "*", + "posthog-js": "^1.376.2", "react": "^19.2.1", "react-dom": "^19.2.1" }, @@ -3832,6 +3833,252 @@ "node": ">=18.0.0" } }, + "node_modules/@opentelemetry/api": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", + "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/api-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.208.0.tgz", + "integrity": "sha512-CjruKY9V6NMssL/T1kAFgzosF1v9o6oeN+aX5JB/C/xPNtmgIJqcXHG7fA82Ou1zCpWGl4lROQUKwUNE1pMCyg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api": "^1.3.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@opentelemetry/core": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.2.0.tgz", + "integrity": "sha512-FuabnnUm8LflnieVxs6eP7Z383hgQU4W1e3KJS6aOG3RxWxcHyBxH8fDMHNgu/gFx/M2jvTOW/4/PHhLz6bjWw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/exporter-logs-otlp-http": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-logs-otlp-http/-/exporter-logs-otlp-http-0.208.0.tgz", + "integrity": "sha512-jOv40Bs9jy9bZVLo/i8FwUiuCvbjWDI+ZW13wimJm4LjnlwJxGgB+N/VWOZUTpM+ah/awXeQqKdNlpLf2EjvYg==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-exporter-base": "0.208.0", + "@opentelemetry/otlp-transformer": "0.208.0", + "@opentelemetry/sdk-logs": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-exporter-base": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.208.0.tgz", + "integrity": "sha512-gMd39gIfVb2OgxldxUtOwGJYSH8P1kVFFlJLuut32L6KgUC4gl1dMhn+YC2mGn0bDOiQYSk/uHOdSjuKp58vvA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/otlp-transformer": "0.208.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.208.0.tgz", + "integrity": "sha512-DCFPY8C6lAQHUNkzcNT9R+qYExvsk6C5Bto2pbNxgicpcSWbe2WHShLxkOxIdNcBiYPdVHv/e7vH7K6TI+C+fQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/sdk-logs": "0.208.0", + "@opentelemetry/sdk-metrics": "2.2.0", + "@opentelemetry/sdk-trace-base": "2.2.0", + "protobufjs": "^7.3.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.3.0" + } + }, + "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.7.1.tgz", + "integrity": "sha512-DeT6KKolmC4e/dRQvMQ/RwlnzhaqeiFOXY5ngoOPJ07GgVVKxZOg9EcrNZb5aTzUn+iCrJldAgOfQm1O/QfPAQ==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.7.1", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz", + "integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.0.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs": { + "version": "0.208.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.208.0.tgz", + "integrity": "sha512-QlAyL1jRpOeaqx7/leG1vJMp84g0xKP6gJmfELBpnI4O/9xPX+Hu5m1POk9Kl+veNkyth5t19hRlN6tNY1sjbA==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/api-logs": "0.208.0", + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.4.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.2.0.tgz", + "integrity": "sha512-G5KYP6+VJMZzpGipQw7Giif48h6SGQ2PFKEYCybeXJsOCB4fp8azqMAAzE5lnnHK3ZVwYQrgmFbsUJO/zOnwGw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.9.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.2.0.tgz", + "integrity": "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/resources": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/resources": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.2.0.tgz", + "integrity": "sha512-1pNQf/JazQTMA0BiO5NINUzH0cbLbbl7mntLa4aJNmCCXSj0q03T5ZXXL0zw4G55TjdL9Tz32cznGClf+8zr5A==", + "license": "Apache-2.0", + "dependencies": { + "@opentelemetry/core": "2.2.0", + "@opentelemetry/semantic-conventions": "^1.29.0" + }, + "engines": { + "node": "^18.19.0 || >=20.6.0" + }, + "peerDependencies": { + "@opentelemetry/api": ">=1.3.0 <1.10.0" + } + }, + "node_modules/@opentelemetry/semantic-conventions": { + "version": "1.41.1", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.41.1.tgz", + "integrity": "sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/@package-json/types": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@package-json/types/-/types-0.0.12.tgz", @@ -3869,6 +4116,21 @@ "node": ">=18" } }, + "node_modules/@posthog/core": { + "version": "1.29.11", + "resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.29.11.tgz", + "integrity": "sha512-/4EF7oxAFSWJgaXxppT8bdYp7MGAnWFnKz994+MetTz/T6CKbYpjqIXHCofQXtcOXjEclTYj91igA+IkVFKiSg==", + "license": "MIT", + "dependencies": { + "@posthog/types": "1.376.2" + } + }, + "node_modules/@posthog/types": { + "version": "1.376.2", + "resolved": "https://registry.npmjs.org/@posthog/types/-/types-1.376.2.tgz", + "integrity": "sha512-Y3ROpAxNqgcy2G0w6JoG5Gt+P6WNY2lkHTPMPzWqexRwemYbFegDi5AifDyD9/tstKTlOYKTTExtaJ5EBcghyQ==", + "license": "MIT" + }, "node_modules/@probo/console": { "resolved": "apps/console", "link": true @@ -3941,6 +4203,69 @@ "resolved": "packages/ui", "link": true }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.1.tgz", + "integrity": "sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==", + "license": "BSD-3-Clause" + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -7291,7 +7616,6 @@ "version": "22.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -9243,6 +9567,17 @@ "url": "https://opencollective.com/express" } }, + "node_modules/core-js": { + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cose-base": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", @@ -11781,6 +12116,12 @@ } } }, + "node_modules/fflate": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", + "integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -14357,6 +14698,12 @@ "license": "MIT", "peer": true }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -16544,6 +16891,37 @@ "dev": true, "license": "ISC" }, + "node_modules/posthog-js": { + "version": "1.376.2", + "resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.376.2.tgz", + "integrity": "sha512-Anz2pCp7dcNbammTExiZpcKC08dxfrHYaJgaXH6rq5x3Zcfj/4FkcMJF2cGCrdQXel5Y4vftiVSseZda0HAQTQ==", + "license": "SEE LICENSE IN LICENSE", + "dependencies": { + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/api-logs": "^0.208.0", + "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", + "@opentelemetry/resources": "^2.2.0", + "@opentelemetry/sdk-logs": "^0.208.0", + "@posthog/core": "1.29.11", + "@posthog/types": "1.376.2", + "core-js": "^3.38.1", + "dompurify": "^3.3.2", + "fflate": "^0.4.8", + "preact": "^10.28.2", + "query-selector-shadow-dom": "^1.0.1", + "web-vitals": "^5.1.0" + } + }, + "node_modules/preact": { + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -16841,6 +17219,30 @@ "prosemirror-transform": "^1.1.0" } }, + "node_modules/protobufjs": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz", + "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.2", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", + "@types/node": ">=13.7.0", + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -16881,6 +17283,12 @@ "node": ">=6" } }, + "node_modules/query-selector-shadow-dom": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz", + "integrity": "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw==", + "license": "MIT" + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -19106,7 +19514,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unified": { @@ -19719,6 +20126,12 @@ "node": ">= 14" } }, + "node_modules/web-vitals": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-5.2.0.tgz", + "integrity": "sha512-i2z98bEmaCqSDiHEDu+gHl/dmR4Q+TxFmG3/13KkMO+o8UxQzCqWaDRCiLgEa41nlO4VpXSI0ASa1xWmO9sBlA==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/packages/cookie-banner/src/index.ts b/packages/cookie-banner/src/index.ts index 01acb8980..8be1e7bb5 100644 --- a/packages/cookie-banner/src/index.ts +++ b/packages/cookie-banner/src/index.ts @@ -13,3 +13,12 @@ // PERFORMANCE OF THIS SOFTWARE. export { registerCookieBanner } from "./themed-banner"; +export type { + BannerConfig, + Category, + ConsentAction, + ConsentRecord, + CookieItem, + Regulation, + VisitorConsent, +} from "./types";