Tier 2 — Advanced
⚠️ Beta API — può breakare in MINOR. Annuncio su Slack
#avacy-dev1 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
| Export | Type | Purpose |
|---|---|---|
init | function | (Tier 1 — programmatic boot, vedi Lifecycle) |
registerStoreProvider | function | Aggiungere un nuovo store.type |
CoreEvents | object | Event emitter (mitt) sui cambi di state — advanced, usa solo se i DOM events Tier 1 non bastano |
getConfig | function | Snapshot read-only della config merged |
BaseProvider | class | Superclasse per gli storage provider, vedi registerStoreProvider |
| Types | type | Vedi Types reference |
RawConsentresta una stringa opaca per chi scrive uno store:readConsent/writeConsentla leggono e scrivono così com'è.@avacy/cmp-webesporta comunque ancheserializeRawConsent/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.typenon è registrato ainit, throwError: Store provider 'X' not registered. - Registrare dopo
init/initCorenon 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 uninit/reloadsuccessivo. Nessun avviso se lo fai per errore.
See also
IStoreProvider— interfaccia da implementare
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 suContextData([K in keyof ContextData]: EventPayload<ContextData[K]>) — una key per bucket, automaticamente in sync: seContextDataguadagna un campo,CoreEventsTypelo eredita senza bisogno di aggiornare questo type a mano.
Per il signal "core è ready", subscribe a
'core'(non'ready'): la chiave del bucket ècore, ilready: booleanvive dentro.CoreEvents.on('core', ({ value }) => { if (value.ready) { /* ... */ } });
EventPayload<T> = { value: T; prev: T; meta?: ChangeMeta }.
meta.cause dice perché il bucket è cambiato:
cause | Quando |
|---|---|
boot | init del core e dei provider (anche a reload()) |
load | lettura del consenso salvato e stato derivato |
decision | una scelta salvata, da UI o da API |
record-update | patch al record del consenso, senza nuova scelta |
language | cambio lingua |
customization | build 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)
| Caso | Usa |
|---|---|
| Reagire a consenso utente in script user-facing | DOM avacy:consent-saved |
| Logging strutturato di tutte le mutazioni state | CoreEvents |
| Bridge nativo, RUM tool, GTM | DOM events |
| Scrivere addon TS che reagisce a cambi di lingua/config | CoreEvents |
💡 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.), preferiscigetData()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 hapurposes/specialFeatures/vendors. Quelle viste pre-computate esistono solo sulConsentdi Tier 1 (il valore ritornato dagetConsent()), che intersecaConsentStatecon{ purposes, specialFeatures, vendors, choices }. AncheConsentè esportato come tipo (import type { Consent } from '@avacy/cmp-web'). Chi osservaCoreEventsriceveConsentStatee, se gli servono quelle viste, le ricalcola dachoices(raggruppa pertypeeframework, chiaveid, valorehasConsent: finalità e special feature TCF hanno id in comune) o chiamagetConsent().
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,FrameworkTypeConsentState,EventPayload<T>,GlossaryEntry,ResolvedStack,StandardText,ContextDataConsent,CmpData,AvacyPublicApi,AvacyUserConfig,AcceptRejectOptions,PrivacyFramework,PrivacySelector,PrivacySelectorType(i tipi della superficie Tier 1)PrivacyChoice,PrivacyChoiceType,PrivacySettingConsentScreen,OnConsentPayload,OnConsentAcceptAllPayload,OnConsentRejectAllPayload,OnConsentCloseBannerPayload,OnConsentPersonalizedPayloadRawConsent,RawConsentMetadata,DecodedConsent,FrameworkDecodedConsentIStoreProvider,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.
| Export | Cos'è |
|---|---|
readInlineConfig() | Legge e fa il parse del blocco #avacy-config (null se assente o non valido). |
serializeRawConsent, parseRawConsent | Codec della stringa RawConsent (metadati + consenso per framework). |
GCM_CONSENT_TYPES | I 7 tipi Google Consent Mode, in ordine di purpose GCM (1 = ad_storage … 7 = security_storage). |
TCString | Riesportazione 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.