Интеграция с фреймворками
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)
Заголовок раздела «Vue 3 (Composition API)»Composable: useCMP.ts
Заголовок раздела «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), }}Использование в App.vue
Заголовок раздела «Использование в 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>Оптимизация SSR / SSG (Vite SSG / Nuxt)
Заголовок раздела «Оптимизация SSR / SSG (Vite SSG / Nuxt)»Если вы используете SSR или SSG, загрузка скрипта исключительно на стороне клиента может вызвать небольшую задержку. Чтобы убедиться, что скрипт включен в HTML, вы можете использовать @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>Использование в компоненте
Заголовок раздела «Использование в компоненте»<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>Переменные окружения
Заголовок раздела «Переменные окружения»VITE_CMP_URL=https://tool.hashentry.com/cmp.jsVITE_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxReact (Hooks)
Заголовок раздела «React (Hooks)»Hook: useCMP.ts
Заголовок раздела «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), []), }}Использование
Заголовок раздела «Использование»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)
Заголовок раздела «Next.js (App Router)»Хук Client Component
Заголовок раздела «Хук Client Component»В Next.js App Router CMP должен загружаться в Client Component, поскольку он обращается к window и document.
'use client'// Same hook as the React version above.// Import and use it in Client Components only.Интеграция Layout
Заголовок раздела «Интеграция 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}Переменные окружения
Заголовок раздела «Переменные окружения»NEXT_PUBLIC_CMP_URL=https://tool.hashentry.com/cmp.jsNEXT_PUBLIC_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxПлагин: cmp.client.ts
Заголовок раздела «Плагин: cmp.client.ts»В Nuxt 3 используйте плагин на стороне клиента для загрузки SDK при запуске:
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
Заголовок раздела «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), }}Конфигурация
Заголовок раздела «Конфигурация»export default defineNuxtConfig({ runtimeConfig: { public: { cmpUrl: 'https://tool.hashentry.com/cmp.js', cmpSiteKey: '', // Set via NUXT_PUBLIC_CMP_SITE_KEY env } }})Angular
Заголовок раздела «Angular»Service: cmp.service.ts
Заголовок раздела «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
Заголовок раздела «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) }}Использование в компоненте
Заголовок раздела «Использование в компоненте»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)
Заголовок раздела «Svelte (5 / Runes)»Store: cmp.svelte.ts
Заголовок раздела «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), }}Использование
Заголовок раздела «Использование»<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>Поскольку Astro по умолчанию рендерит статический HTML, используйте тег <script> в вашем layout. Для интерактивных островов используйте любую интеграцию выше:
<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>Для интерактивных компонентов используйте Vue, React или Svelte с директивами Astro:
<CookieButton client:load />Типы TypeScript
Заголовок раздела «Типы TypeScript»Для всех фреймворков добавьте следующие глобальные типы:
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[]]> }}