Skip to main content

Tier 2 — Advanced

⚠️ Beta API — può breakare in MINOR. Annuncio su Slack #avacy-dev 1 settimana prima.

Tier 2 è solo via TypeScript import. Non disponibile su window.Avacy.

Quando usare Tier 2​

  • Stai scrivendo uno storage provider custom (es. il consenso deve vivere in IndexedDB invece di cookie, oppure in una WebView nativa che espone un bridge custom)
  • Stai integrando il CMP in un sistema che ha bisogno di osservare cambi di state del CMP in modo programmatico (non solo via DOM events Tier 1)

Per il controllo programmatico sul timing del boot (animazioni hero, SPA, config async) usa la Tier 1 window.Avacy.init — non serve toccare l'import TS.

Indice export​

ExportTypePurpose
initfunction(Tier 1 — programmatic boot, vedi Lifecycle)
registerStoreProviderfunctionAggiungere un nuovo store.type
CoreEventsobjectEvent emitter (mitt) sui cambi di state — advanced, usa solo se i DOM events Tier 1 non bastano
getConfigfunctionSnapshot read-only della config merged
BaseProviderclassSuperclasse per gli storage provider, vedi registerStoreProvider
TypestypeVedi Types reference

RawConsent resta una stringa opaca per chi scrive uno store: readConsent/writeConsent la leggono e scrivono così com'è. @avacy/cmp-web esporta comunque anche serializeRawConsent / parseRawConsent, il codec di quella stringa: vedi Altri export.


registerStoreProvider​

Registra un'implementazione di IStoreProvider mappata a un store.type arbitrario. Da chiamare prima di init.

Quello che si registra è la classe, e il core la istanzia chiamando il suo metodo statico getInstance(ctx) (contratto IStoreProviderClass). Il modo più semplice per averlo è estendere BaseProvider, che lo fornisce già (singleton per classe) e dà accesso a this.context:

import { init, registerStoreProvider, BaseProvider } from '@avacy/cmp-web';
import type { IStoreProvider, RawConsent } from '@avacy/cmp-web';

class IndexedDbStore extends BaseProvider implements IStoreProvider {
getStorageTechnology(): string {
return 'IndexedDB'; // opzionale: la label mostrata nella sezione "Conservazione"
}
async readConsent(): Promise<RawConsent | null> {
// … leggi da IndexedDB
}
async writeConsent(consent: RawConsent): Promise<void> {
// … scrivi su IndexedDB
}
}

registerStoreProvider('indexeddb', IndexedDbStore);

await init({
store: { type: 'indexeddb', consentKey: 'avacy_consent' },
// …
});

Senza BaseProvider, la classe deve esporre da sé static getInstance(ctx): IStoreProvider: una classe con il solo costruttore fa fallire il boot.

Behavior​

  • I tipi built-in 'cookie' e 'local-storage' sono sovrascrivibili (last-write-wins, silenzioso) — utile per override avanzati.
  • Se config.store.type non è registrato a init, throw Error: Store provider 'X' not registered.
  • Registrare dopo init/initCore non lancia errori, ma non fa nulla di utile: il registro accetta la scrittura, ma la sessione corrente ha già istanziato il provider — la nuova registrazione vale solo per un init/reload successivo. Nessun avviso se lo fai per errore.

See also​


CoreEvents​

⚠️ Advanced — internal observation hook. Pensato per addon TS o tooling che hanno bisogno di osservare ogni mutazione del state interno. Per la logica consumer-facing usa i DOM events Tier 1 (events.md): coprono i casi normali con un contratto stabile e zero accoppiamento al state shape.

CoreEvents espone bucket interni del CMP — può cambiare in MINOR senza preavviso.

Event emitter (basato su mitt) che notifica ogni mutazione del state interno del CMP. Stesso state che vedi via getData(), ma con notifica push.

import { CoreEvents } from '@avacy/cmp-web';

CoreEvents.on('consent', ({ value, prev, meta }) => {
// meta.cause: 'load' al boot e al reload, 'decision' per una scelta, ...
console.log('consent changed', meta?.cause, { value, prev });
});

CoreEvents.on('language', ({ value, prev }) => {
console.log('language changed', { value, prev });
});

Available keys​

Una key per ogni bucket di state:

type CoreEventsType = {
core: EventPayload<{ ready: boolean }>;
config: EventPayload<ConfigType>;
storage: EventPayload<{ type: string; technology: string }>;
language: EventPayload<{ words: LanguageLabels; activeLanguage: string }>;
consent: EventPayload<ConsentState>;
customization: EventPayload<{ privacySettings: PrivacySettingsMap; vendorsCount?: number }>;
glossary: EventPayload<GlossaryEntry[]>;
stacks: EventPayload<ResolvedStack[]>;
standardTexts: EventPayload<StandardText[]>;
decodedConsent: EventPayload<DecodedConsent | undefined>;
acmAddtlConsent: EventPayload<{ value: string } | undefined>;
};

CoreEventsType è un mapped type su ContextData ([K in keyof ContextData]: EventPayload<ContextData[K]>) — una key per bucket, automaticamente in sync: se ContextData guadagna un campo, CoreEventsType lo eredita senza bisogno di aggiornare questo type a mano.

Per il signal "core è ready", subscribe a 'core' (non 'ready'): la chiave del bucket è core, il ready: boolean vive dentro.

CoreEvents.on('core', ({ value }) => { if (value.ready) { /* ... */ } });

EventPayload<T> = { value: T; prev: T; meta?: ChangeMeta }.

meta.cause dice perché il bucket è cambiato:

causeQuando
bootinit del core e dei provider (anche a reload())
loadlettura del consenso salvato e stato derivato
decisionuna scelta salvata, da UI o da API
record-updatepatch al record del consenso, senza nuova scelta
languagecambio lingua
customizationbuild pigra di impostazioni, glossario, stack, testi, conteggio vendor

Filtra sul motivo, non su meta.intent: un acceptAll() da API non ha intent ed è comunque una decision. Il core emette sempre meta ({} se chi scrive non dichiara il motivo); CoreEvents però è pubblico, quindi meta è un indizio, non una prova. Lo stesso meta arriva ai filters della config e alle righe di host.history.changes() degli add-on.

When to use it (vs DOM events)​

CasoUsa
Reagire a consenso utente in script user-facingDOM avacy:consent-saved
Logging strutturato di tutte le mutazioni stateCoreEvents
Bridge nativo, RUM tool, GTMDOM events
Scrivere addon TS che reagisce a cambi di lingua/configCoreEvents

💡 Non duplicare la stessa logica su entrambi. Scegli un canale.


getConfig​

Snapshot read-only della config merged (utente + remoto + default).

import { getConfig } from '@avacy/cmp-web';

const config = getConfig(); // Readonly<ContextData['config']> | null
console.log(config?.cmpVersion, config?.policyVersion);

ConfigType non è esportato da @avacy/cmp-web: il tipo della config fusa si ricava da ContextData['config'] (o da UserConfigType, che è Partial<ConfigType>).

Ritorna null se init non è ancora chiamato.

💡 Per uso "common" (lettura policyVersion, publisherCountryCode, ecc.), preferisci getData() Tier 1 — espone gli stessi campi più stato UI e altro.


Types reference​

UserConfigType​

interface UserConfigType extends Partial<ConfigType> {}

Tutti i campi opzionali; defaults applicati internamente da ConfigProvider. Vedi ConfigType per la shape completa.

ConfigType​

interface ConfigType {
store: { type: StoreType; consentKey: string };
core: { autoLoad?: boolean; addonsTimeoutMs?: number };
frameworks: FrameworkItem[];
loggers: ('console' | 'event')[];
addons: (string | { [name: string]: Record<string, unknown> })[];
language: LanguageCode;
assetPath: string;
remoteConfigName?: string;
remoteConfigUrl?: string;
remoteGVLName?: string;
publisherCountryCode: string;
policyVersion: number;
cmpVersion?: string;
consentExpiryDays: number;
vendorsCount?: number;
ui?: UiConfig;
autoLanguage?: boolean;
customLabels?: Partial<Record<LanguageCode, Partial<LanguageLabels>>>;
filters?: ContextFilters;
consentFilter?: (choices: PrivacyChoice[], meta: ChangeMeta) => PrivacyChoice[];
}

Il tipo ConfigType non è esportato (vedi getConfig): la shape è qui per riferimento. UserConfigType sì.

CmpData​

type CmpData = ContextData & {
ui: { cpcOpen: boolean };
};

interface ContextData {
core: { ready: boolean };
config: ConfigType;
/** Tipo/tecnologia dello storage effettivamente in uso (es. 'cookie') — utile
* quando la view deve mostrare la label risolta invece della sola config.store.type. */
storage: { type: string; technology: string };
language: { words: LanguageLabels; activeLanguage: string };
consent: ConsentState;
customization: { privacySettings: PrivacySettingsMap; vendorsCount?: number };
glossary: GlossaryEntry[];
/** Stack IAB risolti, in ordine whitelist. Vuoto se nessun framework configura una whitelist. */
stacks: ResolvedStack[];
/** Testi standard che un framework impone accanto a un'intera sezione (oggi: TCF, Features). */
standardTexts: StandardText[];
decodedConsent?: DecodedConsent;
acmAddtlConsent?: { value: string };
}

Returnato da getData() Tier 1. Il "ready" del CMP vive sotto data.core.ready (non al top-level): consistente con i bucket di CoreEvents. L'ui bucket per ora contiene solo cpcOpen — è il punto di estensione futuro per altri segnali di stato visivo. ResolvedStack è esportato per Tier 2 — vedi Altri types esportati.

ConsentState​

type ConsentState =
| { ready: false }
| { ready: true; valid: false }
| {
ready: true;
valid: true;
type: 'AcceptAll' | 'RejectAll' | 'CloseBanner' | 'Personalized';
timestamp: number;
choices: PrivacyChoice[];
};

⚠️ ConsentState (questo type) NON ha purposes/specialFeatures/vendors. Quelle viste pre-computate esistono solo sul Consent di Tier 1 (il valore ritornato da getConsent()), che interseca ConsentState con { purposes, specialFeatures, vendors, choices }. Anche Consent è esportato come tipo (import type { Consent } from '@avacy/cmp-web'). Chi osserva CoreEvents riceve ConsentState e, se gli servono quelle viste, le ricalcola da choices (raggruppa per type e framework, chiave id, valore hasConsent: finalità e special feature TCF hanno id in comune) o chiama getConsent().

PrivacyChoice​

interface PrivacyChoice {
framework: 'tcf' | 'gcm' | 'custom' | 'acm';
type: 'purpose' | 'vendor' | 'specialFeature' | 'customPurpose' | 'feature' | 'specialPurpose';
id: number | string; // numero per tcf/gcm/acm, stringa per i fornitori custom (es. 'd116')
hasConsent?: boolean;
hasLegitimateInterest?: boolean;
}

hasConsent e hasLegitimateInterest sono entrambi opzionali: i due tipi 'feature' e 'specialPurpose' sono disclosure-only (TCF) e non hanno toggle di consenso, quindi non possono apparire nei PrivacySelector accettati da acceptAll({except}) / rejectAll({except}) / updateConsent ma possono comparire in consent.choices come metadata.

IStoreProvider​

interface IStoreProvider {
readConsent(): Promise<RawConsent | null>;
writeConsent(consent: RawConsent): Promise<void>;
/** Opzionale: label della tecnologia (es. 'cookie', 'IndexedDB'); assente ⇒ si usa la chiave di store.type. */
getStorageTechnology?(): string;
/** Opzionale: il cassetto chiave-valore che il core passa agli add-on; assente ⇒ il core tiene i valori in memoria. */
getAddonStorage?(): AddonStorage;
}

L'interfaccia da implementare per scrivere uno store custom: due metodi obbligatori, due opzionali. Oltre all'interfaccia serve il metodo statico getInstance sulla classe (vedi registerStoreProvider). RawConsent è una string opaca — non c'è bisogno di parserla per la maggior parte dei casi (read → leggi raw dal tuo storage; write → salva raw nel tuo storage).

OnConsentPayload​

type ConsentScreen = 'first' | 'second' | 'api';

type OnConsentPayload =
| { type: 'AcceptAll'; screen: ConsentScreen }
| { type: 'RejectAll'; screen: ConsentScreen }
| { type: 'CloseBanner'; screen: ConsentScreen }
| { type: 'Personalized'; screen: ConsentScreen; privacyChoices: PrivacyChoice[] };

screen è un campo audit (drive il consentScreen nel TCString), obbligatorio nel tipo. window.Avacy.triggerConsent lo sostituisce con 'api' se manca o non è valido; acceptAll/rejectAll/updateConsent stampano sempre 'api'; banner/CPC components passano 'first'/'second' direttamente al core. CloseBanner non scrive consenso (vedi Consent actions).

Altri types esportati​

  • UserConfigType, UiConfig, StoreType, FrameworkType
  • ConsentState, EventPayload<T>, GlossaryEntry, ResolvedStack, StandardText, ContextData
  • Consent, CmpData, AvacyPublicApi, AvacyUserConfig, AcceptRejectOptions, PrivacyFramework, PrivacySelector, PrivacySelectorType (i tipi della superficie Tier 1)
  • PrivacyChoice, PrivacyChoiceType, PrivacySetting
  • ConsentScreen, OnConsentPayload, OnConsentAcceptAllPayload, OnConsentRejectAllPayload, OnConsentCloseBannerPayload, OnConsentPersonalizedPayload
  • RawConsent, RawConsentMetadata, DecodedConsent, FrameworkDecodedConsent
  • IStoreProvider, GcmConsentType

Non sono esportati da @avacy/cmp-web (anche se esistono nel core): ConfigType, ConsentPatches, IStoreProviderClass, FrameworkRawConsent, FrameworkRawConsentMap, LoggerType, FrameworkItem, FrameworkConfigItem, CoreEventsType, ProviderInstanceMap, ProviderName.

Altri export (stato da definire)​

Presenti in packages/cmp-web/src/index.ts ma non documentati come API stabile: esistono, ma se diventeranno pubblici, con che contratto, non è ancora deciso. Non costruirci sopra integrazioni.

ExportCos'è
readInlineConfig()Legge e fa il parse del blocco #avacy-config (null se assente o non valido).
serializeRawConsent, parseRawConsentCodec della stringa RawConsent (metadati + consenso per framework).
GCM_CONSENT_TYPESI 7 tipi Google Consent Mode, in ordine di purpose GCM (1 = ad_storage … 7 = security_storage).
TCStringRiesportazione del codec di @iabtechlabtcf/core, la stessa istanza usata dal core.
getContextData()Lo stato del core senza il bucket ui (Tier 1: getData()).

Export per il loader (non API pubblica)​

registerAddon, i tipi AddonDefinition, AddonHost, AddonStorage, AddonHistory, AddonHistoryEvent, AddonHistoryChange, AddonListener, AddonEventPayloadMap, CoreInitOptions e InitOptions (il secondo parametro di init()) sono esportati perché il loader v3 li usa: non sono API pubblica e possono cambiare senza preavviso. Come funzionano: Internals — Core › Add-on e Internals — cmp-web › init.