universal-ad-loader 0.1.0 → 0.1.2

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,29 @@
1
+ # 0.1.2
2
+
3
+ **Changes**
4
+ - README: example of inline configuration (`ual.cmd.push` + `init({...})`) as an alternative to `data-config`.
5
+
6
+ # 0.1.1
7
+
8
+ **Changes**
9
+ - Documentation (README, guides, changelog) translated to English.
10
+ - `engines`: Node 22 or later.
11
+ - `browser`, `jsdelivr` and `unpkg` fields point to `dist/ual.min.js`, so the CDN default file is the loader.
12
+ - The internal publishing guide (`docs/pubblicazione-npm.md`) is no longer included in the package.
13
+
1
14
  # 0.1.0
2
15
 
3
- Prima versione.
16
+ First release.
4
17
 
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.
18
+ **What's new**
19
+ - Loader served with a `<script>` tag (`dist/ual.min.js`): slots, centralized JSON configuration,
20
+ lazy load, waterfall across providers, IAB TCF consent, isolated iframe rendering, viewability and events.
21
+ - Adapters: `json`, `iframe`, `revive`, `revive-async`, `gpt`, `adsense`, `house`.
22
+ - Core as a library (ESM and CJS), safe to import on the server: `createUal`, `getUal`, `defineConfig`.
23
+ - Components for React, Next.js, Vue and Astro, which start UAL after hydration and handle page changes.
24
+ - TypeScript types and a JSON Schema of the configuration.
12
25
 
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'`.
26
+ **Good to know**
27
+ - `adsense`, `revive-async` and `gpt` are written against the public APIs but have not been tried on real accounts.
28
+ - Nuxt, Remix and React Router have not been tried.
29
+ - 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,89 @@
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
+ **Inline configuration (no JSON file).** Instead of `data-config`, the configuration can be passed as an object in the page. The `cmd.push` block can go before or after the loader tag:
20
20
 
21
- ## Configurazione (JSON servito da CDN)
21
+ ```html
22
+ <script>
23
+ window.ual = window.ual || { cmd: [] };
24
+ ual.cmd.push(function (u) {
25
+ u.init({
26
+ site: 'site_123',
27
+ providers: { main: { adapter: 'json', endpoint: 'https://ads.yourdomain.net/v1/decide' } },
28
+ slots: { top: { sizes: [[728, 90]], providers: ['main'] } }
29
+ });
30
+ });
31
+ </script>
32
+ <script async src="https://adcdn.yourdomain.net/ual.min.js"></script>
33
+
34
+ <div data-ad-slot="top"></div>
35
+ ```
36
+
37
+ - Use either `data-config` or `init(...)`, not both: a second `init` is ignored with a warning.
38
+ - The configuration lives in the page HTML, so changing it means redeploying the page (with a JSON on the CDN you just replace the file). `u.init('https://.../site_123.json')` also works if you want to choose the URL at runtime.
39
+ - An inline script needs `'unsafe-inline'` or a nonce in the page's CSP; with a strict CSP prefer the external JSON.
40
+
41
+ > **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`.
42
+
43
+ ## Configuration (JSON served from the CDN)
22
44
 
23
- Vedi `examples/site_demo.json`. Campi principali:
45
+ See `examples/site_demo.json`. Main fields:
24
46
 
25
- | Campo | Descrizione |
47
+ | Field | Description |
26
48
  |---|---|
27
- | `site` | ID del sito, inviato agli adapter |
49
+ | `site` | Site ID, sent to the adapters |
28
50
  | `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 } }` |
51
+ | `lazy` | `{ enabled, fetchMargin:'200px' }`. The slot is requested when it comes within that distance of the viewport |
52
+ | `viewability` | `{ threshold:0.5, durationMs:1000 }` (MRC standard) |
53
+ | `collapseEmpty` | hides the slot if no provider fills it |
54
+ | `providerTimeoutMs` | default timeout per provider |
55
+ | `targeting` | page-level key-values sent to all adapters |
56
+ | `providers` | `{ id: { adapter:'json'\|'iframe'\|'revive'\|'revive-async'\|'gpt'\|'adsense'\|'house', consent:'optional'\|'required'\|'contextual', timeoutMs, ...adapter options } }` |
57
+ | `slots` | `{ name: { sizes \| sizeMapping, providers:[...waterfall], label, minHeight, lazy, collapseEmpty, refreshSec, maxRefresh, enabled } }` |
36
58
  | `pageRules` | `[{ match:'^/on-air', disable:[...], enable:[...], targeting:{}, slots:{ override } }]` |
37
59
 
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**.
60
+ **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
61
 
40
- **Consenso per provider** (`providers.X.consent`):
62
+ **Consent per provider** (`providers.X.consent`):
41
63
 
42
- | Valore | Comportamento |
64
+ | Value | Behavior |
43
65
  |---|---|
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 |
66
+ | `optional` (default) | Waits for the CMP response (up to `consent.timeoutMs`) and receives the consent as it is |
67
+ | `required` | Like `optional`, but if consent is missing the provider is **skipped** and the next level is tried (e.g. a contextual house fallback) |
68
+ | `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
69
 
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`.
70
+ 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
71
 
50
- ## Adapter inclusi
72
+ ## Included adapters
51
73
 
52
- | Adapter | Uso | Rileva il vuoto? |
74
+ | Adapter | Use | Detects empty? |
53
75
  |---|---|---|
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) |
76
+ | `json` | Your ad server: batch POST to `endpoint` (contract below) | Yes |
77
+ | `iframe` | Any URL that returns the banner (`work.php?n=..&size=..`) with `{params.x}`, `{width}`, `{cb}`… templates | No (treated as filled) |
56
78
  | `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ì |
79
+ | `revive-async` | Revive via `asyncjs.php` + `<ins>` | Yes (observes the DOM) |
80
+ | `gpt` | Google Ad Manager, SRA with `disableInitialLoad` + `refresh` | Yes (`slotRenderEnded`) |
81
+ | `adsense` | AdSense as backfill | Yes (`data-ad-status`) |
82
+ | `house` | Creatives in the config, weighted pick by slot/device/date | Yes |
61
83
 
62
- ### Contratto dell'adapter `json`
84
+ ### The `json` adapter contract
63
85
 
64
- Richiesta `POST {endpoint}`:
86
+ Request `POST {endpoint}`:
65
87
  ```json
66
88
  {
67
89
  "site": "site_123",
@@ -72,7 +94,7 @@ Richiesta `POST {endpoint}`:
72
94
  "slots": [ { "id": "top#1", "name": "top", "sizes": [[728,90]], "params": { "zone": 1 } } ]
73
95
  }
74
96
  ```
75
- Risposta (200 oppure 204 se vuota):
97
+ Response (200, or 204 if empty):
76
98
  ```json
77
99
  { "ads": [ {
78
100
  "slotId": "top#1", "type": "image", "src": "https://cdn/.../b.webp",
@@ -80,28 +102,28 @@ Risposta (200 oppure 204 se vuota):
80
102
  "trackers": { "impression": ["https://ads/i?t=..."], "viewable": ["https://ads/v?t=..."], "click": [] }
81
103
  } ] }
82
104
  ```
83
- `type`: `image` (src + clickUrl), `html` (markup, `clickTag` disponibile), `url` (iframe esterno). Gli slot senza voce passano al provider successivo.
105
+ `type`: `image` (src + clickUrl), `html` (markup, `clickTag` available), `url` (external iframe). Slots with no entry fall through to the next provider.
84
106
 
85
- ## Render e API per le creatività
107
+ ## Rendering and creative API
86
108
 
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:
109
+ `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
110
 
89
111
  ```js
90
112
  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
113
+ UALExt.click() // tracks the click and opens clickTag
114
+ UALExt.expand(728, 300) // resizes (max 2000px)
115
+ UALExt.collapse() // back to the original size
116
+ UALExt.close() // closes and hides the slot
95
117
  ```
96
- I messaggi vengono accettati solo dall'iframe dello slot (controllo su `event.source`).
118
+ Messages are accepted only from the slot's own iframe (checked on `event.source`).
97
119
 
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:
120
+ **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
121
 
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.
122
+ - `width` and `height` are required, integers between 1 and 2000.
123
+ - `src` (image), `url` (url), `clickUrl` and all `trackers`: only `http:`/`https:` (and `data:image/` for `src`). No `javascript:`.
124
+ - 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
125
 
104
- ## API JavaScript
126
+ ## JavaScript API
105
127
 
106
128
  ```js
107
129
  window.ual = window.ual || { cmd: [] };
@@ -113,97 +135,96 @@ ual.cmd.push(function (u) {
113
135
  u.on('error', (e) => {});
114
136
  u.setTargeting({ author: 'mario' });
115
137
  });
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
138
+ // Without data-config: ual.cmd.push(u => u.init({ ...config })) or u.init('https://.../site.json')
139
+ ual.refresh(['top']); // reloads slots (all of them if called without arguments)
140
+ ual.pageView(); // SPA: new page
119
141
  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
142
+ ual.isInitialized(); // true if init has already been started (even if not finished yet)
143
+ ual.whenReady(); // the init promise, or null if never started
122
144
  ```
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.
145
+ `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
146
 
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).
147
+ 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
148
 
127
- Debug: `?ual_debug=1` nell'URL, oppure `localStorage.ual_debug = '1'`.
149
+ Debug: `?ual_debug=1` in the URL, or `localStorage.ual_debug = '1'`.
128
150
 
129
- ## Con Astro, Next.js, React, Vue e Node
151
+ ## With Astro, Next.js, React, Vue and Node
130
152
 
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`.
153
+ 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
154
 
133
155
  ```tsx
134
- import { UalProvider, AdSlot } from 'universal-ad-loader/next'; // o /react, /vue; per Astro i file in astro/
156
+ import { UalProvider, AdSlot } from 'universal-ad-loader/next'; // or /react, /vue; for Astro use the files in astro/
135
157
 
136
- <UalProvider config={config}> {/* parte dopo l'idratazione, gestisce il cambio pagina */}
158
+ <UalProvider config={config}> {/* starts after hydration, handles page changes */}
137
159
  <AdSlot name="top" minHeight={90} />
138
160
  </UalProvider>
139
161
  ```
140
162
 
141
- Se usi invece il tag con un framework, cose verificate con un'app React server-rendered:
163
+ If you use the tag with a framework instead, things verified with a server-rendered React app:
142
164
 
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.
165
+ - **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.
166
+ - **Client-side page change:** new slots register themselves, but `pageRules` and targeting are only recomputed with `ual.pageView()`.
167
+ - **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.
168
+ - **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
169
 
148
- Guida con esempi e tabella CSP: configuratore, pagina `/docs/`.
170
+ Guide with examples and a CSP table: configurator, `/docs/` page.
149
171
 
150
- ## Altri documenti
172
+ ## Other documents
151
173
 
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.
174
+ - `docs/esempio.md`: complete client site (config, flow, minimal `decide` endpoint).
175
+ - `docs/cdn.md`: **which files to put on the CDN** (loader and configuration), headers, structure, what is not needed and how to update.
176
+ - `docs/libreria.md`: UAL as a package (React/Next/Vue/Astro components, types, server-safe core, how it is verified).
177
+ - `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.
178
+ - `schema/ual-config.schema.json`: JSON Schema of the configuration.
179
+ - The web configurator (`../ual-configurator`) has an HTML guide with examples that open in the editor.
159
180
 
160
- ## Adapter custom
181
+ ## Custom adapters
161
182
 
162
183
  ```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:
184
+ ual.cmd.push(u => u.registerAdapter('myserver', {
185
+ init(providerCfg, ctx) {}, // optional, once per provider
186
+ // A) the adapter returns the ads and UAL renders them in the sandbox:
166
187
  async request(slots, providerCfg, ctx) { // slots: [{id,name,sizes,params,el}]
167
188
  return { [slots[0].id]: { type:'image', src:'...', width:300, height:250, clickUrl:'...' } };
168
189
  },
169
- // B) oppure l'adapter rende da solo dentro slot.el:
190
+ // B) or the adapter renders by itself inside slot.el:
170
191
  // async fill(slots, providerCfg, ctx) { return { [id]: { filled:true, width, height } } },
171
- destroy(slot, providerCfg) {}, // opzionale
192
+ destroy(slot, providerCfg) {}, // optional
172
193
  }));
173
194
  ```
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.
195
+ `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
196
 
176
- ## Build e test
197
+ ## Build and tests
177
198
 
178
199
  ```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
200
+ npm ci # base dependencies (~120 MB)
201
+ npm ci --prefix test/heavy # only for the framework tests: Next.js and Astro (~620 MB)
202
+ npx playwright install chromium # once, for the browser tests
203
+ npm test # build + all suites, about 2 minutes
183
204
  ```
184
205
 
185
- Per lavorare per parti (è lo stesso che fa la CI, in job paralleli):
206
+ To work on one part at a time (this is what CI does, in parallel jobs):
186
207
 
187
- | Comando | Cosa esegue | Serve |
208
+ | Command | What it runs | Needs |
188
209
  |---|---|---|
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 |
210
+ | `npm run lint` | oxlint on the sources | base dependencies only |
211
+ | `npm run test:node` | `test/package.mjs` | base dependencies only (no browser) |
212
+ | `npm run test:browser` | `test/run.mjs` and `test/regression.mjs` | + Chromium |
213
+ | `npm run test:frameworks` | `test/wrappers.mjs` | + `test/heavy` and Chromium |
193
214
 
194
- Ogni suite scrive anche un report JUnit in `test-results/` (la CI lo mostra nella scheda Tests).
215
+ Every suite also writes a JUnit report to `test-results/` (CI shows it in the Tests tab).
195
216
 
196
- | Suite | Cosa verifica |
217
+ | Suite | What it verifies |
197
218
  |---|---|
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 |
219
+ | `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 |
220
+ | `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 |
221
+ | `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) |
222
+ | `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
223
 
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.
224
+ 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
225
 
205
- ## Note
226
+ ## Notes
206
227
 
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.
228
+ - **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.
229
+ - **Automatic refresh** (`refreshSec`): only if the slot is on screen and the tab is visible. GPT and AdSense have their own refresh policies.
230
+ - **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.2" : "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.2" : "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.2" : "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.2";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()}})();})();