universal-ad-loader 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,16 +1,24 @@
1
+ # 0.1.1
2
+
3
+ **Changes**
4
+ - Documentation (README, guides, changelog) translated to English.
5
+ - `engines`: Node 22 or later.
6
+ - `browser`, `jsdelivr` and `unpkg` fields point to `dist/ual.min.js`, so the CDN default file is the loader.
7
+ - The internal publishing guide (`docs/pubblicazione-npm.md`) is no longer included in the package.
8
+
1
9
  # 0.1.0
2
10
 
3
- Prima versione.
11
+ First release.
4
12
 
5
- **Novità**
6
- - Loader da servire con un tag `<script>` (`dist/ual.min.js`): slot, configurazione JSON centralizzata,
7
- lazy load, waterfall tra provider, consenso IAB TCF, render in iframe isolato, viewability ed eventi.
8
- - Adapter: `json`, `iframe`, `revive`, `revive-async`, `gpt`, `adsense`, `house`.
9
- - Nucleo come libreria (ESM e CJS), sicuro da importare sul server: `createUal`, `getUal`, `defineConfig`.
10
- - Componenti per React, Next.js, Vue e Astro, che avviano UAL dopo l'idratazione e gestiscono il cambio pagina.
11
- - Tipi TypeScript e JSON Schema della configurazione.
13
+ **What's new**
14
+ - Loader served with a `<script>` tag (`dist/ual.min.js`): slots, centralized JSON configuration,
15
+ lazy load, waterfall across providers, IAB TCF consent, isolated iframe rendering, viewability and events.
16
+ - Adapters: `json`, `iframe`, `revive`, `revive-async`, `gpt`, `adsense`, `house`.
17
+ - Core as a library (ESM and CJS), safe to import on the server: `createUal`, `getUal`, `defineConfig`.
18
+ - Components for React, Next.js, Vue and Astro, which start UAL after hydration and handle page changes.
19
+ - TypeScript types and a JSON Schema of the configuration.
12
20
 
13
- **Da sapere**
14
- - `adsense`, `revive-async` e `gpt` sono scritti sulle API pubbliche ma non provati su account reali.
15
- - Nuxt, Remix e React Router non sono stati provati.
16
- - Con una CSP rigida i banner compaiono ma `UALExt` e il click tracciato non funzionano senza `'unsafe-inline'`.
21
+ **Good to know**
22
+ - `adsense`, `revive-async` and `gpt` are written against the public APIs but have not been tried on real accounts.
23
+ - Nuxt, Remix and React Router have not been tried.
24
+ - With a strict CSP banners show up, but `UALExt` and the tracked click do not work without `'unsafe-inline'`.
package/README.md CHANGED
@@ -1,67 +1,67 @@
1
1
  # UAL — Universal Ad Loader
2
2
 
3
- Un solo tag per sito. Gestisce slot, configurazione centralizzata, consenso TCF, lazy load, waterfall tra provider (adapter), render in sandbox, viewability ed eventi. ~10 KB gzip.
3
+ One tag per site. It handles slots, centralized configuration, TCF consent, lazy loading, waterfalls across providers (adapters), sandboxed rendering, viewability and events. ~10 KB gzip.
4
4
 
5
- ## Installazione su un sito
5
+ ## Installing on a site
6
6
 
7
7
  ```html
8
- <script async src="https://adcdn.tuodominio.net/ual.min.js"
9
- data-config="https://adcdn.tuodominio.net/config/site_123.json"></script>
8
+ <script async src="https://adcdn.yourdomain.net/ual.min.js"
9
+ data-config="https://adcdn.yourdomain.net/config/site_123.json"></script>
10
10
 
11
11
  <div data-ad-slot="top"></div>
12
12
  <div data-ad-slot="sidebar" data-ad-params='{"zone":7}'></div>
13
13
  ```
14
14
 
15
- - `data-ad-slot` è il nome dello slot in configurazione. Lo stesso nome può comparire più volte (es. in-feed).
16
- - `data-ad-params` (opzionale) sovrascrive i `params` del provider per quell'istanza.
17
- - I div aggiunti dopo (SPA, infinite scroll) vengono rilevati da soli. Al cambio di route chiama `ual.pageView()`.
15
+ - `data-ad-slot` is the slot name in the configuration. The same name can appear more than once (e.g. in-feed).
16
+ - `data-ad-params` (optional) overrides the provider's `params` for that instance.
17
+ - Divs added later (SPA, infinite scroll) are detected automatically. On a route change, call `ual.pageView()`.
18
18
 
19
- > **Cosa caricare sulla CDN:** il loader (`dist/ual.min.js`) e un JSON di configurazione per sito, con gli header giusti: vedi `docs/cdn.md`.
19
+ > **What to upload to the CDN:** the loader (`dist/ual.min.js`) and one configuration JSON per site, with the right headers: see `docs/cdn.md`.
20
20
 
21
- ## Configurazione (JSON servito da CDN)
21
+ ## Configuration (JSON served from the CDN)
22
22
 
23
- Vedi `examples/site_demo.json`. Campi principali:
23
+ See `examples/site_demo.json`. Main fields:
24
24
 
25
- | Campo | Descrizione |
25
+ | Field | Description |
26
26
  |---|---|
27
- | `site` | ID del sito, inviato agli adapter |
27
+ | `site` | Site ID, sent to the adapters |
28
28
  | `consent` | `{ mode:'tcf'\|'none', timeoutMs, requiredPurposes:[1], vendorId }` |
29
- | `lazy` | `{ enabled, fetchMargin:'200px' }`. Lo slot viene richiesto quando arriva a quella distanza dal viewport |
30
- | `viewability` | `{ threshold:0.5, durationMs:1000 }` (standard MRC) |
31
- | `collapseEmpty` | nasconde lo slot se nessun provider lo riempie |
32
- | `providerTimeoutMs` | timeout di default per provider |
33
- | `targeting` | key-value di pagina inviati a tutti gli adapter |
34
- | `providers` | `{ id: { adapter:'json'\|'iframe'\|'revive'\|'revive-async'\|'gpt'\|'adsense'\|'house', consent:'optional'\|'required'\|'contextual', timeoutMs, ...opzioni adapter } }` |
35
- | `slots` | `{ nome: { sizes \| sizeMapping, providers:[...waterfall], label, minHeight, lazy, collapseEmpty, refreshSec, maxRefresh, enabled } }` |
29
+ | `lazy` | `{ enabled, fetchMargin:'200px' }`. The slot is requested when it comes within that distance of the viewport |
30
+ | `viewability` | `{ threshold:0.5, durationMs:1000 }` (MRC standard) |
31
+ | `collapseEmpty` | hides the slot if no provider fills it |
32
+ | `providerTimeoutMs` | default timeout per provider |
33
+ | `targeting` | page-level key-values sent to all adapters |
34
+ | `providers` | `{ id: { adapter:'json'\|'iframe'\|'revive'\|'revive-async'\|'gpt'\|'adsense'\|'house', consent:'optional'\|'required'\|'contextual', timeoutMs, ...adapter options } }` |
35
+ | `slots` | `{ name: { sizes \| sizeMapping, providers:[...waterfall], label, minHeight, lazy, collapseEmpty, refreshSec, maxRefresh, enabled } }` |
36
36
  | `pageRules` | `[{ match:'^/on-air', disable:[...], enable:[...], targeting:{}, slots:{ override } }]` |
37
37
 
38
- **Waterfall**: `providers` di uno slot è una lista ordinata (`"house"` oppure `{ "id":"main", "params":{...} }`). Se il primo non riempie, si passa al successivo. Per ogni livello, gli slot pronti insieme vengono raggruppati in **una sola richiesta per provider**.
38
+ **Waterfall**: a slot's `providers` is an ordered list (`"house"` or `{ "id":"main", "params":{...} }`). If the first one does not fill the slot, the next one is tried. At each level, the slots that become ready together are grouped into **a single request per provider**.
39
39
 
40
- **Consenso per provider** (`providers.X.consent`):
40
+ **Consent per provider** (`providers.X.consent`):
41
41
 
42
- | Valore | Comportamento |
42
+ | Value | Behavior |
43
43
  |---|---|
44
- | `optional` (default) | Aspetta la risposta della CMP (max `consent.timeoutMs`) e riceve il consenso così com'è |
45
- | `required` | Come `optional`, ma se il consenso non c'è il provider viene **saltato** e si passa al livello successivo (es. un fallback house contestuale) |
46
- | `contextual` | **Non aspetta la CMP**: parte subito e riceve il consenso noto in quel momento (spesso `granted:false`). Per richieste contestuali senza cookie né ID |
44
+ | `optional` (default) | Waits for the CMP response (up to `consent.timeoutMs`) and receives the consent as it is |
45
+ | `required` | Like `optional`, but if consent is missing the provider is **skipped** and the next level is tried (e.g. a contextual house fallback) |
46
+ | `contextual` | **Does not wait for the CMP**: starts immediately and receives the consent known at that moment (often `granted:false`). For contextual requests with no cookies or IDs |
47
47
 
48
- Il consenso viene seguito per tutta la vita della pagina: se la CMP compare in ritardo (fino a `consent.lateWatchMs`, default 30 s) o l'utente cambia scelta, UAL emette l'evento `consent` e, quando si passa a `granted:true`, ricarica gli slot che avevano saltato un provider `required`.
48
+ Consent is followed for the whole life of the page: if the CMP shows up late (up to `consent.lateWatchMs`, default 30 s) or the user changes their choice, UAL emits the `consent` event and, when it switches to `granted:true`, reloads the slots that had skipped a `required` provider.
49
49
 
50
- ## Adapter inclusi
50
+ ## Included adapters
51
51
 
52
- | Adapter | Uso | Rileva il vuoto? |
52
+ | Adapter | Use | Detects empty? |
53
53
  |---|---|---|
54
- | `json` | Il tuo ad server: POST batch su `endpoint` (contratto sotto) | Sì |
55
- | `iframe` | Qualsiasi URL che restituisce il banner (`work.php?n=..&size=..`) con template `{params.x}`, `{width}`, `{cb}`… | No (considerato pieno) |
54
+ | `json` | Your ad server: batch POST to `endpoint` (contract below) | Yes |
55
+ | `iframe` | Any URL that returns the banner (`work.php?n=..&size=..`) with `{params.x}`, `{width}`, `{cb}`… templates | No (treated as filled) |
56
56
  | `revive` | Revive Adserver via `afr.php` (iframe) | No |
57
- | `revive-async` | Revive via `asyncjs.php` + `<ins>` | Sì (osserva il DOM) |
58
- | `gpt` | Google Ad Manager, SRA con `disableInitialLoad` + `refresh` | Sì (`slotRenderEnded`) |
59
- | `adsense` | AdSense come backfill | Sì (`data-ad-status`) |
60
- | `house` | Creatività in config, scelta pesata per slot/dispositivo/date | Sì |
57
+ | `revive-async` | Revive via `asyncjs.php` + `<ins>` | Yes (observes the DOM) |
58
+ | `gpt` | Google Ad Manager, SRA with `disableInitialLoad` + `refresh` | Yes (`slotRenderEnded`) |
59
+ | `adsense` | AdSense as backfill | Yes (`data-ad-status`) |
60
+ | `house` | Creatives in the config, weighted pick by slot/device/date | Yes |
61
61
 
62
- ### Contratto dell'adapter `json`
62
+ ### The `json` adapter contract
63
63
 
64
- Richiesta `POST {endpoint}`:
64
+ Request `POST {endpoint}`:
65
65
  ```json
66
66
  {
67
67
  "site": "site_123",
@@ -72,7 +72,7 @@ Richiesta `POST {endpoint}`:
72
72
  "slots": [ { "id": "top#1", "name": "top", "sizes": [[728,90]], "params": { "zone": 1 } } ]
73
73
  }
74
74
  ```
75
- Risposta (200 oppure 204 se vuota):
75
+ Response (200, or 204 if empty):
76
76
  ```json
77
77
  { "ads": [ {
78
78
  "slotId": "top#1", "type": "image", "src": "https://cdn/.../b.webp",
@@ -80,28 +80,28 @@ Risposta (200 oppure 204 se vuota):
80
80
  "trackers": { "impression": ["https://ads/i?t=..."], "viewable": ["https://ads/v?t=..."], "click": [] }
81
81
  } ] }
82
82
  ```
83
- `type`: `image` (src + clickUrl), `html` (markup, `clickTag` disponibile), `url` (iframe esterno). Gli slot senza voce passano al provider successivo.
83
+ `type`: `image` (src + clickUrl), `html` (markup, `clickTag` available), `url` (external iframe). Slots with no entry fall through to the next provider.
84
84
 
85
- ## Render e API per le creatività
85
+ ## Rendering and creative API
86
86
 
87
- Le creatività `image`/`html` girano in un iframe `srcdoc` con sandbox **senza** `allow-same-origin` (origine opaca): non possono leggere la pagina, i cookie o il DOM del sito. Dentro l'iframe sono disponibili:
87
+ `image`/`html` creatives run in a `srcdoc` iframe with a sandbox **without** `allow-same-origin` (opaque origin): they cannot read the page, its cookies or its DOM. Inside the iframe the following are available:
88
88
 
89
89
  ```js
90
90
  clickTag // = clickUrl
91
- UALExt.click() // traccia il click e apre clickTag
92
- UALExt.expand(728, 300) // ridimensiona (max 2000px)
93
- UALExt.collapse() // torna alla dimensione originale
94
- UALExt.close() // chiude e nasconde lo slot
91
+ UALExt.click() // tracks the click and opens clickTag
92
+ UALExt.expand(728, 300) // resizes (max 2000px)
93
+ UALExt.collapse() // back to the original size
94
+ UALExt.close() // closes and hides the slot
95
95
  ```
96
- I messaggi vengono accettati solo dall'iframe dello slot (controllo su `event.source`).
96
+ Messages are accepted only from the slot's own iframe (checked on `event.source`).
97
97
 
98
- **Validazione delle creatività.** Ogni annuncio ricevuto da un adapter passa da un controllo prima del render; se non è valido viene scartato (log + evento `error`) e si passa al provider successivo:
98
+ **Creative validation.** Every ad received from an adapter goes through a check before rendering; if it is not valid it is discarded (log + `error` event) and the next provider is tried:
99
99
 
100
- - `width` e `height` obbligatori, interi tra 1 e 2000.
101
- - `src` (image), `url` (url), `clickUrl` e tutti i `trackers`: solo `http:`/`https:` (per `src` anche `data:image/`). Niente `javascript:`.
102
- - Sandbox: mai `allow-same-origin` per `image`/`html`; mai `allow-top-navigation` (la creatività non può reindirizzare la pagina; i link `target=_blank` aprono un popup). Per `type:'url'` `allow-same-origin` resta (serve ai server legacy) **solo se l'URL è di un'altra origine** della pagina; se è della stessa origine viene tolto. L'override `sandbox` di una creatività è filtrato con le stesse regole.
100
+ - `width` and `height` are required, integers between 1 and 2000.
101
+ - `src` (image), `url` (url), `clickUrl` and all `trackers`: only `http:`/`https:` (and `data:image/` for `src`). No `javascript:`.
102
+ - Sandbox: never `allow-same-origin` for `image`/`html`; never `allow-top-navigation` (a creative cannot redirect the page; `target=_blank` links open a popup). For `type:'url'`, `allow-same-origin` is kept (legacy servers need it) **only if the URL has a different origin from the page**; if it is the same origin it is removed. A creative's `sandbox` override is filtered with the same rules.
103
103
 
104
- ## API JavaScript
104
+ ## JavaScript API
105
105
 
106
106
  ```js
107
107
  window.ual = window.ual || { cmd: [] };
@@ -113,97 +113,96 @@ ual.cmd.push(function (u) {
113
113
  u.on('error', (e) => {});
114
114
  u.setTargeting({ author: 'mario' });
115
115
  });
116
- // Senza data-config: ual.cmd.push(u => u.init({ ...config })) oppure u.init('https://.../site.json')
117
- ual.refresh(['top']); // ricarica slot (tutti se senza argomenti)
118
- ual.pageView(); // SPA: nuova pagina
116
+ // Without data-config: ual.cmd.push(u => u.init({ ...config })) or u.init('https://.../site.json')
117
+ ual.refresh(['top']); // reloads slots (all of them if called without arguments)
118
+ ual.pageView(); // SPA: new page
119
119
  ual.getSlots(); ual.getConsent();
120
- ual.isInitialized(); // true se init è già stato avviato (anche se non ancora finito)
121
- ual.whenReady(); // la promessa dell'init, o null se mai avviato
120
+ ual.isInitialized(); // true if init has already been started (even if not finished yet)
121
+ ual.whenReady(); // the init promise, or null if never started
122
122
  ```
123
- `setTargeting` funziona anche prima dell'init (dentro `ual.cmd.push`), resta valido dopo `pageView()` e un valore `null` rimuove la chiave. Il targeting delle `pageRules` viene ricalcolato a ogni pagina. L'adapter `gpt` lo riapplica a ogni richiesta e toglie le chiavi non più presenti.
123
+ `setTargeting` also works before init (inside `ual.cmd.push`), stays valid after `pageView()`, and a `null` value removes the key. The `pageRules` targeting is recomputed on every page. The `gpt` adapter reapplies it on every request and removes keys that are no longer present.
124
124
 
125
- L'attributo sullo script può chiamarsi `data-config` o `data-ual-config` (è equivalente; il secondo si legge anche se lo script è stato inserito dinamicamente da un framework).
125
+ The script attribute can be named `data-config` or `data-ual-config` (equivalent; the second is also read when the script was inserted dynamically by a framework).
126
126
 
127
- Debug: `?ual_debug=1` nell'URL, oppure `localStorage.ual_debug = '1'`.
127
+ Debug: `?ual_debug=1` in the URL, or `localStorage.ual_debug = '1'`.
128
128
 
129
- ## Con Astro, Next.js, React, Vue e Node
129
+ ## With Astro, Next.js, React, Vue and Node
130
130
 
131
- UAL è uno script del browser (non gira in Node). Per i siti con una build c'è anche il **pacchetto**: import sicuro sul server, componenti per React, Next.js, Vue e Astro, tipi TypeScript (`defineConfig`). Vedi `docs/libreria.md`. **Il pacchetto `universal-ad-loader` non è ancora pubblicato su npm** (vedi `docs/pubblicazione-npm.md`): per ora si installa dal tarball di `npm pack`.
131
+ UAL is a browser script (it does not run in Node). For sites with a build step there is also the **package**: server-safe import, components for React, Next.js, Vue and Astro, TypeScript types (`defineConfig`). See `docs/libreria.md`. **The `universal-ad-loader` package is published on npm** (`npm i universal-ad-loader`).
132
132
 
133
133
  ```tsx
134
- import { UalProvider, AdSlot } from 'universal-ad-loader/next'; // o /react, /vue; per Astro i file in astro/
134
+ import { UalProvider, AdSlot } from 'universal-ad-loader/next'; // or /react, /vue; for Astro use the files in astro/
135
135
 
136
- <UalProvider config={config}> {/* parte dopo l'idratazione, gestisce il cambio pagina */}
136
+ <UalProvider config={config}> {/* starts after hydration, handles page changes */}
137
137
  <AdSlot name="top" minHeight={90} />
138
138
  </UalProvider>
139
139
  ```
140
140
 
141
- Se usi invece il tag con un framework, cose verificate con un'app React server-rendered:
141
+ If you use the tag with a framework instead, things verified with a server-rendered React app:
142
142
 
143
- - **Carica il loader dopo l'idratazione** (`next/script` con `strategy="afterInteractive"`, o un `useEffect`). Se parte prima, UAL inserisce il banner nel `div` prima che React si attacchi: errore *Hydration failed* (anche con `suppressHydrationWarning`) e banner richiesto due volte. I componenti lo evitano da soli.
144
- - **Cambio pagina lato client:** gli slot nuovi si registrano da soli, ma `pageRules` e targeting si ricalcolano solo con `ual.pageView()`.
145
- - **Non importare `dist/ual.js` in codice server:** usa `window` al caricamento (`ReferenceError` in Node). Il nucleo del pacchetto (`dist/index.*`) invece è sicuro.
146
- - **CSP rigida:** le creatività ereditano la CSP della pagina. Provato: senza `'unsafe-inline'` per script e stili il banner compare ma `UALExt` e il click tracciato non funzionano; con nonce e `'strict-dynamic'` nemmeno.
143
+ - **Load the loader after hydration** (`next/script` with `strategy="afterInteractive"`, or a `useEffect`). If it starts earlier, UAL inserts the banner into the `div` before React attaches: *Hydration failed* error (even with `suppressHydrationWarning`) and the banner is requested twice. The components avoid this on their own.
144
+ - **Client-side page change:** new slots register themselves, but `pageRules` and targeting are only recomputed with `ual.pageView()`.
145
+ - **Do not import `dist/ual.js` in server code:** it uses `window` at load time (`ReferenceError` in Node). The package core (`dist/index.*`) is safe, though.
146
+ - **Strict CSP:** creatives inherit the page's CSP. Tested: without `'unsafe-inline'` for scripts and styles the banner shows up but `UALExt` and the tracked click do not work; the same goes for nonces with `'strict-dynamic'`.
147
147
 
148
- Guida con esempi e tabella CSP: configuratore, pagina `/docs/`.
148
+ Guide with examples and a CSP table: configurator, `/docs/` page.
149
149
 
150
- ## Altri documenti
150
+ ## Other documents
151
151
 
152
- - `docs/esempio.md`: sito cliente completo (config, flusso, endpoint `decide` minimo).
153
- - `docs/pubblicazione-npm.md`: come pubblicare il pacchetto su npm (CI GitLab con il branch `deploy-npm`, token, mirror GitHub, rilasci).
154
- - `docs/cdn.md`: **quali file mettere sulla CDN** (loader e configurazione), header, struttura, cosa non serve e come aggiornare.
155
- - `docs/libreria.md`: UAL come pacchetto (componenti React/Next/Vue/Astro, tipi, nucleo sicuro sul server, come è verificato).
156
- - `docs/ad-server-esistenti.md`: come collegare AdManager, Banner Exchange Pro, Banner Exchange e i PHP legacy con l'adapter `iframe`, con limiti e verifiche.
157
- - `schema/ual-config.schema.json`: JSON Schema della configurazione.
158
- - Il configuratore web (`../ual-configurator`) ha una guida HTML con esempi apribili nell'editor.
152
+ - `docs/esempio.md`: complete client site (config, flow, minimal `decide` endpoint).
153
+ - `docs/cdn.md`: **which files to put on the CDN** (loader and configuration), headers, structure, what is not needed and how to update.
154
+ - `docs/libreria.md`: UAL as a package (React/Next/Vue/Astro components, types, server-safe core, how it is verified).
155
+ - `docs/ad-server-esistenti.md`: how to connect AdManager, Banner Exchange Pro, Banner Exchange and the legacy PHP systems with the `iframe` adapter, with limits and checks.
156
+ - `schema/ual-config.schema.json`: JSON Schema of the configuration.
157
+ - The web configurator (`../ual-configurator`) has an HTML guide with examples that open in the editor.
159
158
 
160
- ## Adapter custom
159
+ ## Custom adapters
161
160
 
162
161
  ```js
163
- ual.cmd.push(u => u.registerAdapter('mioserver', {
164
- init(providerCfg, ctx) {}, // opzionale, una volta per provider
165
- // A) l'adapter restituisce gli annunci e UAL li rende in sandbox:
162
+ ual.cmd.push(u => u.registerAdapter('myserver', {
163
+ init(providerCfg, ctx) {}, // optional, once per provider
164
+ // A) the adapter returns the ads and UAL renders them in the sandbox:
166
165
  async request(slots, providerCfg, ctx) { // slots: [{id,name,sizes,params,el}]
167
166
  return { [slots[0].id]: { type:'image', src:'...', width:300, height:250, clickUrl:'...' } };
168
167
  },
169
- // B) oppure l'adapter rende da solo dentro slot.el:
168
+ // B) or the adapter renders by itself inside slot.el:
170
169
  // async fill(slots, providerCfg, ctx) { return { [id]: { filled:true, width, height } } },
171
- destroy(slot, providerCfg) {}, // opzionale
170
+ destroy(slot, providerCfg) {}, // optional
172
171
  }));
173
172
  ```
174
- `ctx` contiene `site, page, device, viewport, targeting, consent`. Per registrare un adapter custom prima dell'init automatico, metti `ual.cmd.push(...)` **prima** del tag del loader.
173
+ `ctx` contains `site, page, device, viewport, targeting, consent`. To register a custom adapter before the automatic init, put `ual.cmd.push(...)` **before** the loader tag.
175
174
 
176
- ## Build e test
175
+ ## Build and tests
177
176
 
178
177
  ```bash
179
- npm ci # dipendenze di base (~120 MB)
180
- npm ci --prefix test/heavy # solo per i test dei framework: Next.js e Astro (~620 MB)
181
- npx playwright install chromium # una tantum, per i test in browser
182
- npm test # build + tutte le suite, circa 2 minuti
178
+ npm ci # base dependencies (~120 MB)
179
+ npm ci --prefix test/heavy # only for the framework tests: Next.js and Astro (~620 MB)
180
+ npx playwright install chromium # once, for the browser tests
181
+ npm test # build + all suites, about 2 minutes
183
182
  ```
184
183
 
185
- Per lavorare per parti (è lo stesso che fa la CI, in job paralleli):
184
+ To work on one part at a time (this is what CI does, in parallel jobs):
186
185
 
187
- | Comando | Cosa esegue | Serve |
186
+ | Command | What it runs | Needs |
188
187
  |---|---|---|
189
- | `npm run lint` | oxlint sui sorgenti | solo le dipendenze di base |
190
- | `npm run test:node` | `test/package.mjs` | solo le dipendenze di base (nessun browser) |
191
- | `npm run test:browser` | `test/run.mjs` e `test/regression.mjs` | + Chromium |
192
- | `npm run test:frameworks` | `test/wrappers.mjs` | + `test/heavy` e Chromium |
188
+ | `npm run lint` | oxlint on the sources | base dependencies only |
189
+ | `npm run test:node` | `test/package.mjs` | base dependencies only (no browser) |
190
+ | `npm run test:browser` | `test/run.mjs` and `test/regression.mjs` | + Chromium |
191
+ | `npm run test:frameworks` | `test/wrappers.mjs` | + `test/heavy` and Chromium |
193
192
 
194
- Ogni suite scrive anche un report JUnit in `test-results/` (la CI lo mostra nella scheda Tests).
193
+ Every suite also writes a JUnit report to `test-results/` (CI shows it in the Tests tab).
195
194
 
196
- | Suite | Cosa verifica |
195
+ | Suite | What it verifies |
197
196
  |---|---|
198
- | `test/run.mjs` | e2e del loader con ad server finto e CMP finta: batch per provider, waterfall verso house, lazy load, `sizeMapping`, consenso, `pageRules`, tracker, expand, sandbox |
199
- | `test/regression.mjs` | sicurezza del render, `setTargeting`, CMP in ritardo, provider `contextual`, slot spostati/chiusi nel DOM, refresh, `pageView`, adapter `gpt` (con uno stub di `googletag`), adapter `iframe` con i sistemi esistenti |
200
- | `test/package.mjs` | il pacchetto: import in Node (ESM e CJS) senza `window`, rendering sul server di React e Vue, mappa `exports`, contenuto del tarball (`npm pack`), tipi (usi corretti compilano, quelli errati danno errore) |
201
- | `test/wrappers.mjs` | i componenti in un browser vero, con rendering sul server e idratazione ritardata: React (nel bundle, da CDN, versione Next), Vue, **Next.js 16** (App Router, build di produzione) e **Astro 7** (View Transitions); più un controllo che dimostra il problema che i componenti evitano |
197
+ | `test/run.mjs` | loader e2e with a fake ad server and a fake CMP: batching per provider, waterfall to house, lazy load, `sizeMapping`, consent, `pageRules`, trackers, expand, sandbox |
198
+ | `test/regression.mjs` | render security, `setTargeting`, late CMP, `contextual` providers, slots moved/closed in the DOM, refresh, `pageView`, the `gpt` adapter (with a `googletag` stub), the `iframe` adapter with the existing systems |
199
+ | `test/package.mjs` | the package: import in Node (ESM and CJS) without `window`, server rendering of React and Vue, `exports` map, tarball contents (`npm pack`), types (correct usages compile, wrong ones fail) |
200
+ | `test/wrappers.mjs` | the components in a real browser, with server rendering and delayed hydration: React (in the bundle, from CDN, Next version), Vue, **Next.js 16** (App Router, production build) and **Astro 7** (View Transitions); plus a check that demonstrates the problem the components avoid |
202
201
 
203
- Gli adapter `adsense` e `revive-async` sono scritti sulle API pubbliche ma non sono testati: vanno provati sui tuoi account e server. Anche `gpt` va provato con Google Ad Manager reale: lo stub verifica la logica di UAL, non il comportamento di GPT. Non sono stati eseguiti Nuxt, Remix e React Router.
202
+ The `adsense` and `revive-async` adapters are written against the public APIs but are not tested: they need to be tried on your own accounts and servers. `gpt` too must be tried with a real Google Ad Manager: the stub verifies UAL's logic, not GPT's behavior. Nuxt, Remix and React Router have not been run.
204
203
 
205
- ## Note
204
+ ## Notes
206
205
 
207
- - **Consenso**: su siti di clienti il tuo ad server è un fornitore terzo. Senza consenso, invia solo richieste contestuali (niente cookie né ID). `vendorId` serve se il tuo server è registrato nella GVL IAB. Fai verificare l'impostazione privacy a un legale.
208
- - **Refresh automatico** (`refreshSec`): solo se lo slot è a schermo e la tab è visibile. GPT e AdSense hanno policy proprie sul refresh.
209
- - **CLS**: usa `minHeight` sugli slot sopra la piega.
206
+ - **Consent**: on client sites your ad server is a third-party vendor. Without consent, send only contextual requests (no cookies or IDs). `vendorId` is needed if your server is registered in the IAB GVL. Have the privacy setup reviewed by a lawyer.
207
+ - **Automatic refresh** (`refreshSec`): only if the slot is on screen and the tab is visible. GPT and AdSense have their own refresh policies.
208
+ - **CLS**: use `minHeight` on above-the-fold slots.
package/dist/index.cjs CHANGED
@@ -1261,7 +1261,7 @@ var house_default = {
1261
1261
  };
1262
1262
 
1263
1263
  // src/lib.js
1264
- var version = true ? "0.1.0" : "dev";
1264
+ var version = true ? "0.1.1" : "dev";
1265
1265
  function createUal() {
1266
1266
  const registry = new Map(Object.entries({
1267
1267
  json: json_default,
package/dist/index.mjs CHANGED
@@ -1233,7 +1233,7 @@ var house_default = {
1233
1233
  };
1234
1234
 
1235
1235
  // src/lib.js
1236
- var version = true ? "0.1.0" : "dev";
1236
+ var version = true ? "0.1.1" : "dev";
1237
1237
  function createUal() {
1238
1238
  const registry = new Map(Object.entries({
1239
1239
  json: json_default,
package/dist/ual.js CHANGED
@@ -1234,7 +1234,7 @@ window.UALExt={
1234
1234
  };
1235
1235
 
1236
1236
  // src/lib.js
1237
- var version = true ? "0.1.0" : "dev";
1237
+ var version = true ? "0.1.1" : "dev";
1238
1238
  function createUal() {
1239
1239
  const registry = new Map(Object.entries({
1240
1240
  json: json_default,
package/dist/ual.min.js CHANGED
@@ -9,4 +9,4 @@ window.UALExt={
9
9
  close:function(){s({cmd:'close'});},
10
10
  click:function(){s({cmd:'click'});if(window.clickTag)window.open(window.clickTag,'_blank');}
11
11
  };
12
- })();<\/script>`}function ee(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function st(e,r){let t='<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>html,body{margin:0;padding:0;overflow:hidden;background:transparent}img{display:block;border:0;max-width:100%}</style>'+ot(r,e.clickUrl)+"</head><body>",o="";if(e.type==="image"){let i=`<img src="${ee(e.src)}" width="${e.width}" height="${e.height}" alt="${ee(e.alt||"Advertisement")}">`;o=e.clickUrl?`<a href="${ee(e.clickUrl)}" rel="noopener sponsored" onclick="parent.postMessage({ual:'${r}',cmd:'click'},'*')">${i}</a>`:i}else o=e.html||"";return t+o+"</body></html>"}function Ce(e,r,{slotId:t,onClick:o,onClose:i}={}){at();let a="ual"+ ++rt,c=document.createElement("iframe");c.title="Advertisement",c.setAttribute("scrolling","no"),c.setAttribute("frameborder","0"),c.setAttribute("loading","eager"),c.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),c.style.cssText=`border:0;display:block;margin:0 auto;width:${r.width}px;height:${r.height}px;max-width:100%`,c.width=r.width,c.height=r.height;let l=it(r);return l!==null&&c.setAttribute("sandbox",l),r.type==="url"?c.src=r.url:c.srcdoc=st(r,a),te.set(a,{iframe:c,slotId:t,ad:r,original:{w:r.width,h:r.height},onClick:()=>{q(r.trackers&&r.trackers.click),o&&o()},onClose:i}),e.innerHTML="",e.appendChild(c),{iframe:c,channel:a,destroy:()=>te.delete(a)}}var Me=!1;function at(){Me||(Me=!0,window.addEventListener("message",lt))}function lt(e){let r=e.data;if(!r||typeof r!="object"||!r.ual)return;let t=te.get(r.ual);if(!(!t||e.source!==t.iframe.contentWindow))switch(r.cmd){case"resize":{let o=Math.min(+r.w||t.original.w,B),i=Math.min(+r.h||t.original.h,B);t.iframe.style.width=o+"px",t.iframe.style.height=i+"px";break}case"collapse":t.iframe.style.width=t.original.w+"px",t.iframe.style.height=t.original.h+"px";break;case"close":t.onClose&&t.onClose();break;case"click":t.onClick();break;default:}}function Oe(e,{threshold:r=.5,durationMs:t=1e3}={},o){if(!("IntersectionObserver"in window))return()=>{};let i=null,a=!1,c=!1,l=()=>{a||i||document.visibilityState!=="visible"||(i=setTimeout(()=>{a=!0,E(),o()},t))},u=()=>{clearTimeout(i),i=null},f=new IntersectionObserver(A=>{A.forEach(p=>{c=p.intersectionRatio>=r,c?l():u()})},{threshold:[0,r,1]}),y=()=>document.visibilityState==="visible"&&c?l():u();document.addEventListener("visibilitychange",y),f.observe(e);function E(){u(),f.disconnect(),document.removeEventListener("visibilitychange",y)}return E}var ct=new Set(["unconfigured","disabled","closed"]),dt=242500;function $e(e){let r=ce(),t=null,o=null,i={},a={gdprApplies:void 0,tcString:"",purposes:{},granted:!1,source:"pending"},c=Promise.resolve(a),l=!1,u=null,f=new Map,y=new Map,E=0,A=null,p=null,v=new Set,M=null;function z(){return{site:t.site,page:{url:location.href,path:location.pathname,ref:document.referrer,title:document.title},device:pe(),viewport:{w:window.innerWidth,h:window.innerHeight},targeting:{...t.targeting},consent:a,debug:ue()}}function k(){let n=ye(o);return Object.assign(n.targeting,i),n}function _(n){let s=a;if(!(s.granted===n.granted&&s.tcString===n.tcString&&s.source===n.source)&&(a=n,r.emit("consent",a),!s.granted&&n.granted))for(let d of[...f.values()])d.consentSkipped&&U(d)}function U(n,s=20){if(f.get(n.instanceId)===n){if(n.status==="loading"||n.status==="queued"){s>0&&setTimeout(()=>U(n,s-1),250);return}K(n)}}function We(n){return u?(S("init gi\xE0 eseguito"),u):(u=He(n).catch(s=>{throw u=null,s}),u)}async function He(n){o=await ge(n),o.debug&&Y(!0),he(o,e),t=k(),I("Config",t),c=Ae(t.consent,_).then(s=>(_(s),s)),document.readyState==="loading"&&await new Promise(s=>document.addEventListener("DOMContentLoaded",s,{once:!0})),l=!0,Xe(),V(document),Je(),r.emit("ready",{site:t.site})}function V(n){let s=[];n.nodeType===1&&n.matches&&n.matches("[data-ad-slot]")&&s.push(n),n.querySelectorAll&&n.querySelectorAll("[data-ad-slot]").forEach(d=>s.push(d)),s.forEach(Be)}function Be(n){let s=n.dataset.ualId;if(!s)return oe(n);f.has(s)||ct.has(n.dataset.ualStatus)||(ie(n),oe(n))}function ie(n){delete n.dataset.ualId,delete n.dataset.ualStatus,n.querySelectorAll(":scope > .ual-label, :scope > .ual-creative").forEach(s=>s.remove()),n.classList.remove("ual-slot","ual-filled"),n.style.display="",n.style.minHeight=""}function oe(n){let s=n.getAttribute("data-ad-slot"),d=t.slots[s];if(n.dataset.ualId=s+"#"+ ++E,!d){I("Slot non configurato, ignorato:",s),n.dataset.ualStatus="unconfigured";return}if(d.enabled===!1){I("Slot disabilitato:",s),n.dataset.ualStatus="disabled",X(n,d);return}let w={};try{w=n.dataset.adParams?JSON.parse(n.dataset.adParams):{}}catch(T){S("data-ad-params non \xE8 JSON valido",n)}let h={instanceId:n.dataset.ualId,name:s,el:n,cfg:d,params:w,status:"idle",cleanup:[],tries:0,consentSkipped:!1};f.set(h.instanceId,h),Ve(h),r.emit("slotRegistered",C(h)),(d.lazy!==void 0?d.lazy:t.lazy.enabled)&&A?A.observe(n):J(h)}function Ve(n){let{el:s,cfg:d}=n;if(d.minHeight&&(s.style.minHeight=d.minHeight+"px"),s.classList.add("ual-slot"),d.label){let h=document.createElement("div");h.className="ual-label",h.textContent=d.label,h.style.cssText="font:10px/1.4 sans-serif;color:#888;text-align:center;text-transform:uppercase;letter-spacing:.05em",s.appendChild(h)}let w=document.createElement("div");w.className="ual-creative",s.appendChild(w),n.box=w}function Je(){"MutationObserver"in window&&(p=new MutationObserver(n=>{for(let s of n)s.removedNodes.forEach(d=>{if(d.nodeType===1)for(let w of[...f.values()])(d===w.el||d.contains(w.el))&&G(w)}),s.addedNodes.forEach(d=>{d.nodeType===1&&V(d)})}),p.observe(document.body||document.documentElement,{childList:!0,subtree:!0}))}function Xe(){"IntersectionObserver"in window&&(A=new IntersectionObserver(n=>{n.forEach(s=>{if(!s.isIntersecting)return;A.unobserve(s.target);let d=f.get(s.target.dataset.ualId);d&&J(d)})},{rootMargin:t.lazy.fetchMargin}))}function J(n){n.status!=="loading"&&(n.status="queued",v.add(n),clearTimeout(M),M=setTimeout(Ke,30))}function Ke(){let n=[...v];v=new Set,n.length&&se(n,0)}async function se(n,s){let d=new Map,w=[];for(let h of n){let b=(h.cfg.providers||[])[s];if(!b){w.push(h);continue}let T=typeof b=="string"?b:b.id;d.has(T)||d.set(T,[]),d.get(T).push(h)}w.forEach(Ye),await Promise.all([...d.entries()].map(async([h,b])=>{let T=await Qe(h,b,s);T.length&&await se(T,s+1)}))}async function Ge(n){let s=t.providers[n];if(!s)throw new Error("Provider non definito: "+n);let d=e.get(s.adapter);if(!d)throw new Error("Adapter non registrato: "+s.adapter);return y.has(n)||y.set(n,Promise.resolve(d.init?d.init(s,z()):null)),await y.get(n),{adapter:d,pcfg:s}}async function Qe(n,s,d){let w=t.providers[n];if((!w||w.consent!=="contextual")&&await c,s=s.filter(m=>f.get(m.instanceId)===m),!s.length)return[];let h,b;try{({adapter:h,pcfg:b}=await Ge(n))}catch(m){return S(m.message),r.emit("error",{provider:n,error:m}),s}if(b.consent==="required"&&!a.granted)return I(`Provider ${n} saltato: consenso non concesso`),s.forEach(m=>{m.consentSkipped=!0}),s;let T=z(),O=s.map(m=>{m.status="loading",m.provider=n;let g=m.cfg.providers[d];return{id:m.instanceId,name:m.name,el:m.box,sizes:ve(m.cfg),params:{...typeof g=="object"?g.params:{},...m.params},cfg:m.cfg}});O.forEach(m=>r.emit("slotRequested",{slot:m.name,id:m.id,provider:n})),I(`Richiesta a ${n} (${b.adapter})`,O.map(m=>m.name));let le=b.timeoutMs||t.providerTimeoutMs,L=[];try{if(h.fill){let m=await R(h.fill(O,b,T),le+2e3,{});s.forEach((g,Q)=>{let x=m[O[Q].id];x&&x.filled?ae(g,{width:x.width,height:x.height,selfRendered:!0,meta:x.meta}):(N(g),P(g),L.push(g))})}else{let m=await R(h.request(O,b,T),le,{});s.forEach((g,Q)=>{let x=m[O[Q].id];if(!x||!x.html&&!x.src&&!x.url){L.push(g);return}let D=ze(x);if(!D.ok){S(`Annuncio scartato (${n}, slot ${g.name}): ${D.reason}`),r.emit("error",{provider:n,slot:g.name,error:new Error(D.reason)}),L.push(g);return}let j=D.ad,et=Ce(g.box,j,{slotId:g.instanceId,onClick:()=>r.emit("slotClick",C(g)),onClose:()=>{G(g),g.el.dataset.ualStatus="closed",X(g.el,g.cfg,!0)}});g.cleanup.push(et.destroy),q(j.trackers.impression),ae(g,{width:j.width,height:j.height,ad:j})})}}catch(m){S(`Errore provider ${n}`,m),r.emit("error",{provider:n,error:m}),s.forEach(g=>{g.status==="loading"&&(N(g),P(g),L.push(g))})}return L}function ae(n,s){n.status="filled",n.el.dataset.ualStatus="filled",n.el.classList.add("ual-filled"),n.el.style.display="",r.emit("slotRendered",{...C(n),width:s.width,height:s.height,meta:s.meta});let d={...t.viewability,...n.cfg.viewability};(s.width||0)*(s.height||0)>dt&&!(n.cfg.viewability&&n.cfg.viewability.threshold!==void 0)&&(d.threshold=Math.min(d.threshold,.3));let h=n.box.firstElementChild||n.box,b=Oe(h,d,()=>{s.ad&&q(s.ad.trackers.viewable),r.emit("slotViewable",C(n)),Ze(n)});n.cleanup.push(b)}function Ye(n){n.status="empty",n.el.dataset.ualStatus="empty",P(n),X(n.el,n.cfg),r.emit("slotEmpty",C(n))}function X(n,s,d){(d||(s.collapseEmpty!==void 0?s.collapseEmpty:t&&t.collapseEmpty))&&(n.style.display="none",n.style.minHeight="")}function P(n){n.cleanup.forEach(s=>{try{s()}catch(d){}}),n.cleanup=[],n.box&&(n.box.innerHTML="")}function N(n){let s=n.provider&&t.providers[n.provider],d=s&&e.get(s.adapter);if(d&&d.destroy)try{d.destroy({id:n.instanceId,el:n.box},s)}catch(w){}}function Ze(n){let s=n.cfg.refreshSec;if(!s||n.cfg.maxRefresh&&n.tries>=n.cfg.maxRefresh)return;let d=()=>{n.refreshTimer=setTimeout(()=>{if(!f.has(n.instanceId))return;let w=n.el.getBoundingClientRect();w.bottom>0&&w.top<window.innerHeight&&document.visibilityState==="visible"?(n.tries++,K(n)):d()},s*1e3)};d(),n.cleanup.push(()=>clearTimeout(n.refreshTimer))}function K(n){n.status==="loading"||n.status==="queued"||(N(n),P(n),n.status="idle",n.consentSkipped=!1,J(n))}function G(n){N(n),P(n),A&&A.unobserve(n.el),f.delete(n.instanceId)}function C(n){return{slot:n.name,id:n.instanceId,provider:n.provider,status:n.status,el:n.el}}return{init:We,isInitialized:()=>u!==null,whenReady:()=>u,on:r.on.bind(r),off:r.off.bind(r),registerAdapter:(n,s)=>e.set(n,s),setTargeting(n){Object.entries(n||{}).forEach(([s,d])=>{d==null?(delete i[s],t&&delete t.targeting[s]):(i[s]=d,t&&(t.targeting[s]=d))})},refresh(n){for(let s of f.values())(!n||n.includes(s.name))&&K(s)},pageView(){if(l){for(let n of[...f.values()])G(n);document.querySelectorAll("[data-ual-id]").forEach(ie),t=k(),V(document)}},getSlots:()=>[...f.values()].map(C),getConsent:()=>a,debug:Y}}var Pe={name:"json",async request(e,r,t){let o={site:t.site,page:t.page,device:t.device,viewport:t.viewport,targeting:t.targeting,consent:{gdprApplies:t.consent.gdprApplies,tcString:t.consent.tcString,granted:t.consent.granted},slots:e.map(l=>({id:l.id,name:l.name,sizes:l.sizes,params:l.params}))},i=await fetch(r.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...r.headers},body:JSON.stringify(o),credentials:r.withCredentials&&t.consent.granted?"include":"omit",keepalive:!1});if(i.status===204)return{};if(!i.ok)throw new Error("decide HTTP "+i.status);let a=await i.json();r.map&&typeof window[r.map]=="function"&&(a=window[r.map](a,e));let c={};return(a.ads||[]).forEach(l=>{l&&l.slotId&&(c[l.slotId]=l)}),c}};var Le={name:"iframe",async request(e,r,t){let o={};return e.forEach(i=>{let[a,c]=i.sizes&&i.sizes[0]||[i.params.width||300,i.params.height||250],l=fe(r.url,{params:i.params,width:a,height:c,site:t.site,slot:i.name,device:t.device,cb:W(),page:t.page,consent:{tcString:t.consent.tcString,gdpr:t.consent.gdprApplies?1:0}});o[i.id]={type:"url",url:l,width:a,height:c,sandbox:r.sandbox||void 0}}),o}};function je(e){return e.server.replace(/\/$/,"")+(e.deliveryPath||"/www/delivery")}var qe={name:"revive",async request(e,r,t){let o={};return e.forEach(i=>{if(i.params.zoneid==null){S(`revive: zoneid mancante per lo slot "${i.name}", slot saltato`);return}let[a,c]=i.sizes&&i.sizes[0]||[300,250],l=new URLSearchParams({zoneid:i.params.zoneid,cb:W()});t.page.ref&&l.set("referer",t.page.ref),Object.entries(i.params.vars||{}).forEach(([u,f])=>l.set(u,f)),o[i.id]={type:"url",url:`${je(r)}/afr.php?${l}`,width:a,height:c}}),o}},Re={name:"revive-async",async fill(e,r){let t={},o=`${je(r)}/asyncjs.php`,i=e.filter(l=>l.params.zoneid!=null?!0:(S(`revive-async: zoneid mancante per lo slot "${l.name}", slot saltato`),t[l.id]={filled:!1},!1));if(!i.length)return t;let a=i.map(l=>{let u=document.createElement("ins");return u.setAttribute("data-revive-zoneid",l.params.zoneid),u.setAttribute("data-revive-id",r.reviveId),Object.entries(l.params.vars||{}).forEach(([f,y])=>u.setAttribute("data-revive-"+f,y)),l.el.appendChild(u),new Promise(f=>{let y=p=>{E.disconnect(),clearTimeout(A);let v=u.getBoundingClientRect();t[l.id]={filled:p,width:Math.round(v.width),height:Math.round(v.height)},f()},E=new MutationObserver(()=>{u.querySelector("img,iframe,a,div,embed")&&y(!0)});E.observe(u,{childList:!0,subtree:!0});let A=setTimeout(()=>y(u.children.length>0),r.fillTimeoutMs||2500)})}),c=window.reviveAsync&&window.reviveAsync[r.reviveId];return c&&typeof c.refresh=="function"?c.refresh():await $(o),await Promise.all(a),t}};var ut=0,ne=new Map;function ft(e,r){return e.replace(/\{([\w.]+)\}/g,(t,o)=>{let i=o.split(".").reduce((a,c)=>a==null?void 0:a[c],r);return i==null?"":String(i)})}var re=new Set;function pt(e,r){let t=r||{};re.forEach(o=>{if(!(o in t)){try{e.clearTargeting(o)}catch(i){}re.delete(o)}}),Object.entries(t).forEach(([o,i])=>{e.setTargeting(o,i),re.add(o)})}var _e={name:"gpt",async init(e,r){window.googletag=window.googletag||{cmd:[]};let t=window.googletag;t.cmd.push(()=>{let o=t.pubads();e.singleRequest!==!1&&o.enableSingleRequest(),o.disableInitialLoad(),o.collapseEmptyDivs(!1),e.nonPersonalizedWithoutConsent&&r.consent&&!r.consent.granted&&o.setPrivacySettings&&o.setPrivacySettings({nonPersonalizedAds:!0}),t.enableServices()}),await $(e.src||"https://securepubads.g.doubleclick.net/tag/js/gpt.js",{crossorigin:"anonymous"})},fill(e,r,t){return new Promise(o=>{let i=window.googletag,a={},c=e.length;i.cmd.push(()=>{let l=i.pubads();pt(l,t.targeting);let u=[],f={};if(e.forEach(p=>{let v=document.createElement("div");v.id="ual-gpt-"+ ++ut,p.el.appendChild(v);let M=p.params.adUnitPath||ft(r.adUnitPath,{network:r.network,site:t.site,slot:p.name,section:p.params.section,params:p.params}),z=p.sizes.length?p.sizes:["fluid"],k=i.defineSlot(M,z,v.id);if(!k){a[p.id]={filled:!1},c--;return}k.addService(l),Object.entries(p.params.targeting||{}).forEach(([_,U])=>k.setTargeting(_,U)),ne.set(p.id,k),f[v.id]=p.id,u.push(k),i.display(v.id)}),!c)return o(a);let y,E=()=>{clearTimeout(y),l.removeEventListener("slotRenderEnded",A),Object.values(f).forEach(p=>{a[p]||(a[p]={filled:!1})}),o(a)},A=p=>{let v=f[p.slot.getSlotElementId()];if(!v||a[v])return;let[M,z]=Array.isArray(p.size)?p.size:[0,0];a[v]={filled:!p.isEmpty,width:M,height:z,meta:{advertiserId:p.advertiserId,lineItemId:p.lineItemId,creativeId:p.creativeId}},--c===0&&E()};l.addEventListener("slotRenderEnded",A),y=setTimeout(E,r.fillTimeoutMs||4e3),l.refresh(u)})})},destroy(e){let r=ne.get(e.id);r&&window.googletag&&window.googletag.destroySlots&&(window.googletag.destroySlots([r]),ne.delete(e.id))}};var Ue={name:"adsense",async init(e){await $(`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(e.client)}`,{crossorigin:"anonymous"})},fill(e,r){return Promise.all(e.map(t=>new Promise(o=>{let i=document.createElement("ins");i.className="adsbygoogle",i.setAttribute("data-ad-client",r.client),i.setAttribute("data-ad-slot",t.params.slot);let[a,c]=t.sizes[0]||[];t.params.format||!a?(i.style.display="block",i.setAttribute("data-ad-format",t.params.format||"auto"),t.params.responsive!==!1&&i.setAttribute("data-full-width-responsive","true")):i.style.cssText=`display:inline-block;width:${a}px;height:${c}px`,r.test&&i.setAttribute("data-adtest","on"),t.el.appendChild(i);let l=y=>{u.disconnect(),clearTimeout(f);let E=i.getBoundingClientRect();o([t.id,{filled:y,width:Math.round(E.width),height:Math.round(E.height)}])},u=new MutationObserver(()=>{let y=i.getAttribute("data-ad-status");y==="filled"?l(!0):y==="unfilled"&&l(!1)});u.observe(i,{attributes:!0,attributeFilter:["data-ad-status"]});let f=setTimeout(()=>l(!!i.querySelector("iframe")),r.fillTimeoutMs||4e3);try{(window.adsbygoogle=window.adsbygoogle||[]).push({})}catch(y){l(!1)}}))).then(t=>Object.fromEntries(t))}};function mt(e){let r=e.reduce((o,i)=>o+(i.weight||1),0),t=Math.random()*r;for(let o of e)if(t-=o.weight||1,t<=0)return o;return e[e.length-1]}var Ne={name:"house",async request(e,r,t){let o=Date.now(),i={};return e.forEach(a=>{let c=(r.creatives||[]).filter(l=>!(l.slots&&!l.slots.includes(a.name)||l.devices&&!l.devices.includes(t.device)||l.from&&o<Date.parse(l.from)||l.to&&o>Date.parse(l.to)+864e5||a.sizes.length&&!a.sizes.some(([u,f])=>u===l.width&&f===l.height)));if(c.length){let l=mt(c);i[a.id]={...l,type:l.type||(l.html?"html":"image")}}}),i}};var gt="0.1.0";function De(){let e=new Map(Object.entries({json:Pe,iframe:Le,revive:qe,"revive-async":Re,gpt:_e,adsense:Ue,house:Ne}));return{...$e(e),version:gt}}var Fe="ual";(function(){let r=window[Fe]||{};if(r.version)return;let o={...De(),cmd:{push:u=>i(u)}};function i(u){try{u(o)}catch(f){S("Errore in comando cmd",f)}}let a=Array.isArray(r.cmd)?r.cmd:[];window[Fe]=o;let c=document.currentScript||document.querySelector('script[data-ual-config],script[data-config][src*="ual"]'),l=c&&(c.getAttribute("data-ual-config")||c.getAttribute("data-config"));if(a.forEach(i),l){let u=()=>o.init(l).catch(f=>S("Init fallito",f));document.readyState==="loading"?document.addEventListener("DOMContentLoaded",u):u()}})();})();
12
+ })();<\/script>`}function ee(e){return String(e).replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function st(e,r){let t='<!doctype html><html><head><meta charset="utf-8"><base target="_blank"><style>html,body{margin:0;padding:0;overflow:hidden;background:transparent}img{display:block;border:0;max-width:100%}</style>'+ot(r,e.clickUrl)+"</head><body>",o="";if(e.type==="image"){let i=`<img src="${ee(e.src)}" width="${e.width}" height="${e.height}" alt="${ee(e.alt||"Advertisement")}">`;o=e.clickUrl?`<a href="${ee(e.clickUrl)}" rel="noopener sponsored" onclick="parent.postMessage({ual:'${r}',cmd:'click'},'*')">${i}</a>`:i}else o=e.html||"";return t+o+"</body></html>"}function Ce(e,r,{slotId:t,onClick:o,onClose:i}={}){at();let a="ual"+ ++rt,c=document.createElement("iframe");c.title="Advertisement",c.setAttribute("scrolling","no"),c.setAttribute("frameborder","0"),c.setAttribute("loading","eager"),c.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),c.style.cssText=`border:0;display:block;margin:0 auto;width:${r.width}px;height:${r.height}px;max-width:100%`,c.width=r.width,c.height=r.height;let l=it(r);return l!==null&&c.setAttribute("sandbox",l),r.type==="url"?c.src=r.url:c.srcdoc=st(r,a),te.set(a,{iframe:c,slotId:t,ad:r,original:{w:r.width,h:r.height},onClick:()=>{q(r.trackers&&r.trackers.click),o&&o()},onClose:i}),e.innerHTML="",e.appendChild(c),{iframe:c,channel:a,destroy:()=>te.delete(a)}}var Me=!1;function at(){Me||(Me=!0,window.addEventListener("message",lt))}function lt(e){let r=e.data;if(!r||typeof r!="object"||!r.ual)return;let t=te.get(r.ual);if(!(!t||e.source!==t.iframe.contentWindow))switch(r.cmd){case"resize":{let o=Math.min(+r.w||t.original.w,B),i=Math.min(+r.h||t.original.h,B);t.iframe.style.width=o+"px",t.iframe.style.height=i+"px";break}case"collapse":t.iframe.style.width=t.original.w+"px",t.iframe.style.height=t.original.h+"px";break;case"close":t.onClose&&t.onClose();break;case"click":t.onClick();break;default:}}function Oe(e,{threshold:r=.5,durationMs:t=1e3}={},o){if(!("IntersectionObserver"in window))return()=>{};let i=null,a=!1,c=!1,l=()=>{a||i||document.visibilityState!=="visible"||(i=setTimeout(()=>{a=!0,E(),o()},t))},u=()=>{clearTimeout(i),i=null},f=new IntersectionObserver(A=>{A.forEach(p=>{c=p.intersectionRatio>=r,c?l():u()})},{threshold:[0,r,1]}),y=()=>document.visibilityState==="visible"&&c?l():u();document.addEventListener("visibilitychange",y),f.observe(e);function E(){u(),f.disconnect(),document.removeEventListener("visibilitychange",y)}return E}var ct=new Set(["unconfigured","disabled","closed"]),dt=242500;function $e(e){let r=ce(),t=null,o=null,i={},a={gdprApplies:void 0,tcString:"",purposes:{},granted:!1,source:"pending"},c=Promise.resolve(a),l=!1,u=null,f=new Map,y=new Map,E=0,A=null,p=null,v=new Set,M=null;function z(){return{site:t.site,page:{url:location.href,path:location.pathname,ref:document.referrer,title:document.title},device:pe(),viewport:{w:window.innerWidth,h:window.innerHeight},targeting:{...t.targeting},consent:a,debug:ue()}}function k(){let n=ye(o);return Object.assign(n.targeting,i),n}function _(n){let s=a;if(!(s.granted===n.granted&&s.tcString===n.tcString&&s.source===n.source)&&(a=n,r.emit("consent",a),!s.granted&&n.granted))for(let d of[...f.values()])d.consentSkipped&&U(d)}function U(n,s=20){if(f.get(n.instanceId)===n){if(n.status==="loading"||n.status==="queued"){s>0&&setTimeout(()=>U(n,s-1),250);return}K(n)}}function We(n){return u?(S("init gi\xE0 eseguito"),u):(u=He(n).catch(s=>{throw u=null,s}),u)}async function He(n){o=await ge(n),o.debug&&Y(!0),he(o,e),t=k(),I("Config",t),c=Ae(t.consent,_).then(s=>(_(s),s)),document.readyState==="loading"&&await new Promise(s=>document.addEventListener("DOMContentLoaded",s,{once:!0})),l=!0,Xe(),V(document),Je(),r.emit("ready",{site:t.site})}function V(n){let s=[];n.nodeType===1&&n.matches&&n.matches("[data-ad-slot]")&&s.push(n),n.querySelectorAll&&n.querySelectorAll("[data-ad-slot]").forEach(d=>s.push(d)),s.forEach(Be)}function Be(n){let s=n.dataset.ualId;if(!s)return oe(n);f.has(s)||ct.has(n.dataset.ualStatus)||(ie(n),oe(n))}function ie(n){delete n.dataset.ualId,delete n.dataset.ualStatus,n.querySelectorAll(":scope > .ual-label, :scope > .ual-creative").forEach(s=>s.remove()),n.classList.remove("ual-slot","ual-filled"),n.style.display="",n.style.minHeight=""}function oe(n){let s=n.getAttribute("data-ad-slot"),d=t.slots[s];if(n.dataset.ualId=s+"#"+ ++E,!d){I("Slot non configurato, ignorato:",s),n.dataset.ualStatus="unconfigured";return}if(d.enabled===!1){I("Slot disabilitato:",s),n.dataset.ualStatus="disabled",X(n,d);return}let w={};try{w=n.dataset.adParams?JSON.parse(n.dataset.adParams):{}}catch(T){S("data-ad-params non \xE8 JSON valido",n)}let h={instanceId:n.dataset.ualId,name:s,el:n,cfg:d,params:w,status:"idle",cleanup:[],tries:0,consentSkipped:!1};f.set(h.instanceId,h),Ve(h),r.emit("slotRegistered",C(h)),(d.lazy!==void 0?d.lazy:t.lazy.enabled)&&A?A.observe(n):J(h)}function Ve(n){let{el:s,cfg:d}=n;if(d.minHeight&&(s.style.minHeight=d.minHeight+"px"),s.classList.add("ual-slot"),d.label){let h=document.createElement("div");h.className="ual-label",h.textContent=d.label,h.style.cssText="font:10px/1.4 sans-serif;color:#888;text-align:center;text-transform:uppercase;letter-spacing:.05em",s.appendChild(h)}let w=document.createElement("div");w.className="ual-creative",s.appendChild(w),n.box=w}function Je(){"MutationObserver"in window&&(p=new MutationObserver(n=>{for(let s of n)s.removedNodes.forEach(d=>{if(d.nodeType===1)for(let w of[...f.values()])(d===w.el||d.contains(w.el))&&G(w)}),s.addedNodes.forEach(d=>{d.nodeType===1&&V(d)})}),p.observe(document.body||document.documentElement,{childList:!0,subtree:!0}))}function Xe(){"IntersectionObserver"in window&&(A=new IntersectionObserver(n=>{n.forEach(s=>{if(!s.isIntersecting)return;A.unobserve(s.target);let d=f.get(s.target.dataset.ualId);d&&J(d)})},{rootMargin:t.lazy.fetchMargin}))}function J(n){n.status!=="loading"&&(n.status="queued",v.add(n),clearTimeout(M),M=setTimeout(Ke,30))}function Ke(){let n=[...v];v=new Set,n.length&&se(n,0)}async function se(n,s){let d=new Map,w=[];for(let h of n){let b=(h.cfg.providers||[])[s];if(!b){w.push(h);continue}let T=typeof b=="string"?b:b.id;d.has(T)||d.set(T,[]),d.get(T).push(h)}w.forEach(Ye),await Promise.all([...d.entries()].map(async([h,b])=>{let T=await Qe(h,b,s);T.length&&await se(T,s+1)}))}async function Ge(n){let s=t.providers[n];if(!s)throw new Error("Provider non definito: "+n);let d=e.get(s.adapter);if(!d)throw new Error("Adapter non registrato: "+s.adapter);return y.has(n)||y.set(n,Promise.resolve(d.init?d.init(s,z()):null)),await y.get(n),{adapter:d,pcfg:s}}async function Qe(n,s,d){let w=t.providers[n];if((!w||w.consent!=="contextual")&&await c,s=s.filter(m=>f.get(m.instanceId)===m),!s.length)return[];let h,b;try{({adapter:h,pcfg:b}=await Ge(n))}catch(m){return S(m.message),r.emit("error",{provider:n,error:m}),s}if(b.consent==="required"&&!a.granted)return I(`Provider ${n} saltato: consenso non concesso`),s.forEach(m=>{m.consentSkipped=!0}),s;let T=z(),O=s.map(m=>{m.status="loading",m.provider=n;let g=m.cfg.providers[d];return{id:m.instanceId,name:m.name,el:m.box,sizes:ve(m.cfg),params:{...typeof g=="object"?g.params:{},...m.params},cfg:m.cfg}});O.forEach(m=>r.emit("slotRequested",{slot:m.name,id:m.id,provider:n})),I(`Richiesta a ${n} (${b.adapter})`,O.map(m=>m.name));let le=b.timeoutMs||t.providerTimeoutMs,L=[];try{if(h.fill){let m=await R(h.fill(O,b,T),le+2e3,{});s.forEach((g,Q)=>{let x=m[O[Q].id];x&&x.filled?ae(g,{width:x.width,height:x.height,selfRendered:!0,meta:x.meta}):(N(g),P(g),L.push(g))})}else{let m=await R(h.request(O,b,T),le,{});s.forEach((g,Q)=>{let x=m[O[Q].id];if(!x||!x.html&&!x.src&&!x.url){L.push(g);return}let D=ze(x);if(!D.ok){S(`Annuncio scartato (${n}, slot ${g.name}): ${D.reason}`),r.emit("error",{provider:n,slot:g.name,error:new Error(D.reason)}),L.push(g);return}let j=D.ad,et=Ce(g.box,j,{slotId:g.instanceId,onClick:()=>r.emit("slotClick",C(g)),onClose:()=>{G(g),g.el.dataset.ualStatus="closed",X(g.el,g.cfg,!0)}});g.cleanup.push(et.destroy),q(j.trackers.impression),ae(g,{width:j.width,height:j.height,ad:j})})}}catch(m){S(`Errore provider ${n}`,m),r.emit("error",{provider:n,error:m}),s.forEach(g=>{g.status==="loading"&&(N(g),P(g),L.push(g))})}return L}function ae(n,s){n.status="filled",n.el.dataset.ualStatus="filled",n.el.classList.add("ual-filled"),n.el.style.display="",r.emit("slotRendered",{...C(n),width:s.width,height:s.height,meta:s.meta});let d={...t.viewability,...n.cfg.viewability};(s.width||0)*(s.height||0)>dt&&!(n.cfg.viewability&&n.cfg.viewability.threshold!==void 0)&&(d.threshold=Math.min(d.threshold,.3));let h=n.box.firstElementChild||n.box,b=Oe(h,d,()=>{s.ad&&q(s.ad.trackers.viewable),r.emit("slotViewable",C(n)),Ze(n)});n.cleanup.push(b)}function Ye(n){n.status="empty",n.el.dataset.ualStatus="empty",P(n),X(n.el,n.cfg),r.emit("slotEmpty",C(n))}function X(n,s,d){(d||(s.collapseEmpty!==void 0?s.collapseEmpty:t&&t.collapseEmpty))&&(n.style.display="none",n.style.minHeight="")}function P(n){n.cleanup.forEach(s=>{try{s()}catch(d){}}),n.cleanup=[],n.box&&(n.box.innerHTML="")}function N(n){let s=n.provider&&t.providers[n.provider],d=s&&e.get(s.adapter);if(d&&d.destroy)try{d.destroy({id:n.instanceId,el:n.box},s)}catch(w){}}function Ze(n){let s=n.cfg.refreshSec;if(!s||n.cfg.maxRefresh&&n.tries>=n.cfg.maxRefresh)return;let d=()=>{n.refreshTimer=setTimeout(()=>{if(!f.has(n.instanceId))return;let w=n.el.getBoundingClientRect();w.bottom>0&&w.top<window.innerHeight&&document.visibilityState==="visible"?(n.tries++,K(n)):d()},s*1e3)};d(),n.cleanup.push(()=>clearTimeout(n.refreshTimer))}function K(n){n.status==="loading"||n.status==="queued"||(N(n),P(n),n.status="idle",n.consentSkipped=!1,J(n))}function G(n){N(n),P(n),A&&A.unobserve(n.el),f.delete(n.instanceId)}function C(n){return{slot:n.name,id:n.instanceId,provider:n.provider,status:n.status,el:n.el}}return{init:We,isInitialized:()=>u!==null,whenReady:()=>u,on:r.on.bind(r),off:r.off.bind(r),registerAdapter:(n,s)=>e.set(n,s),setTargeting(n){Object.entries(n||{}).forEach(([s,d])=>{d==null?(delete i[s],t&&delete t.targeting[s]):(i[s]=d,t&&(t.targeting[s]=d))})},refresh(n){for(let s of f.values())(!n||n.includes(s.name))&&K(s)},pageView(){if(l){for(let n of[...f.values()])G(n);document.querySelectorAll("[data-ual-id]").forEach(ie),t=k(),V(document)}},getSlots:()=>[...f.values()].map(C),getConsent:()=>a,debug:Y}}var Pe={name:"json",async request(e,r,t){let o={site:t.site,page:t.page,device:t.device,viewport:t.viewport,targeting:t.targeting,consent:{gdprApplies:t.consent.gdprApplies,tcString:t.consent.tcString,granted:t.consent.granted},slots:e.map(l=>({id:l.id,name:l.name,sizes:l.sizes,params:l.params}))},i=await fetch(r.endpoint,{method:"POST",headers:{"Content-Type":"application/json",...r.headers},body:JSON.stringify(o),credentials:r.withCredentials&&t.consent.granted?"include":"omit",keepalive:!1});if(i.status===204)return{};if(!i.ok)throw new Error("decide HTTP "+i.status);let a=await i.json();r.map&&typeof window[r.map]=="function"&&(a=window[r.map](a,e));let c={};return(a.ads||[]).forEach(l=>{l&&l.slotId&&(c[l.slotId]=l)}),c}};var Le={name:"iframe",async request(e,r,t){let o={};return e.forEach(i=>{let[a,c]=i.sizes&&i.sizes[0]||[i.params.width||300,i.params.height||250],l=fe(r.url,{params:i.params,width:a,height:c,site:t.site,slot:i.name,device:t.device,cb:W(),page:t.page,consent:{tcString:t.consent.tcString,gdpr:t.consent.gdprApplies?1:0}});o[i.id]={type:"url",url:l,width:a,height:c,sandbox:r.sandbox||void 0}}),o}};function je(e){return e.server.replace(/\/$/,"")+(e.deliveryPath||"/www/delivery")}var qe={name:"revive",async request(e,r,t){let o={};return e.forEach(i=>{if(i.params.zoneid==null){S(`revive: zoneid mancante per lo slot "${i.name}", slot saltato`);return}let[a,c]=i.sizes&&i.sizes[0]||[300,250],l=new URLSearchParams({zoneid:i.params.zoneid,cb:W()});t.page.ref&&l.set("referer",t.page.ref),Object.entries(i.params.vars||{}).forEach(([u,f])=>l.set(u,f)),o[i.id]={type:"url",url:`${je(r)}/afr.php?${l}`,width:a,height:c}}),o}},Re={name:"revive-async",async fill(e,r){let t={},o=`${je(r)}/asyncjs.php`,i=e.filter(l=>l.params.zoneid!=null?!0:(S(`revive-async: zoneid mancante per lo slot "${l.name}", slot saltato`),t[l.id]={filled:!1},!1));if(!i.length)return t;let a=i.map(l=>{let u=document.createElement("ins");return u.setAttribute("data-revive-zoneid",l.params.zoneid),u.setAttribute("data-revive-id",r.reviveId),Object.entries(l.params.vars||{}).forEach(([f,y])=>u.setAttribute("data-revive-"+f,y)),l.el.appendChild(u),new Promise(f=>{let y=p=>{E.disconnect(),clearTimeout(A);let v=u.getBoundingClientRect();t[l.id]={filled:p,width:Math.round(v.width),height:Math.round(v.height)},f()},E=new MutationObserver(()=>{u.querySelector("img,iframe,a,div,embed")&&y(!0)});E.observe(u,{childList:!0,subtree:!0});let A=setTimeout(()=>y(u.children.length>0),r.fillTimeoutMs||2500)})}),c=window.reviveAsync&&window.reviveAsync[r.reviveId];return c&&typeof c.refresh=="function"?c.refresh():await $(o),await Promise.all(a),t}};var ut=0,ne=new Map;function ft(e,r){return e.replace(/\{([\w.]+)\}/g,(t,o)=>{let i=o.split(".").reduce((a,c)=>a==null?void 0:a[c],r);return i==null?"":String(i)})}var re=new Set;function pt(e,r){let t=r||{};re.forEach(o=>{if(!(o in t)){try{e.clearTargeting(o)}catch(i){}re.delete(o)}}),Object.entries(t).forEach(([o,i])=>{e.setTargeting(o,i),re.add(o)})}var _e={name:"gpt",async init(e,r){window.googletag=window.googletag||{cmd:[]};let t=window.googletag;t.cmd.push(()=>{let o=t.pubads();e.singleRequest!==!1&&o.enableSingleRequest(),o.disableInitialLoad(),o.collapseEmptyDivs(!1),e.nonPersonalizedWithoutConsent&&r.consent&&!r.consent.granted&&o.setPrivacySettings&&o.setPrivacySettings({nonPersonalizedAds:!0}),t.enableServices()}),await $(e.src||"https://securepubads.g.doubleclick.net/tag/js/gpt.js",{crossorigin:"anonymous"})},fill(e,r,t){return new Promise(o=>{let i=window.googletag,a={},c=e.length;i.cmd.push(()=>{let l=i.pubads();pt(l,t.targeting);let u=[],f={};if(e.forEach(p=>{let v=document.createElement("div");v.id="ual-gpt-"+ ++ut,p.el.appendChild(v);let M=p.params.adUnitPath||ft(r.adUnitPath,{network:r.network,site:t.site,slot:p.name,section:p.params.section,params:p.params}),z=p.sizes.length?p.sizes:["fluid"],k=i.defineSlot(M,z,v.id);if(!k){a[p.id]={filled:!1},c--;return}k.addService(l),Object.entries(p.params.targeting||{}).forEach(([_,U])=>k.setTargeting(_,U)),ne.set(p.id,k),f[v.id]=p.id,u.push(k),i.display(v.id)}),!c)return o(a);let y,E=()=>{clearTimeout(y),l.removeEventListener("slotRenderEnded",A),Object.values(f).forEach(p=>{a[p]||(a[p]={filled:!1})}),o(a)},A=p=>{let v=f[p.slot.getSlotElementId()];if(!v||a[v])return;let[M,z]=Array.isArray(p.size)?p.size:[0,0];a[v]={filled:!p.isEmpty,width:M,height:z,meta:{advertiserId:p.advertiserId,lineItemId:p.lineItemId,creativeId:p.creativeId}},--c===0&&E()};l.addEventListener("slotRenderEnded",A),y=setTimeout(E,r.fillTimeoutMs||4e3),l.refresh(u)})})},destroy(e){let r=ne.get(e.id);r&&window.googletag&&window.googletag.destroySlots&&(window.googletag.destroySlots([r]),ne.delete(e.id))}};var Ue={name:"adsense",async init(e){await $(`https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(e.client)}`,{crossorigin:"anonymous"})},fill(e,r){return Promise.all(e.map(t=>new Promise(o=>{let i=document.createElement("ins");i.className="adsbygoogle",i.setAttribute("data-ad-client",r.client),i.setAttribute("data-ad-slot",t.params.slot);let[a,c]=t.sizes[0]||[];t.params.format||!a?(i.style.display="block",i.setAttribute("data-ad-format",t.params.format||"auto"),t.params.responsive!==!1&&i.setAttribute("data-full-width-responsive","true")):i.style.cssText=`display:inline-block;width:${a}px;height:${c}px`,r.test&&i.setAttribute("data-adtest","on"),t.el.appendChild(i);let l=y=>{u.disconnect(),clearTimeout(f);let E=i.getBoundingClientRect();o([t.id,{filled:y,width:Math.round(E.width),height:Math.round(E.height)}])},u=new MutationObserver(()=>{let y=i.getAttribute("data-ad-status");y==="filled"?l(!0):y==="unfilled"&&l(!1)});u.observe(i,{attributes:!0,attributeFilter:["data-ad-status"]});let f=setTimeout(()=>l(!!i.querySelector("iframe")),r.fillTimeoutMs||4e3);try{(window.adsbygoogle=window.adsbygoogle||[]).push({})}catch(y){l(!1)}}))).then(t=>Object.fromEntries(t))}};function mt(e){let r=e.reduce((o,i)=>o+(i.weight||1),0),t=Math.random()*r;for(let o of e)if(t-=o.weight||1,t<=0)return o;return e[e.length-1]}var Ne={name:"house",async request(e,r,t){let o=Date.now(),i={};return e.forEach(a=>{let c=(r.creatives||[]).filter(l=>!(l.slots&&!l.slots.includes(a.name)||l.devices&&!l.devices.includes(t.device)||l.from&&o<Date.parse(l.from)||l.to&&o>Date.parse(l.to)+864e5||a.sizes.length&&!a.sizes.some(([u,f])=>u===l.width&&f===l.height)));if(c.length){let l=mt(c);i[a.id]={...l,type:l.type||(l.html?"html":"image")}}}),i}};var gt="0.1.1";function De(){let e=new Map(Object.entries({json:Pe,iframe:Le,revive:qe,"revive-async":Re,gpt:_e,adsense:Ue,house:Ne}));return{...$e(e),version:gt}}var Fe="ual";(function(){let r=window[Fe]||{};if(r.version)return;let o={...De(),cmd:{push:u=>i(u)}};function i(u){try{u(o)}catch(f){S("Errore in comando cmd",f)}}let a=Array.isArray(r.cmd)?r.cmd:[];window[Fe]=o;let c=document.currentScript||document.querySelector('script[data-ual-config],script[data-config][src*="ual"]'),l=c&&(c.getAttribute("data-ual-config")||c.getAttribute("data-config"));if(a.forEach(i),l){let u=()=>o.init(l).catch(f=>S("Init fallito",f));document.readyState==="loading"?document.addEventListener("DOMContentLoaded",u):u()}})();})();