diff --git a/packages/cookie-banner/src/client.ts b/packages/cookie-banner/src/client.ts index c31d6294d..eb0277e92 100644 --- a/packages/cookie-banner/src/client.ts +++ b/packages/cookie-banner/src/client.ts @@ -79,6 +79,10 @@ export class CookieBannerClient { } async load(): Promise { + for (const integration of this.integrations) { + integration.bootstrap(); + } + const configUrl = new URL(`${this.bannerId}/config`, this.baseUrl); if (this.lang) { configUrl.searchParams.set("lang", this.lang); @@ -89,6 +93,8 @@ export class CookieBannerClient { config = await fetchJSON(configUrl); } catch { this.startDetector(); + this.observer = observeAndActivate({}, {}); + getConsent()._setReady({}, false); return; } this.bannerConfig = config; diff --git a/packages/cookie-banner/src/integrations/gcm.ts b/packages/cookie-banner/src/integrations/gcm.ts index 6d8e3fdae..cec93d8f1 100644 --- a/packages/cookie-banner/src/integrations/gcm.ts +++ b/packages/cookie-banner/src/integrations/gcm.ts @@ -22,14 +22,16 @@ export class GoogleConsentModeIntegration implements ConsentIntegration { ); } - private getConsentFn(): ((...args: unknown[]) => void) | null { + private getConsentFn(): (...args: unknown[]) => void { const w = window as unknown as Record; if (typeof w.gtag === "function") { return w.gtag as (...args: unknown[]) => void; } - if (!Array.isArray(w.dataLayer)) return null; + if (!Array.isArray(w.dataLayer)) { + w.dataLayer = []; + } const dataLayer = w.dataLayer as unknown[]; return function () { @@ -37,11 +39,23 @@ export class GoogleConsentModeIntegration implements ConsentIntegration { }; } + bootstrap(): void { + const consentFn = this.getConsentFn(); + consentFn("consent", "default", { + ad_storage: "denied", + ad_user_data: "denied", + ad_personalization: "denied", + analytics_storage: "denied", + functionality_storage: "denied", + personalization_storage: "denied", + security_storage: "denied", + }); + } + setDefaults(categories: Category[]): void { if (!this.hasMapping(categories)) return; const consentFn = this.getConsentFn(); - if (!consentFn) return; const defaults: Record = {}; for (const cat of categories) { @@ -63,7 +77,6 @@ export class GoogleConsentModeIntegration implements ConsentIntegration { if (!this.hasMapping(categories)) return; const consentFn = this.getConsentFn(); - if (!consentFn) return; const update: Record = {}; for (const cat of categories) { diff --git a/packages/cookie-banner/src/integrations/index.ts b/packages/cookie-banner/src/integrations/index.ts index b02263190..2c4b1ac60 100644 --- a/packages/cookie-banner/src/integrations/index.ts +++ b/packages/cookie-banner/src/integrations/index.ts @@ -14,15 +14,12 @@ export type { ConsentIntegration } from "./integration"; export { GoogleConsentModeIntegration } from "./gcm"; -export { PostHogIntegration } from "./posthog"; import type { ConsentIntegration } from "./integration"; import { GoogleConsentModeIntegration } from "./gcm"; -import { PostHogIntegration } from "./posthog"; export function createDefaultIntegrations(): ConsentIntegration[] { return [ new GoogleConsentModeIntegration(), - new PostHogIntegration(), ]; } diff --git a/packages/cookie-banner/src/integrations/integration.ts b/packages/cookie-banner/src/integrations/integration.ts index 6e773601f..1d73469a6 100644 --- a/packages/cookie-banner/src/integrations/integration.ts +++ b/packages/cookie-banner/src/integrations/integration.ts @@ -15,6 +15,9 @@ import type { Category } from "../types"; export interface ConsentIntegration { + /** Called eagerly before config fetch to deny all tracking by default. */ + bootstrap(): void; + /** Called once after config is loaded, before any consent is applied. */ setDefaults(categories: Category[]): void; diff --git a/packages/cookie-banner/src/integrations/posthog.ts b/packages/cookie-banner/src/integrations/posthog.ts deleted file mode 100644 index f12b1dd1d..000000000 --- a/packages/cookie-banner/src/integrations/posthog.ts +++ /dev/null @@ -1,69 +0,0 @@ -// 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 type { Category } from "../types"; -import type { ConsentIntegration } from "./integration"; - -interface PostHogInstance { - opt_in_capturing: () => void; - opt_out_capturing: () => void; -} - -export class PostHogIntegration implements ConsentIntegration { - private hasMapping(categories: Category[]): boolean { - return categories.some((cat) => cat.posthog_consent); - } - - private getPostHog(): PostHogInstance | null { - const w = window as unknown as Record; - const posthog = w.posthog as (PostHogInstance & Record) | undefined; - if ( - !posthog || - typeof posthog.opt_in_capturing !== "function" || - typeof posthog.opt_out_capturing !== "function" - ) { - return null; - } - return posthog; - } - - setDefaults(categories: Category[]): void { - if (!this.hasMapping(categories)) return; - - const posthog = this.getPostHog(); - if (!posthog) return; - - posthog.opt_out_capturing(); - } - - update( - categories: Category[], - consentData: Record, - ): void { - if (!this.hasMapping(categories)) return; - - const posthog = this.getPostHog(); - if (!posthog) return; - - const granted = categories - .filter((cat) => cat.posthog_consent) - .every((cat) => !!consentData[cat.slug]); - - if (granted) { - posthog.opt_in_capturing(); - } else { - posthog.opt_out_capturing(); - } - } -}