Intégrations Framework
While the basic script tag integration works universally, modern frameworks benefit from deeper integration using composables, hooks, and services. This guide provides ready-to-use examples for popular frameworks.
Vue 3 (Composition API)
Section intitulée « Vue 3 (Composition API) »Composable : useCMP.ts
Section intitulée « Composable : useCMP.ts »import { ref, onMounted, onUnmounted, readonly } from 'vue'
interface ConsentCategories { necessary: boolean analytics: boolean marketing: boolean preferences: boolean}
let loadPromise: Promise<void> | null = null
function loadScript(src: string, siteKey: string): Promise<void> { if (loadPromise) return loadPromise loadPromise = new Promise((resolve, reject) => { if (document.querySelector(`script[data-site="${siteKey}"]`)) { resolve(); return } const el = document.createElement('script') el.src = src el.async = true el.setAttribute('data-site', siteKey) el.onload = () => resolve() el.onerror = () => reject(new Error(`CMP script failed: ${src}`)) document.head.appendChild(el) }) return loadPromise}
export function useCMP(options?: { cmpUrl?: string; siteKey?: string }) { const isReady = ref(false) const consent = ref<{ decision: string; categories: ConsentCategories } | null>(null) let cleanups: Array<() => void> = []
const sync = () => { consent.value = window.CMP?.getConsent() ?? null }
onMounted(async () => { const url = options?.cmpUrl ?? import.meta.env.VITE_CMP_URL const key = options?.siteKey ?? import.meta.env.VITE_CMP_SITE_KEY if (!url || !key) return
await loadScript(url, key) window.CMP?.ready(() => { isReady.value = true; sync() })
const onChange = () => sync() window.CMP?.on('consent:accepted', onChange) window.CMP?.on('consent:rejected', onChange) window.CMP?.on('consent:updated', onChange) cleanups = [ () => window.CMP?.off('consent:accepted', onChange), () => window.CMP?.off('consent:rejected', onChange), () => window.CMP?.off('consent:updated', onChange), ] })
onUnmounted(() => { cleanups.forEach(fn => fn()); cleanups = [] })
return { isReady: readonly(isReady), consent: readonly(consent), show: () => window.CMP?.show(), hide: () => window.CMP?.hide(), accept: (cats?: Partial<ConsentCategories>) => window.CMP?.accept(cats), reject: () => window.CMP?.reject(), hasConsent: (cat: string) => window.CMP?.hasConsent(cat) ?? false, setLanguage: (lang: string) => window.CMP?.setLanguage(lang), }}Utilisation dans App.vue
Section intitulée « Utilisation dans App.vue »<script setup lang="ts">import { useCMP } from '@/composables/useCMP'import { useI18n } from 'vue-i18n'import { watch } from 'vue'
const { isReady, consent, setLanguage } = useCMP()const { locale } = useI18n()
// Sync language changes to CMPwatch(locale, (lang) => { if (isReady.value) setLanguage(lang)})</script>Optimisation SSR / SSG (Vite SSG / Nuxt)
Section intitulée « Optimisation SSR / SSG (Vite SSG / Nuxt) »Si vous utilisez le rendu côté serveur (SSR) ou la génération de sites statiques (SSG), le chargement exclusif côté client peut provoquer un léger retard. Pour assurer l’inclusion du script dans le HTML, vous pouvez utiliser @unhead/vue :
<script setup lang="ts">import { useCMP } from '@/composables/useCMP'import { useHead } from '@unhead/vue' // Veya Nuxt için doğrudan useHead()
const { isReady, setLanguage } = useCMP()
if (import.meta.env.VITE_CMP_URL && import.meta.env.VITE_CMP_SITE_KEY) { useHead({ script: [ { src: import.meta.env.VITE_CMP_URL, async: true, 'data-site': import.meta.env.VITE_CMP_SITE_KEY } ] })}
// ... sync logic</script>Utilisation dans un composant
Section intitulée « Utilisation dans un composant »<script setup lang="ts">import { useCMP } from '@/composables/useCMP'
const { consent, hasConsent, show } = useCMP()</script>
<template> <div v-if="consent"> <p>Analytics: {{ hasConsent('analytics') ? '✅' : '❌' }}</p> </div> <button @click="show()">Cookie Preferences</button></template>Variables d’environnement
Section intitulée « Variables d’environnement »VITE_CMP_URL=https://tool.hashentry.com/cmp.jsVITE_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxReact (Hooks)
Section intitulée « React (Hooks) »Hook : useCMP.ts
Section intitulée « Hook : useCMP.ts »import { useState, useEffect, useCallback, useRef } from 'react'
interface ConsentCategories { necessary: boolean analytics: boolean marketing: boolean preferences: boolean}
let loadPromise: Promise<void> | null = null
function loadScript(src: string, siteKey: string): Promise<void> { if (loadPromise) return loadPromise loadPromise = new Promise((resolve, reject) => { if (document.querySelector(`script[data-site="${siteKey}"]`)) { resolve(); return } const el = document.createElement('script') el.src = src el.async = true el.setAttribute('data-site', siteKey) el.onload = () => resolve() el.onerror = () => reject(new Error(`CMP script failed: ${src}`)) document.head.appendChild(el) }) return loadPromise}
export function useCMP(options?: { cmpUrl?: string; siteKey?: string }) { const [isReady, setIsReady] = useState(false) const [consent, setConsent] = useState<{ decision: string; categories: ConsentCategories } | null>(null) const cleanups = useRef<Array<() => void>>([])
const sync = useCallback(() => { setConsent(window.CMP?.getConsent() ?? null) }, [])
useEffect(() => { const url = options?.cmpUrl ?? import.meta.env.VITE_CMP_URL const key = options?.siteKey ?? import.meta.env.VITE_CMP_SITE_KEY if (!url || !key) return
loadScript(url, key).then(() => { window.CMP?.ready(() => { setIsReady(true); sync() }) const onChange = () => sync() window.CMP?.on('consent:accepted', onChange) window.CMP?.on('consent:rejected', onChange) window.CMP?.on('consent:updated', onChange) cleanups.current = [ () => window.CMP?.off('consent:accepted', onChange), () => window.CMP?.off('consent:rejected', onChange), () => window.CMP?.off('consent:updated', onChange), ] })
return () => { cleanups.current.forEach(fn => fn()); cleanups.current = [] } }, [])
return { isReady, consent, show: useCallback(() => window.CMP?.show(), []), hide: useCallback(() => window.CMP?.hide(), []), accept: useCallback((cats?: Partial<ConsentCategories>) => window.CMP?.accept(cats), []), reject: useCallback(() => window.CMP?.reject(), []), hasConsent: useCallback((cat: string) => window.CMP?.hasConsent(cat) ?? false, []), setLanguage: useCallback((lang: string) => window.CMP?.setLanguage(lang), []), }}Utilisation
Section intitulée « Utilisation »import { useCMP } from './hooks/useCMP'
function CookieBanner() { const { isReady, consent, show, hasConsent } = useCMP()
return ( <div> {isReady && consent && ( <p>Analytics: {hasConsent('analytics') ? '✅' : '❌'}</p> )} <button onClick={show}>Cookie Preferences</button> </div> )}Next.js (App Router)
Section intitulée « Next.js (App Router) »Hook de Client Component
Section intitulée « Hook de Client Component »Dans Next.js App Router, le CMP doit être chargé dans un Client Component car il accède à window et document.
'use client'// Same hook as the React version above.// Import and use it in Client Components only.Intégration de Layout
Section intitulée « Intégration de Layout »import { CmpProvider } from '@/components/CmpProvider'
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <CmpProvider /> {children} </body> </html> )}'use client'import { useCMP } from '@/hooks/useCMP'
export function CmpProvider() { useCMP({ cmpUrl: process.env.NEXT_PUBLIC_CMP_URL!, siteKey: process.env.NEXT_PUBLIC_CMP_SITE_KEY!, }) return null}Variables d’environnement
Section intitulée « Variables d’environnement »NEXT_PUBLIC_CMP_URL=https://tool.hashentry.com/cmp.jsNEXT_PUBLIC_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxPlugin : cmp.client.ts
Section intitulée « Plugin : cmp.client.ts »Dans Nuxt 3, utilisez un plugin côté client pour charger le SDK au démarrage :
export default defineNuxtPlugin(() => { const config = useRuntimeConfig() const src = config.public.cmpUrl as string const siteKey = config.public.cmpSiteKey as string
if (!src || !siteKey) return
const script = document.createElement('script') script.src = src script.async = true script.setAttribute('data-site', siteKey) document.head.appendChild(script)})Composable : useCMP.ts
Section intitulée « Composable : useCMP.ts »export function useCMP() { const isReady = ref(false) const consent = ref<Record<string, any> | null>(null)
const sync = () => { consent.value = window.CMP?.getConsent() ?? null }
onMounted(() => { window.CMP?.ready(() => { isReady.value = true; sync() }) const onChange = () => sync() window.CMP?.on('consent:accepted', onChange) window.CMP?.on('consent:rejected', onChange) window.CMP?.on('consent:updated', onChange) })
return { isReady: readonly(isReady), consent: readonly(consent), show: () => window.CMP?.show(), hide: () => window.CMP?.hide(), accept: (cats?: Record<string, boolean>) => window.CMP?.accept(cats), reject: () => window.CMP?.reject(), hasConsent: (cat: string) => window.CMP?.hasConsent(cat) ?? false, setLanguage: (lang: string) => window.CMP?.setLanguage(lang), }}Configuration
Section intitulée « Configuration »export default defineNuxtConfig({ runtimeConfig: { public: { cmpUrl: 'https://tool.hashentry.com/cmp.js', cmpSiteKey: '', // Set via NUXT_PUBLIC_CMP_SITE_KEY env } }})Service : cmp.service.ts
Section intitulée « Service : cmp.service.ts »import { Injectable, signal } from '@angular/core'
@Injectable({ providedIn: 'root' })export class CmpService { isReady = signal(false) consent = signal<Record<string, any> | null>(null)
private loaded = false
init(cmpUrl: string, siteKey: string): void { if (this.loaded) return this.loaded = true
const el = document.createElement('script') el.src = cmpUrl el.async = true el.setAttribute('data-site', siteKey) el.onload = () => { (window as any).CMP?.ready(() => { this.isReady.set(true) this.sync() }) const onChange = () => this.sync() ;(window as any).CMP?.on('consent:accepted', onChange) ;(window as any).CMP?.on('consent:rejected', onChange) ;(window as any).CMP?.on('consent:updated', onChange) } document.head.appendChild(el) }
private sync(): void { this.consent.set((window as any).CMP?.getConsent() ?? null) }
show = () => (window as any).CMP?.show() hide = () => (window as any).CMP?.hide() accept = (cats?: Record<string, boolean>) => (window as any).CMP?.accept(cats) reject = () => (window as any).CMP?.reject() hasConsent = (cat: string) => (window as any).CMP?.hasConsent(cat) ?? false setLanguage = (lang: string) => (window as any).CMP?.setLanguage(lang)}App Component
Section intitulée « App Component »import { Component, OnInit, inject } from '@angular/core'import { CmpService } from './services/cmp.service'import { environment } from '../environments/environment'
@Component({ selector: 'app-root', template: '<router-outlet />' })export class AppComponent implements OnInit { private cmp = inject(CmpService)
ngOnInit() { this.cmp.init(environment.cmpUrl, environment.cmpSiteKey) }}Utilisation dans un composant
Section intitulée « Utilisation dans un composant »import { Component, inject } from '@angular/core'import { CmpService } from '../services/cmp.service'
@Component({ selector: 'app-footer', template: ` <footer> <button (click)="cmp.show()">Cookie Preferences</button> @if (cmp.isReady()) { <span>Analytics: {{ cmp.hasConsent('analytics') ? '✅' : '❌' }}</span> } </footer> `})export class FooterComponent { cmp = inject(CmpService)}Svelte (5 / Runes)
Section intitulée « Svelte (5 / Runes) »Store : cmp.svelte.ts
Section intitulée « Store : cmp.svelte.ts »let loadPromise: Promise<void> | null = null
function loadScript(src: string, siteKey: string): Promise<void> { if (loadPromise) return loadPromise loadPromise = new Promise((resolve, reject) => { if (document.querySelector(`script[data-site="${siteKey}"]`)) { resolve(); return } const el = document.createElement('script') el.src = src; el.async = true el.setAttribute('data-site', siteKey) el.onload = () => resolve() el.onerror = () => reject() document.head.appendChild(el) }) return loadPromise}
export function createCMP(cmpUrl: string, siteKey: string) { let isReady = $state(false) let consent = $state<Record<string, any> | null>(null)
const sync = () => { consent = (window as any).CMP?.getConsent() ?? null }
loadScript(cmpUrl, siteKey).then(() => { (window as any).CMP?.ready(() => { isReady = true; sync() }) ;(window as any).CMP?.on('consent:accepted', sync) ;(window as any).CMP?.on('consent:rejected', sync) ;(window as any).CMP?.on('consent:updated', sync) })
return { get isReady() { return isReady }, get consent() { return consent }, show: () => (window as any).CMP?.show(), hide: () => (window as any).CMP?.hide(), accept: (cats?: Record<string, boolean>) => (window as any).CMP?.accept(cats), reject: () => (window as any).CMP?.reject(), hasConsent: (cat: string) => (window as any).CMP?.hasConsent(cat) ?? false, setLanguage: (lang: string) => (window as any).CMP?.setLanguage(lang), }}Utilisation
Section intitulée « Utilisation »<script lang="ts"> import { createCMP } from '$lib/cmp.svelte' import { PUBLIC_CMP_URL, PUBLIC_CMP_SITE_KEY } from '$env/static/public'
const cmp = createCMP(PUBLIC_CMP_URL, PUBLIC_CMP_SITE_KEY)</script>
<slot />
<footer> <button onclick={() => cmp.show()}>Cookie Preferences</button> {#if cmp.isReady} <span>Analytics: {cmp.hasConsent('analytics') ? '✅' : '❌'}</span> {/if}</footer>Puisqu’Astro génère du HTML statique par défaut, utilisez une balise <script> dans votre layout. Pour les îlots interactifs, utilisez les intégrations ci-dessus :
<html> <head> <script src={import.meta.env.PUBLIC_CMP_URL} data-site={import.meta.env.PUBLIC_CMP_SITE_KEY} async /> </head> <body> <slot /> </body></html>Pour les composants interactifs, utilisez Vue, React ou Svelte avec les directives client d’Astro :
<CookieButton client:load />Types TypeScript
Section intitulée « Types TypeScript »Pour tous les frameworks, ajoutez ces types globaux :
interface ConsentCategories { necessary: boolean analytics: boolean marketing: boolean preferences: boolean}
interface ConsentState { decision: 'accept' | 'reject' | 'partial' categories: ConsentCategories timestamp: string consent_token?: string}
interface ConsentResult { success: boolean consent_token: string proof_hash: string created_at: string}
interface CMPAPI { show(): void hide(): void accept(categories?: Partial<ConsentCategories>): Promise<ConsentResult> reject(): Promise<ConsentResult> getConsent(): ConsentState | null hasConsent(category: string): boolean setLanguage(lang: string): void getLanguage(): string setMetadata(key: string, value: string): void setMetadata(obj: Record<string, string>): void setRegion(region: string): void on(event: string, callback: (...args: unknown[]) => void): void off(event: string, callback: (...args: unknown[]) => void): void ready(callback: () => void): void}
declare global { interface Window { CMP?: CMPAPI __CMP_QUEUE?: Array<[string, ...unknown[]]> }}