import { customElement, property } from 'lit/decorators.js' import { html, PropertyValues } from 'lit' import { PktElement } from '@/base-elements/element' import { consentStrings } from './strings' import '../button' interface ConsentItem { name: string consent: boolean } interface ConsentValue { items: ConsentItem[] } interface CookieConsentEvent { value: string | ConsentValue } type ConsentDetailsMap = Record type CookieEventHandler = (consent: CookieConsentEvent) => void interface CookieEvents { on(eventName: string, handler: CookieEventHandler): void off(eventName: string, handler: CookieEventHandler): void } interface CookieBannerApi { cookieConsent: { validateConsentCookie(): Promise getConsentCookie(): string | ConsentValue } openCookieModal(): void } declare global { interface Window { cookieBanner_devMode?: boolean cookieBanner_cookieDomain?: string | null cookieBanner_cookieSecure?: string | null cookieBanner_cookieExpiryDays?: string | null cookieBanner_googleAnalyticsId?: string | null cookieBanner_hotjarId?: string | null cookieBanner?: CookieBannerApi __cookieEvents?: CookieEvents } } let consentScriptPromise: Promise | null = null function loadConsentScript(): Promise { if (consentScriptPromise) return consentScriptPromise consentScriptPromise = new Promise((resolve, reject) => { if (document.querySelector('#oslo-consent-script')) { resolve() return } const script = document.createElement('script') script.src = 'https://cdn.web.oslo.kommune.no/cb/cb-v1.1.0.js' script.id = 'oslo-consent-script' script.onload = () => resolve() script.onerror = reject document.head.appendChild(script) const styles = document.createElement('link') styles.href = 'https://cdn.web.oslo.kommune.no/cb/cb-v1.1.0.css' styles.type = 'text/css' styles.rel = 'stylesheet' styles.id = 'oslo-consent-styles' document.head.appendChild(styles) }) return consentScriptPromise } const parseJsonOrPassThrough = (value: T | string): T => { if (typeof value !== 'string') return value try { return JSON.parse(value) as T } catch { return value as unknown as T } } const toConsentDetails = (consent: CookieConsentEvent): ConsentDetailsMap => { const parsed = parseJsonOrPassThrough(consent.value) return parsed.items.reduce((acc, item) => { acc[item.name] = item.consent return acc }, {}) } export type TPktConsentTriggerType = 'button' | 'link' | 'footerlink' | 'icon' export interface IPktConsent { devMode?: boolean cookieDomain?: string | null cookieSecure?: string | null cookieExpiryDays?: string | null hotjarId?: string | null googleAnalyticsId?: string | null i18nLanguage?: string triggerType?: TPktConsentTriggerType | null triggerText?: string | null } export class PktConsent extends PktElement implements IPktConsent { private _cookieEventHandler?: CookieEventHandler @property({ type: Boolean }) devMode: boolean = false @property({ type: String }) hotjarId: string | null = null @property({ type: String }) googleAnalyticsId: string | null = null @property({ type: String }) cookieDomain: string | null = null @property({ type: String }) cookieSecure: string | null = null @property({ type: String }) cookieExpiryDays: string | null = null @property({ type: String }) triggerType: TPktConsentTriggerType | null = 'button' @property({ type: String }) triggerText: string | null = null @property({ type: String }) i18nLanguage: string = 'nb' connectedCallback(): void { super.connectedCallback() const language = this.i18nLanguage as keyof typeof consentStrings.i18n this.triggerText = this.triggerText || consentStrings.i18n[language].contentPresentation.buttons.settings } disconnectedCallback(): void { super.disconnectedCallback() if (this._cookieEventHandler) { window.__cookieEvents?.off('CookieManager.setCookie', this._cookieEventHandler) } } /** * Parses a JSON string when needed; returns the input unchanged when it's * already a parsed object or when parsing fails. * Exposed for backwards compatibility — tests still rely on it. */ returnJsonOrObject(obj: T | string): T { return parseJsonOrPassThrough(obj) } emitCookieConsents(consent: CookieConsentEvent): void { this.dispatchEvent( new CustomEvent('toggle-consent', { detail: toConsentDetails(consent), bubbles: true, cancelable: false, }), ) } protected async firstUpdated(_changedProperties: PropertyValues): Promise { // cookieBanner_* identifiers are defined by the external cookie banner script /* eslint-disable camelcase */ window.cookieBanner_googleAnalyticsId = this.googleAnalyticsId window.cookieBanner_hotjarId = this.hotjarId if (this.cookieDomain) window.cookieBanner_cookieDomain = this.cookieDomain if (this.cookieSecure) window.cookieBanner_cookieSecure = this.cookieSecure if (this.cookieExpiryDays) window.cookieBanner_cookieExpiryDays = this.cookieExpiryDays if (this.devMode) window.cookieBanner_devMode = this.devMode /* eslint-enable camelcase */ await loadConsentScript() this.triggerInit() } triggerInit(): void { window.document.dispatchEvent( new Event('CookieBannerReady', { bubbles: true, cancelable: true, }), ) const banner = window.cookieBanner const events = window.__cookieEvents if (!banner || !events) return banner.cookieConsent.validateConsentCookie().then((hasConsent) => { if (!hasConsent) return const cookie = banner.cookieConsent.getConsentCookie() window.setTimeout(() => this.emitCookieConsents({ value: cookie }), 0) if (this._cookieEventHandler) { events.off('CookieManager.setCookie', this._cookieEventHandler) } this._cookieEventHandler = (consent) => this.emitCookieConsents(consent) events.on('CookieManager.setCookie', this._cookieEventHandler) }) } openModal(e: Event): void { e.preventDefault() if (!window.cookieBanner?.cookieConsent) { this.triggerInit() } setTimeout(() => window.cookieBanner?.openCookieModal()) } render() { // aria-haspopup: triggeren åpner consent-dialogen, den navigerer ikke if (this.triggerType === 'link') { return html`${this.triggerText}` } if (this.triggerType === 'footerlink') { return html`${this.triggerText}` } if (this.triggerType === 'icon') { return html`${this.triggerText}` } return html`${this.triggerText}` } } try { customElement('pkt-consent')(PktConsent) } catch (e) { console.warn('Forsøker å definere , men den er allerede definert') }