تكاملات الأطر (Frameworks)
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 titled “Vue 3 (Composition API)”Composable: useCMP.ts
Section titled “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
Section titled “الاستخدام في 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)
Section titled “تحسين SSR / SSG (Vite SSG / Nuxt)”إذا كنت تستخدم العرض من جانب الخادم (SSR) أو توليد المواقع الثابتة (SSG)، فإن تحميل سكريبت CMP حصريًا من جانب العميل قد يسبب تأخيرًا طفيفًا. لضمان تضمين السكريبت في ملف HTML المولد الأولي، يمكنك استخدام @unhead/vue لحقنه في <head> مباشرة:
<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>الاستخدام في Component
Section titled “الاستخدام في Component”<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>متغيرات البيئة
Section titled “متغيرات البيئة”VITE_CMP_URL=https://tool.hashentry.com/cmp.jsVITE_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxReact (Hooks)
Section titled “React (Hooks)”Hook: useCMP.ts
Section titled “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), []), }}الاستخدام
Section titled “الاستخدام”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 titled “Next.js (App Router)”Client Component Hook
Section titled “Client Component Hook”في 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
Section titled “تكامل 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}متغيرات البيئة
Section titled “متغيرات البيئة”NEXT_PUBLIC_CMP_URL=https://tool.hashentry.com/cmp.jsNEXT_PUBLIC_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxNuxt 3
Section titled “Nuxt 3”إضافة (Plugin): cmp.client.ts
Section titled “إضافة (Plugin): 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
Section titled “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 titled “التكوين (Configuration)”export default defineNuxtConfig({ runtimeConfig: { public: { cmpUrl: 'https://tool.hashentry.com/cmp.js', cmpSiteKey: '', // Set via NUXT_PUBLIC_CMP_SITE_KEY env } }})Angular
Section titled “Angular”Service: cmp.service.ts
Section titled “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 titled “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) }}الاستخدام في Component
Section titled “الاستخدام في Component”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 titled “Svelte (5 / Runes)”Store: cmp.svelte.ts
Section titled “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), }}الاستخدام
Section titled “الاستخدام”<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> في التخطيط. للمكونات التفاعلية، استخدم أيًا من التكاملات أعلاه:
<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
Section titled “أنواع 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[]]> }}