universal-ad-loader 0.0.0-stage → 0.1.0
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 +16 -0
- package/LICENSE +21 -0
- package/README.md +208 -2
- package/astro/AdSlot.astro +23 -0
- package/astro/UalBundled.astro +34 -0
- package/astro/UalLoader.astro +40 -0
- package/dist/global.d.ts +13 -0
- package/dist/index.cjs +1288 -0
- package/dist/index.d.ts +281 -0
- package/dist/index.mjs +1266 -0
- package/dist/next.cjs +35 -0
- package/dist/next.d.ts +8 -0
- package/dist/next.mjs +16 -0
- package/dist/react.cjs +122 -0
- package/dist/react.d.ts +36 -0
- package/dist/react.mjs +103 -0
- package/dist/ual.js +1277 -0
- package/dist/ual.min.js +12 -0
- package/dist/vue.cjs +128 -0
- package/dist/vue.d.ts +21 -0
- package/dist/vue.mjs +108 -0
- package/docs/ad-server-esistenti.md +78 -0
- package/docs/cdn.md +58 -0
- package/docs/esempio.md +156 -0
- package/docs/libreria.md +142 -0
- package/docs/pubblicazione-npm.md +114 -0
- package/package.json +119 -4
- package/schema/ual-config.schema.json +191 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 0.1.0
|
|
2
|
+
|
|
3
|
+
Prima versione.
|
|
4
|
+
|
|
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.
|
|
12
|
+
|
|
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'`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ArdensLab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,209 @@
|
|
|
1
|
-
#
|
|
1
|
+
# UAL — Universal Ad Loader
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Installazione su un sito
|
|
6
|
+
|
|
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>
|
|
10
|
+
|
|
11
|
+
<div data-ad-slot="top"></div>
|
|
12
|
+
<div data-ad-slot="sidebar" data-ad-params='{"zone":7}'></div>
|
|
13
|
+
```
|
|
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()`.
|
|
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`.
|
|
20
|
+
|
|
21
|
+
## Configurazione (JSON servito da CDN)
|
|
22
|
+
|
|
23
|
+
Vedi `examples/site_demo.json`. Campi principali:
|
|
24
|
+
|
|
25
|
+
| Campo | Descrizione |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `site` | ID del sito, inviato agli adapter |
|
|
28
|
+
| `consent` | `{ mode:'tcf'\|'none', timeoutMs, requiredPurposes:[1], vendorId }` |
|
|
29
|
+
| `lazy` | `{ enabled, fetchMargin:'200px' }`. Lo slot viene richiesto quando arriva a quella distanza dal viewport |
|
|
30
|
+
| `viewability` | `{ threshold:0.5, durationMs:1000 }` (standard MRC) |
|
|
31
|
+
| `collapseEmpty` | nasconde lo slot se nessun provider lo riempie |
|
|
32
|
+
| `providerTimeoutMs` | timeout di default per provider |
|
|
33
|
+
| `targeting` | key-value di pagina inviati a tutti gli adapter |
|
|
34
|
+
| `providers` | `{ id: { adapter:'json'\|'iframe'\|'revive'\|'revive-async'\|'gpt'\|'adsense'\|'house', consent:'optional'\|'required'\|'contextual', timeoutMs, ...opzioni adapter } }` |
|
|
35
|
+
| `slots` | `{ nome: { sizes \| sizeMapping, providers:[...waterfall], label, minHeight, lazy, collapseEmpty, refreshSec, maxRefresh, enabled } }` |
|
|
36
|
+
| `pageRules` | `[{ match:'^/on-air', disable:[...], enable:[...], targeting:{}, slots:{ override } }]` |
|
|
37
|
+
|
|
38
|
+
**Waterfall**: `providers` di uno slot è una lista ordinata (`"house"` oppure `{ "id":"main", "params":{...} }`). Se il primo non riempie, si passa al successivo. Per ogni livello, gli slot pronti insieme vengono raggruppati in **una sola richiesta per provider**.
|
|
39
|
+
|
|
40
|
+
**Consenso per provider** (`providers.X.consent`):
|
|
41
|
+
|
|
42
|
+
| Valore | Comportamento |
|
|
43
|
+
|---|---|
|
|
44
|
+
| `optional` (default) | Aspetta la risposta della CMP (max `consent.timeoutMs`) e riceve il consenso così com'è |
|
|
45
|
+
| `required` | Come `optional`, ma se il consenso non c'è il provider viene **saltato** e si passa al livello successivo (es. un fallback house contestuale) |
|
|
46
|
+
| `contextual` | **Non aspetta la CMP**: parte subito e riceve il consenso noto in quel momento (spesso `granted:false`). Per richieste contestuali senza cookie né ID |
|
|
47
|
+
|
|
48
|
+
Il consenso viene seguito per tutta la vita della pagina: se la CMP compare in ritardo (fino a `consent.lateWatchMs`, default 30 s) o l'utente cambia scelta, UAL emette l'evento `consent` e, quando si passa a `granted:true`, ricarica gli slot che avevano saltato un provider `required`.
|
|
49
|
+
|
|
50
|
+
## Adapter inclusi
|
|
51
|
+
|
|
52
|
+
| Adapter | Uso | Rileva il vuoto? |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `json` | Il tuo ad server: POST batch su `endpoint` (contratto sotto) | Sì |
|
|
55
|
+
| `iframe` | Qualsiasi URL che restituisce il banner (`work.php?n=..&size=..`) con template `{params.x}`, `{width}`, `{cb}`… | No (considerato pieno) |
|
|
56
|
+
| `revive` | Revive Adserver via `afr.php` (iframe) | No |
|
|
57
|
+
| `revive-async` | Revive via `asyncjs.php` + `<ins>` | Sì (osserva il DOM) |
|
|
58
|
+
| `gpt` | Google Ad Manager, SRA con `disableInitialLoad` + `refresh` | Sì (`slotRenderEnded`) |
|
|
59
|
+
| `adsense` | AdSense come backfill | Sì (`data-ad-status`) |
|
|
60
|
+
| `house` | Creatività in config, scelta pesata per slot/dispositivo/date | Sì |
|
|
61
|
+
|
|
62
|
+
### Contratto dell'adapter `json`
|
|
63
|
+
|
|
64
|
+
Richiesta `POST {endpoint}`:
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"site": "site_123",
|
|
68
|
+
"page": { "url": "...", "path": "/", "ref": "...", "title": "..." },
|
|
69
|
+
"device": "desktop", "viewport": { "w": 1280, "h": 900 },
|
|
70
|
+
"targeting": { "section": "onair" },
|
|
71
|
+
"consent": { "gdprApplies": true, "tcString": "...", "granted": true },
|
|
72
|
+
"slots": [ { "id": "top#1", "name": "top", "sizes": [[728,90]], "params": { "zone": 1 } } ]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
Risposta (200 oppure 204 se vuota):
|
|
76
|
+
```json
|
|
77
|
+
{ "ads": [ {
|
|
78
|
+
"slotId": "top#1", "type": "image", "src": "https://cdn/.../b.webp",
|
|
79
|
+
"width": 728, "height": 90, "clickUrl": "https://ads/c?t=SIGNED",
|
|
80
|
+
"trackers": { "impression": ["https://ads/i?t=..."], "viewable": ["https://ads/v?t=..."], "click": [] }
|
|
81
|
+
} ] }
|
|
82
|
+
```
|
|
83
|
+
`type`: `image` (src + clickUrl), `html` (markup, `clickTag` disponibile), `url` (iframe esterno). Gli slot senza voce passano al provider successivo.
|
|
84
|
+
|
|
85
|
+
## Render e API per le creatività
|
|
86
|
+
|
|
87
|
+
Le creatività `image`/`html` girano in un iframe `srcdoc` con sandbox **senza** `allow-same-origin` (origine opaca): non possono leggere la pagina, i cookie o il DOM del sito. Dentro l'iframe sono disponibili:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
clickTag // = clickUrl
|
|
91
|
+
UALExt.click() // traccia il click e apre clickTag
|
|
92
|
+
UALExt.expand(728, 300) // ridimensiona (max 2000px)
|
|
93
|
+
UALExt.collapse() // torna alla dimensione originale
|
|
94
|
+
UALExt.close() // chiude e nasconde lo slot
|
|
95
|
+
```
|
|
96
|
+
I messaggi vengono accettati solo dall'iframe dello slot (controllo su `event.source`).
|
|
97
|
+
|
|
98
|
+
**Validazione delle creatività.** Ogni annuncio ricevuto da un adapter passa da un controllo prima del render; se non è valido viene scartato (log + evento `error`) e si passa al provider successivo:
|
|
99
|
+
|
|
100
|
+
- `width` e `height` obbligatori, interi tra 1 e 2000.
|
|
101
|
+
- `src` (image), `url` (url), `clickUrl` e tutti i `trackers`: solo `http:`/`https:` (per `src` anche `data:image/`). Niente `javascript:`.
|
|
102
|
+
- Sandbox: mai `allow-same-origin` per `image`/`html`; mai `allow-top-navigation` (la creatività non può reindirizzare la pagina; i link `target=_blank` aprono un popup). Per `type:'url'` `allow-same-origin` resta (serve ai server legacy) **solo se l'URL è di un'altra origine** della pagina; se è della stessa origine viene tolto. L'override `sandbox` di una creatività è filtrato con le stesse regole.
|
|
103
|
+
|
|
104
|
+
## API JavaScript
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
window.ual = window.ual || { cmd: [] };
|
|
108
|
+
ual.cmd.push(function (u) {
|
|
109
|
+
u.on('slotRendered', (e) => console.log(e.slot, e.provider, e.width, e.height));
|
|
110
|
+
u.on('slotEmpty', (e) => {});
|
|
111
|
+
u.on('slotViewable', (e) => {});
|
|
112
|
+
u.on('slotClick', (e) => {});
|
|
113
|
+
u.on('error', (e) => {});
|
|
114
|
+
u.setTargeting({ author: 'mario' });
|
|
115
|
+
});
|
|
116
|
+
// Senza data-config: ual.cmd.push(u => u.init({ ...config })) oppure u.init('https://.../site.json')
|
|
117
|
+
ual.refresh(['top']); // ricarica slot (tutti se senza argomenti)
|
|
118
|
+
ual.pageView(); // SPA: nuova pagina
|
|
119
|
+
ual.getSlots(); ual.getConsent();
|
|
120
|
+
ual.isInitialized(); // true se init è già stato avviato (anche se non ancora finito)
|
|
121
|
+
ual.whenReady(); // la promessa dell'init, o null se mai avviato
|
|
122
|
+
```
|
|
123
|
+
`setTargeting` funziona anche prima dell'init (dentro `ual.cmd.push`), resta valido dopo `pageView()` e un valore `null` rimuove la chiave. Il targeting delle `pageRules` viene ricalcolato a ogni pagina. L'adapter `gpt` lo riapplica a ogni richiesta e toglie le chiavi non più presenti.
|
|
124
|
+
|
|
125
|
+
L'attributo sullo script può chiamarsi `data-config` o `data-ual-config` (è equivalente; il secondo si legge anche se lo script è stato inserito dinamicamente da un framework).
|
|
126
|
+
|
|
127
|
+
Debug: `?ual_debug=1` nell'URL, oppure `localStorage.ual_debug = '1'`.
|
|
128
|
+
|
|
129
|
+
## Con Astro, Next.js, React, Vue e Node
|
|
130
|
+
|
|
131
|
+
UAL è uno script del browser (non gira in Node). Per i siti con una build c'è anche il **pacchetto**: import sicuro sul server, componenti per React, Next.js, Vue e Astro, tipi TypeScript (`defineConfig`). Vedi `docs/libreria.md`. **Il pacchetto `universal-ad-loader` non è ancora pubblicato su npm** (vedi `docs/pubblicazione-npm.md`): per ora si installa dal tarball di `npm pack`.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import { UalProvider, AdSlot } from 'universal-ad-loader/next'; // o /react, /vue; per Astro i file in astro/
|
|
135
|
+
|
|
136
|
+
<UalProvider config={config}> {/* parte dopo l'idratazione, gestisce il cambio pagina */}
|
|
137
|
+
<AdSlot name="top" minHeight={90} />
|
|
138
|
+
</UalProvider>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Se usi invece il tag con un framework, cose verificate con un'app React server-rendered:
|
|
142
|
+
|
|
143
|
+
- **Carica il loader dopo l'idratazione** (`next/script` con `strategy="afterInteractive"`, o un `useEffect`). Se parte prima, UAL inserisce il banner nel `div` prima che React si attacchi: errore *Hydration failed* (anche con `suppressHydrationWarning`) e banner richiesto due volte. I componenti lo evitano da soli.
|
|
144
|
+
- **Cambio pagina lato client:** gli slot nuovi si registrano da soli, ma `pageRules` e targeting si ricalcolano solo con `ual.pageView()`.
|
|
145
|
+
- **Non importare `dist/ual.js` in codice server:** usa `window` al caricamento (`ReferenceError` in Node). Il nucleo del pacchetto (`dist/index.*`) invece è sicuro.
|
|
146
|
+
- **CSP rigida:** le creatività ereditano la CSP della pagina. Provato: senza `'unsafe-inline'` per script e stili il banner compare ma `UALExt` e il click tracciato non funzionano; con nonce e `'strict-dynamic'` nemmeno.
|
|
147
|
+
|
|
148
|
+
Guida con esempi e tabella CSP: configuratore, pagina `/docs/`.
|
|
149
|
+
|
|
150
|
+
## Altri documenti
|
|
151
|
+
|
|
152
|
+
- `docs/esempio.md`: sito cliente completo (config, flusso, endpoint `decide` minimo).
|
|
153
|
+
- `docs/pubblicazione-npm.md`: come pubblicare il pacchetto su npm (CI GitLab con il branch `deploy-npm`, token, mirror GitHub, rilasci).
|
|
154
|
+
- `docs/cdn.md`: **quali file mettere sulla CDN** (loader e configurazione), header, struttura, cosa non serve e come aggiornare.
|
|
155
|
+
- `docs/libreria.md`: UAL come pacchetto (componenti React/Next/Vue/Astro, tipi, nucleo sicuro sul server, come è verificato).
|
|
156
|
+
- `docs/ad-server-esistenti.md`: come collegare AdManager, Banner Exchange Pro, Banner Exchange e i PHP legacy con l'adapter `iframe`, con limiti e verifiche.
|
|
157
|
+
- `schema/ual-config.schema.json`: JSON Schema della configurazione.
|
|
158
|
+
- Il configuratore web (`../ual-configurator`) ha una guida HTML con esempi apribili nell'editor.
|
|
159
|
+
|
|
160
|
+
## Adapter custom
|
|
161
|
+
|
|
162
|
+
```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:
|
|
166
|
+
async request(slots, providerCfg, ctx) { // slots: [{id,name,sizes,params,el}]
|
|
167
|
+
return { [slots[0].id]: { type:'image', src:'...', width:300, height:250, clickUrl:'...' } };
|
|
168
|
+
},
|
|
169
|
+
// B) oppure l'adapter rende da solo dentro slot.el:
|
|
170
|
+
// async fill(slots, providerCfg, ctx) { return { [id]: { filled:true, width, height } } },
|
|
171
|
+
destroy(slot, providerCfg) {}, // opzionale
|
|
172
|
+
}));
|
|
173
|
+
```
|
|
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.
|
|
175
|
+
|
|
176
|
+
## Build e test
|
|
177
|
+
|
|
178
|
+
```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
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Per lavorare per parti (è lo stesso che fa la CI, in job paralleli):
|
|
186
|
+
|
|
187
|
+
| Comando | Cosa esegue | Serve |
|
|
188
|
+
|---|---|---|
|
|
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 |
|
|
193
|
+
|
|
194
|
+
Ogni suite scrive anche un report JUnit in `test-results/` (la CI lo mostra nella scheda Tests).
|
|
195
|
+
|
|
196
|
+
| Suite | Cosa verifica |
|
|
197
|
+
|---|---|
|
|
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 |
|
|
202
|
+
|
|
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.
|
|
204
|
+
|
|
205
|
+
## Note
|
|
206
|
+
|
|
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.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Una posizione pubblicitaria: un div vuoto che UAL riempie.
|
|
3
|
+
// <AdSlot name="top" minHeight={90} params={{ zona: 7 }} />
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Nome dello slot in configurazione (data-ad-slot). */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Sovrascrive i `params` del provider per questa istanza. */
|
|
8
|
+
params?: Record<string, unknown>;
|
|
9
|
+
/** Riserva lo spazio già nell'HTML (evita salti di layout). */
|
|
10
|
+
minHeight?: number;
|
|
11
|
+
class?: string;
|
|
12
|
+
id?: string;
|
|
13
|
+
}
|
|
14
|
+
const { name, params, minHeight, class: className, id } = Astro.props;
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
data-ad-slot={name}
|
|
19
|
+
data-ad-params={params ? JSON.stringify(params) : undefined}
|
|
20
|
+
id={id}
|
|
21
|
+
class={className}
|
|
22
|
+
style={minHeight ? `min-height:${minHeight}px` : undefined}
|
|
23
|
+
></div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Carica UAL incluso nel bundle del sito (nessuna CDN per il loader), con la configurazione nella pagina.
|
|
3
|
+
// <UalBundled config={{ site: '…', providers: {…}, slots: {…} }} />
|
|
4
|
+
// <UalBundled config="/config/sito.json" />
|
|
5
|
+
// Per caricare il loader da una CDN usa invece UalLoader.
|
|
6
|
+
// La configurazione sta in un blocco JSON (non eseguito, quindi compatibile con una CSP rigida).
|
|
7
|
+
import type { UalConfig } from '../dist/index';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** La configurazione, oppure l'URL del JSON. */
|
|
11
|
+
config: UalConfig | string;
|
|
12
|
+
}
|
|
13
|
+
const { config } = Astro.props;
|
|
14
|
+
const data = JSON.stringify(config).replace(/</g, '\\u003c');
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<script type="application/json" data-ual-config set:html={data}></script>
|
|
18
|
+
<script>
|
|
19
|
+
import { getUal } from '../dist/index.mjs';
|
|
20
|
+
|
|
21
|
+
const el = document.querySelector('script[data-ual-config]');
|
|
22
|
+
if (el && el.textContent) {
|
|
23
|
+
const ual = getUal();
|
|
24
|
+
if (!ual.isInitialized()) {
|
|
25
|
+
ual.init(JSON.parse(el.textContent)).catch((e) => console.warn('[UAL]', e && e.message ? e.message : e));
|
|
26
|
+
}
|
|
27
|
+
// View Transitions: il primo astro:page-load è il caricamento iniziale, gli altri sono navigazioni
|
|
28
|
+
let first = true;
|
|
29
|
+
document.addEventListener('astro:page-load', () => {
|
|
30
|
+
if (first) { first = false; return; }
|
|
31
|
+
ual.pageView();
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Carica UAL in una pagina Astro con il loader servito da una CDN (aggiornabile senza rideploy del sito).
|
|
3
|
+
// <UalLoader src="https://cdn.tuodominio.net/ual.min.js" config="https://cdn.tuodominio.net/config/sito.json" />
|
|
4
|
+
// <UalLoader src="…/ual.min.js" config={{ site: '…', providers: {…}, slots: {…} }} />
|
|
5
|
+
// Per includere UAL nel bundle del sito usa invece UalBundled.
|
|
6
|
+
// Con le View Transitions (ClientRouter) ricalcola le regole per pagina a ogni navigazione.
|
|
7
|
+
import type { UalConfig } from '../dist/index';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
/** URL del loader (ual.min.js). */
|
|
11
|
+
src: string;
|
|
12
|
+
/** URL del JSON di configurazione, oppure la configurazione stessa. */
|
|
13
|
+
config: UalConfig | string;
|
|
14
|
+
}
|
|
15
|
+
const { src, config } = Astro.props;
|
|
16
|
+
const isUrl = typeof config === 'string';
|
|
17
|
+
// "<" escapato: i dati non possono chiudere lo script
|
|
18
|
+
const data = JSON.stringify(config).replace(/</g, '\\u003c');
|
|
19
|
+
const init = `window.ual=window.ual||{cmd:[]};window.ual.cmd.push(function(u){u.init(${data})});`;
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
{isUrl
|
|
23
|
+
? <script is:inline async src={src} data-ual-config={config as string}></script>
|
|
24
|
+
: (
|
|
25
|
+
<Fragment>
|
|
26
|
+
<script is:inline set:html={init}></script>
|
|
27
|
+
<script is:inline async src={src}></script>
|
|
28
|
+
</Fragment>
|
|
29
|
+
)}
|
|
30
|
+
<script is:inline>
|
|
31
|
+
(function () {
|
|
32
|
+
// Il primo astro:page-load coincide con il caricamento iniziale: lì UAL parte da solo
|
|
33
|
+
var first = true;
|
|
34
|
+
document.addEventListener('astro:page-load', function () {
|
|
35
|
+
if (first) { first = false; return; }
|
|
36
|
+
var ual = window.ual;
|
|
37
|
+
if (ual && ual.cmd && typeof ual.cmd.push === 'function') ual.cmd.push(function (u) { u.pageView(); });
|
|
38
|
+
});
|
|
39
|
+
})();
|
|
40
|
+
</script>
|
package/dist/global.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Tipi opzionali per chi usa il tag <script> (window.ual): import 'universal-ad-loader/global';
|
|
2
|
+
// Sono in un file a parte perché dichiarano una proprietà globale di Window, che potrebbe
|
|
3
|
+
// confliggere con una dichiarazione già presente nel progetto.
|
|
4
|
+
import type { Ual } from './index';
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
/** Prima del caricamento del loader è solo la coda dei comandi; dopo è l'API completa. */
|
|
9
|
+
ual?: (Partial<Ual> & { cmd: { push(fn: (u: Ual) => void): void } | Array<(u: Ual) => void> });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export {};
|