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 +20 -12
- package/README.md +104 -105
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/ual.js +1 -1
- package/dist/ual.min.js +1 -1
- package/docs/ad-server-esistenti.md +43 -43
- package/docs/cdn.md +32 -32
- package/docs/esempio.md +49 -49
- package/docs/libreria.md +50 -50
- package/package.json +8 -1
- package/docs/pubblicazione-npm.md +0 -114
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
|
-
|
|
11
|
+
First release.
|
|
4
12
|
|
|
5
|
-
**
|
|
6
|
-
- Loader
|
|
7
|
-
lazy load, waterfall
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
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
|
-
**
|
|
14
|
-
- `adsense`, `revive-async`
|
|
15
|
-
- Nuxt, Remix
|
|
16
|
-
-
|
|
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
|
-
|
|
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
|
-
##
|
|
5
|
+
## Installing on a site
|
|
6
6
|
|
|
7
7
|
```html
|
|
8
|
-
<script async src="https://adcdn.
|
|
9
|
-
data-config="https://adcdn.
|
|
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`
|
|
16
|
-
- `data-ad-params` (
|
|
17
|
-
-
|
|
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
|
-
> **
|
|
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
|
-
##
|
|
21
|
+
## Configuration (JSON served from the CDN)
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
See `examples/site_demo.json`. Main fields:
|
|
24
24
|
|
|
25
|
-
|
|
|
25
|
+
| Field | Description |
|
|
26
26
|
|---|---|
|
|
27
|
-
| `site` | ID
|
|
27
|
+
| `site` | Site ID, sent to the adapters |
|
|
28
28
|
| `consent` | `{ mode:'tcf'\|'none', timeoutMs, requiredPurposes:[1], vendorId }` |
|
|
29
|
-
| `lazy` | `{ enabled, fetchMargin:'200px' }`.
|
|
30
|
-
| `viewability` | `{ threshold:0.5, durationMs:1000 }` (standard
|
|
31
|
-
| `collapseEmpty` |
|
|
32
|
-
| `providerTimeoutMs` | timeout
|
|
33
|
-
| `targeting` | key-
|
|
34
|
-
| `providers` | `{ id: { adapter:'json'\|'iframe'\|'revive'\|'revive-async'\|'gpt'\|'adsense'\|'house', consent:'optional'\|'required'\|'contextual', timeoutMs, ...
|
|
35
|
-
| `slots` | `{
|
|
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`
|
|
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
|
-
**
|
|
40
|
+
**Consent per provider** (`providers.X.consent`):
|
|
41
41
|
|
|
42
|
-
|
|
|
42
|
+
| Value | Behavior |
|
|
43
43
|
|---|---|
|
|
44
|
-
| `optional` (default) |
|
|
45
|
-
| `required` |
|
|
46
|
-
| `contextual` | **
|
|
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
|
-
|
|
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
|
-
##
|
|
50
|
+
## Included adapters
|
|
51
51
|
|
|
52
|
-
| Adapter |
|
|
52
|
+
| Adapter | Use | Detects empty? |
|
|
53
53
|
|---|---|---|
|
|
54
|
-
| `json` |
|
|
55
|
-
| `iframe` |
|
|
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>` |
|
|
58
|
-
| `gpt` | Google Ad Manager, SRA
|
|
59
|
-
| `adsense` | AdSense
|
|
60
|
-
| `house` |
|
|
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
|
-
###
|
|
62
|
+
### The `json` adapter contract
|
|
63
63
|
|
|
64
|
-
|
|
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
|
-
|
|
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`
|
|
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
|
-
##
|
|
85
|
+
## Rendering and creative API
|
|
86
86
|
|
|
87
|
-
|
|
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() //
|
|
92
|
-
UALExt.expand(728, 300) //
|
|
93
|
-
UALExt.collapse() //
|
|
94
|
-
UALExt.close() //
|
|
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
|
-
|
|
96
|
+
Messages are accepted only from the slot's own iframe (checked on `event.source`).
|
|
97
97
|
|
|
98
|
-
**
|
|
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`
|
|
101
|
-
- `src` (image), `url` (url), `clickUrl`
|
|
102
|
-
- Sandbox:
|
|
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
|
|
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
|
-
//
|
|
117
|
-
ual.refresh(['top']); //
|
|
118
|
-
ual.pageView(); // SPA:
|
|
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
|
|
121
|
-
ual.whenReady(); //
|
|
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`
|
|
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
|
-
|
|
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`
|
|
127
|
+
Debug: `?ual_debug=1` in the URL, or `localStorage.ual_debug = '1'`.
|
|
128
128
|
|
|
129
|
-
##
|
|
129
|
+
## With Astro, Next.js, React, Vue and Node
|
|
130
130
|
|
|
131
|
-
UAL
|
|
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'; //
|
|
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}> {/*
|
|
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
|
-
|
|
141
|
+
If you use the tag with a framework instead, things verified with a server-rendered React app:
|
|
142
142
|
|
|
143
|
-
- **
|
|
144
|
-
- **
|
|
145
|
-
- **
|
|
146
|
-
- **CSP
|
|
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
|
-
|
|
148
|
+
Guide with examples and a CSP table: configurator, `/docs/` page.
|
|
149
149
|
|
|
150
|
-
##
|
|
150
|
+
## Other documents
|
|
151
151
|
|
|
152
|
-
- `docs/esempio.md`:
|
|
153
|
-
- `docs/
|
|
154
|
-
- `docs/
|
|
155
|
-
- `docs/
|
|
156
|
-
- `
|
|
157
|
-
-
|
|
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
|
-
##
|
|
159
|
+
## Custom adapters
|
|
161
160
|
|
|
162
161
|
```js
|
|
163
|
-
ual.cmd.push(u => u.registerAdapter('
|
|
164
|
-
init(providerCfg, ctx) {}, //
|
|
165
|
-
// A)
|
|
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)
|
|
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) {}, //
|
|
170
|
+
destroy(slot, providerCfg) {}, // optional
|
|
172
171
|
}));
|
|
173
172
|
```
|
|
174
|
-
`ctx`
|
|
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
|
|
175
|
+
## Build and tests
|
|
177
176
|
|
|
178
177
|
```bash
|
|
179
|
-
npm ci #
|
|
180
|
-
npm ci --prefix test/heavy #
|
|
181
|
-
npx playwright install chromium #
|
|
182
|
-
npm test # build +
|
|
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
|
-
|
|
184
|
+
To work on one part at a time (this is what CI does, in parallel jobs):
|
|
186
185
|
|
|
187
|
-
|
|
|
186
|
+
| Command | What it runs | Needs |
|
|
188
187
|
|---|---|---|
|
|
189
|
-
| `npm run lint` | oxlint
|
|
190
|
-
| `npm run test:node` | `test/package.mjs` |
|
|
191
|
-
| `npm run test:browser` | `test/run.mjs`
|
|
192
|
-
| `npm run test:frameworks` | `test/wrappers.mjs` | + `test/heavy`
|
|
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
|
-
|
|
193
|
+
Every suite also writes a JUnit report to `test-results/` (CI shows it in the Tests tab).
|
|
195
194
|
|
|
196
|
-
| Suite |
|
|
195
|
+
| Suite | What it verifies |
|
|
197
196
|
|---|---|
|
|
198
|
-
| `test/run.mjs` | e2e
|
|
199
|
-
| `test/regression.mjs` |
|
|
200
|
-
| `test/package.mjs` |
|
|
201
|
-
| `test/wrappers.mjs` |
|
|
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
|
-
|
|
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
|
-
##
|
|
204
|
+
## Notes
|
|
206
205
|
|
|
207
|
-
- **
|
|
208
|
-
- **
|
|
209
|
-
- **CLS**:
|
|
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
package/dist/index.mjs
CHANGED
package/dist/ual.js
CHANGED
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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}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,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}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()}})();})();
|