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/dist/index.mjs ADDED
@@ -0,0 +1,1266 @@
1
+ // src/core/events.js
2
+ function createEmitter() {
3
+ const handlers = {};
4
+ return {
5
+ on(evt, fn) {
6
+ (handlers[evt] = handlers[evt] || []).push(fn);
7
+ return () => this.off(evt, fn);
8
+ },
9
+ off(evt, fn) {
10
+ handlers[evt] = (handlers[evt] || []).filter((h) => h !== fn);
11
+ },
12
+ emit(evt, data) {
13
+ [...handlers[evt] || [], ...handlers["*"] || []].forEach((h) => {
14
+ try {
15
+ h(data, evt);
16
+ } catch (e) {
17
+ console.error("[UAL] handler error", evt, e);
18
+ }
19
+ });
20
+ }
21
+ };
22
+ }
23
+
24
+ // src/core/utils.js
25
+ var PREFIX = "[UAL]";
26
+ var debugOn = false;
27
+ try {
28
+ debugOn = /[?&]ual_debug=1/.test(location.search) || localStorage.getItem("ual_debug") === "1";
29
+ } catch (e) {
30
+ }
31
+ function setDebug(v) {
32
+ debugOn = !!v;
33
+ }
34
+ function isDebug() {
35
+ return debugOn;
36
+ }
37
+ function log(...a) {
38
+ if (debugOn) console.log(PREFIX, ...a);
39
+ }
40
+ function warn(...a) {
41
+ console.warn(PREFIX, ...a);
42
+ }
43
+ function loadScript(src, attrs = {}) {
44
+ return new Promise((resolve, reject) => {
45
+ const existing = document.querySelector(`script[data-ual-src="${src}"]`);
46
+ if (existing) {
47
+ if (existing.dataset.loaded) return resolve();
48
+ existing.addEventListener("load", () => resolve());
49
+ existing.addEventListener("error", reject);
50
+ return;
51
+ }
52
+ const s = document.createElement("script");
53
+ s.async = true;
54
+ s.src = src;
55
+ s.dataset.ualSrc = src;
56
+ Object.entries(attrs).forEach(([k, v]) => s.setAttribute(k, v));
57
+ s.onload = () => {
58
+ s.dataset.loaded = "1";
59
+ resolve();
60
+ };
61
+ s.onerror = () => reject(new Error("Script load failed: " + src));
62
+ document.head.appendChild(s);
63
+ });
64
+ }
65
+ function template(str, vars) {
66
+ return str.replace(/\{([\w.]+)\}/g, (_, path2) => {
67
+ const v = path2.split(".").reduce((o, k) => o == null ? void 0 : o[k], vars);
68
+ return v == null ? "" : encodeURIComponent(String(v));
69
+ });
70
+ }
71
+ function fire(urls) {
72
+ (urls || []).forEach((u) => {
73
+ if (!u) return;
74
+ try {
75
+ if (navigator.sendBeacon && navigator.sendBeacon(u)) return;
76
+ } catch (e) {
77
+ }
78
+ const img = new Image(1, 1);
79
+ img.src = u;
80
+ });
81
+ }
82
+ function withTimeout(promise, ms, fallbackValue) {
83
+ let t;
84
+ return Promise.race([
85
+ promise,
86
+ new Promise((resolve) => {
87
+ t = setTimeout(() => resolve(fallbackValue), ms);
88
+ })
89
+ ]).finally(() => clearTimeout(t));
90
+ }
91
+ function cb() {
92
+ return Math.floor(Math.random() * 1e10);
93
+ }
94
+ function deviceType() {
95
+ const w = window.innerWidth || document.documentElement.clientWidth;
96
+ return w < 768 ? "mobile" : w < 1024 ? "tablet" : "desktop";
97
+ }
98
+
99
+ // src/core/config.js
100
+ var DEFAULTS = {
101
+ site: null,
102
+ consent: { mode: "tcf", timeoutMs: 4e3, requiredPurposes: [1] },
103
+ lazy: { enabled: true, fetchMargin: "200px" },
104
+ viewability: { threshold: 0.5, durationMs: 1e3 },
105
+ collapseEmpty: true,
106
+ providerTimeoutMs: 3e3,
107
+ targeting: {},
108
+ providers: {},
109
+ slots: {},
110
+ pageRules: []
111
+ };
112
+ async function loadConfig(source) {
113
+ let cfg = source;
114
+ if (typeof source === "string") {
115
+ const res = await fetch(source, { credentials: "omit" });
116
+ if (!res.ok) throw new Error("Config HTTP " + res.status);
117
+ cfg = await res.json();
118
+ }
119
+ return merge(DEFAULTS, cfg || {});
120
+ }
121
+ var CONSENT_MODES = ["optional", "required", "contextual"];
122
+ function validateConfig(cfg, adapters) {
123
+ const w = (msg) => warn("Config: " + msg);
124
+ if (!cfg.site) w('manca "site"');
125
+ for (const [pid, p] of Object.entries(cfg.providers || {})) {
126
+ if (!p || !p.adapter) {
127
+ w(`provider "${pid}" senza "adapter"`);
128
+ continue;
129
+ }
130
+ if (adapters && !adapters.has(p.adapter)) w(`provider "${pid}": adapter "${p.adapter}" non registrato (registralo con registerAdapter prima dell'init)`);
131
+ if (p.consent && !CONSENT_MODES.includes(p.consent)) w(`provider "${pid}": consent "${p.consent}" non valido (${CONSENT_MODES.join("|")})`);
132
+ }
133
+ for (const [name, s] of Object.entries(cfg.slots || {})) {
134
+ if (!s || !Array.isArray(s.providers) || !s.providers.length) {
135
+ w(`slot "${name}" senza "providers"`);
136
+ continue;
137
+ }
138
+ s.providers.forEach((e) => {
139
+ const id = typeof e === "string" ? e : e && e.id;
140
+ if (!id || !cfg.providers[id]) w(`slot "${name}": provider "${id}" non definito in "providers"`);
141
+ });
142
+ }
143
+ }
144
+ function merge(a, b) {
145
+ const out = { ...a };
146
+ for (const k of Object.keys(b)) {
147
+ const v = b[k];
148
+ out[k] = v && typeof v === "object" && !Array.isArray(v) && a[k] && typeof a[k] === "object" && !Array.isArray(a[k]) ? merge(a[k], v) : v;
149
+ }
150
+ return out;
151
+ }
152
+ function resolveForPage(cfg, path2 = location.pathname) {
153
+ const resolved = { ...cfg, slots: JSON.parse(JSON.stringify(cfg.slots)), targeting: { ...cfg.targeting } };
154
+ for (const rule of cfg.pageRules || []) {
155
+ let re;
156
+ try {
157
+ re = new RegExp(rule.match);
158
+ } catch (e) {
159
+ warn("pageRule regex non valida", rule.match);
160
+ continue;
161
+ }
162
+ if (!re.test(path2)) continue;
163
+ (rule.disable || []).forEach((id) => {
164
+ if (resolved.slots[id]) resolved.slots[id].enabled = false;
165
+ });
166
+ (rule.enable || []).forEach((id) => {
167
+ if (resolved.slots[id]) resolved.slots[id].enabled = true;
168
+ });
169
+ Object.assign(resolved.targeting, rule.targeting || {});
170
+ Object.entries(rule.slots || {}).forEach(([id, o]) => {
171
+ resolved.slots[id] = { ...resolved.slots[id], ...o };
172
+ });
173
+ }
174
+ return resolved;
175
+ }
176
+ function sizesFor(slotCfg) {
177
+ const w = window.innerWidth || document.documentElement.clientWidth;
178
+ if (Array.isArray(slotCfg.sizeMapping) && slotCfg.sizeMapping.length) {
179
+ const match = [...slotCfg.sizeMapping].sort((x, y) => y.minWidth - x.minWidth).find((m) => w >= m.minWidth);
180
+ return match ? match.sizes : [];
181
+ }
182
+ return slotCfg.sizes || [];
183
+ }
184
+
185
+ // src/core/consent.js
186
+ function waitForTcfApi(ms) {
187
+ return new Promise((resolve) => {
188
+ if (typeof window.__tcfapi === "function") return resolve(true);
189
+ const start = Date.now();
190
+ const iv = setInterval(() => {
191
+ if (typeof window.__tcfapi === "function") {
192
+ clearInterval(iv);
193
+ resolve(true);
194
+ } else if (Date.now() - start > ms) {
195
+ clearInterval(iv);
196
+ resolve(false);
197
+ }
198
+ }, 50);
199
+ });
200
+ }
201
+ function listen(opts, handler) {
202
+ window.__tcfapi("addEventListener", 2, (tcData, success) => {
203
+ if (!success || !tcData) return;
204
+ if (tcData.gdprApplies === false || tcData.eventStatus === "tcloaded" || tcData.eventStatus === "useractioncomplete") {
205
+ handler(normalize(tcData, opts));
206
+ }
207
+ });
208
+ }
209
+ var TIMEOUT = /* @__PURE__ */ Symbol("timeout");
210
+ async function getConsent(cfg = {}, onUpdate) {
211
+ const opts = {
212
+ mode: "tcf",
213
+ // 'tcf' | 'none' (nessuna CMP, consenso considerato non necessario)
214
+ timeoutMs: 4e3,
215
+ // attesa massima della scelta utente / CMP
216
+ cmpWaitMs: 1500,
217
+ // attesa massima che __tcfapi compaia
218
+ lateWatchMs: 3e4,
219
+ // dopo cmpWaitMs, per quanto tempo continuare a cercare una CMP che arriva tardi
220
+ requiredPurposes: [1],
221
+ // purpose necessarie per "granted"
222
+ vendorId: null,
223
+ // ID GVL del tuo ad server, se registrato
224
+ ...cfg
225
+ };
226
+ if (opts.mode === "none") {
227
+ return { gdprApplies: false, tcString: "", purposes: {}, granted: true, source: "none" };
228
+ }
229
+ const hasApi = await waitForTcfApi(opts.cmpWaitMs);
230
+ if (!hasApi) {
231
+ log("Nessuna CMP TCF trovata (continuo a cercarla)");
232
+ if (onUpdate) {
233
+ waitForTcfApi(opts.lateWatchMs).then((found) => {
234
+ if (found) listen(opts, onUpdate);
235
+ });
236
+ }
237
+ return { gdprApplies: void 0, tcString: "", purposes: {}, granted: false, source: "no-cmp" };
238
+ }
239
+ let settled = false;
240
+ const first = new Promise((resolve) => {
241
+ listen(opts, (c) => {
242
+ if (!settled) {
243
+ settled = true;
244
+ resolve(c);
245
+ } else if (onUpdate) onUpdate(c);
246
+ });
247
+ });
248
+ const res = await withTimeout(first, opts.timeoutMs, TIMEOUT);
249
+ settled = true;
250
+ if (res === TIMEOUT) {
251
+ return { gdprApplies: true, tcString: "", purposes: {}, granted: false, source: "timeout" };
252
+ }
253
+ return res;
254
+ }
255
+ function normalize(tc, opts) {
256
+ const purposes = tc.purpose && tc.purpose.consents || {};
257
+ const vendorConsents = tc.vendor && tc.vendor.consents || {};
258
+ let granted = tc.gdprApplies === false || opts.requiredPurposes.every((p) => purposes[p] === true);
259
+ if (granted && tc.gdprApplies !== false && opts.vendorId) {
260
+ granted = vendorConsents[opts.vendorId] === true;
261
+ }
262
+ const out = {
263
+ gdprApplies: tc.gdprApplies,
264
+ tcString: tc.tcString || "",
265
+ purposes,
266
+ vendorConsents,
267
+ granted,
268
+ source: "tcf"
269
+ };
270
+ log("Consenso", out.granted ? "OK" : "limitato", out);
271
+ return out;
272
+ }
273
+
274
+ // src/core/render.js
275
+ var DEFAULT_SANDBOX = "allow-scripts allow-popups allow-popups-to-escape-sandbox";
276
+ var MAX_DIM = 2e3;
277
+ var channelSeq = 0;
278
+ var frames = /* @__PURE__ */ new Map();
279
+ function dim(v) {
280
+ const n = Math.round(Number(v));
281
+ return n > 0 && n <= MAX_DIM ? n : 0;
282
+ }
283
+ function safeUrl(u, allowDataImage) {
284
+ if (typeof u !== "string" || !u) return null;
285
+ try {
286
+ const x = new URL(u, location.href);
287
+ if (x.protocol === "http:" || x.protocol === "https:") return x.href;
288
+ if (allowDataImage && x.protocol === "data:" && /^data:image\//i.test(x.href)) return x.href;
289
+ } catch (e) {
290
+ }
291
+ return null;
292
+ }
293
+ function cleanList(list) {
294
+ return (Array.isArray(list) ? list : []).map((u) => safeUrl(u)).filter(Boolean);
295
+ }
296
+ function normalizeAd(raw) {
297
+ if (!raw || typeof raw !== "object") return { ok: false, reason: "annuncio non valido" };
298
+ const type = raw.type || (raw.url ? "url" : raw.src ? "image" : "html");
299
+ const width = dim(raw.width);
300
+ const height = dim(raw.height);
301
+ if (!width || !height) return { ok: false, reason: `dimensioni non valide (${raw.width}x${raw.height}, max ${MAX_DIM})` };
302
+ const t = raw.trackers || {};
303
+ const ad = {
304
+ type,
305
+ width,
306
+ height,
307
+ alt: raw.alt == null ? void 0 : String(raw.alt),
308
+ clickUrl: safeUrl(raw.clickUrl) || "",
309
+ trackers: { impression: cleanList(t.impression), viewable: cleanList(t.viewable), click: cleanList(t.click) },
310
+ sandbox: typeof raw.sandbox === "string" ? raw.sandbox : void 0
311
+ };
312
+ if (type === "image") {
313
+ ad.src = safeUrl(raw.src, true);
314
+ if (!ad.src) return { ok: false, reason: "src immagine non valido (ammessi http, https, data:image)" };
315
+ } else if (type === "url") {
316
+ ad.url = safeUrl(raw.url);
317
+ if (!ad.url) return { ok: false, reason: "url non valido (ammessi solo http e https)" };
318
+ } else if (type === "html") {
319
+ if (typeof raw.html !== "string" || !raw.html) return { ok: false, reason: "html mancante" };
320
+ ad.html = raw.html;
321
+ } else {
322
+ return { ok: false, reason: "tipo sconosciuto: " + type };
323
+ }
324
+ return { ok: true, ad };
325
+ }
326
+ function tokens(s) {
327
+ return String(s).split(/\s+/).filter(Boolean);
328
+ }
329
+ function sandboxFor(ad) {
330
+ const asked = ad.sandbox && ad.sandbox !== "none" ? ad.sandbox : null;
331
+ if (ad.type === "url") {
332
+ let same = true;
333
+ try {
334
+ same = new URL(ad.url).origin === location.origin;
335
+ } catch (e) {
336
+ }
337
+ if (ad.sandbox === "none" && !same) return null;
338
+ return tokens(asked || DEFAULT_SANDBOX + " allow-same-origin").filter((t) => t !== "allow-top-navigation" && !(same && t === "allow-same-origin")).join(" ");
339
+ }
340
+ return tokens(asked || DEFAULT_SANDBOX).filter((t) => t !== "allow-top-navigation" && t !== "allow-same-origin").join(" ");
341
+ }
342
+ function jsStr(v) {
343
+ return JSON.stringify(String(v)).replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
344
+ }
345
+ function extScript(channel, clickUrl) {
346
+ return `<script>(function(){
347
+ var C=${jsStr(channel)};
348
+ function s(m){m.ual=C;parent.postMessage(m,'*');}
349
+ window.clickTag=${jsStr(clickUrl || "")};
350
+ window.UALExt={
351
+ resize:function(w,h){s({cmd:'resize',w:w,h:h});},
352
+ expand:function(w,h){s({cmd:'resize',w:w,h:h,expanded:true});},
353
+ collapse:function(){s({cmd:'collapse'});},
354
+ close:function(){s({cmd:'close'});},
355
+ click:function(){s({cmd:'click'});if(window.clickTag)window.open(window.clickTag,'_blank');}
356
+ };
357
+ })();</script>`;
358
+ }
359
+ function escapeAttr(s) {
360
+ return String(s).replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
361
+ }
362
+ function buildSrcdoc(ad, channel) {
363
+ const base = '<!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>' + extScript(channel, ad.clickUrl) + "</head><body>";
364
+ let body = "";
365
+ if (ad.type === "image") {
366
+ const img = `<img src="${escapeAttr(ad.src)}" width="${ad.width}" height="${ad.height}" alt="${escapeAttr(ad.alt || "Advertisement")}">`;
367
+ body = ad.clickUrl ? `<a href="${escapeAttr(ad.clickUrl)}" rel="noopener sponsored" onclick="parent.postMessage({ual:'${channel}',cmd:'click'},'*')">${img}</a>` : img;
368
+ } else {
369
+ body = ad.html || "";
370
+ }
371
+ return base + body + "</body></html>";
372
+ }
373
+ function renderInFrame(container, ad, { slotId, onClick, onClose } = {}) {
374
+ ensureListener();
375
+ const channel = "ual" + ++channelSeq;
376
+ const iframe = document.createElement("iframe");
377
+ iframe.title = "Advertisement";
378
+ iframe.setAttribute("scrolling", "no");
379
+ iframe.setAttribute("frameborder", "0");
380
+ iframe.setAttribute("loading", "eager");
381
+ iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin");
382
+ iframe.style.cssText = `border:0;display:block;margin:0 auto;width:${ad.width}px;height:${ad.height}px;max-width:100%`;
383
+ iframe.width = ad.width;
384
+ iframe.height = ad.height;
385
+ const sb = sandboxFor(ad);
386
+ if (sb !== null) iframe.setAttribute("sandbox", sb);
387
+ if (ad.type === "url") iframe.src = ad.url;
388
+ else iframe.srcdoc = buildSrcdoc(ad, channel);
389
+ frames.set(channel, {
390
+ iframe,
391
+ slotId,
392
+ ad,
393
+ original: { w: ad.width, h: ad.height },
394
+ onClick: () => {
395
+ fire(ad.trackers && ad.trackers.click);
396
+ if (onClick) onClick();
397
+ },
398
+ onClose
399
+ });
400
+ container.innerHTML = "";
401
+ container.appendChild(iframe);
402
+ return { iframe, channel, destroy: () => frames.delete(channel) };
403
+ }
404
+ var listening = false;
405
+ function ensureListener() {
406
+ if (listening) return;
407
+ listening = true;
408
+ window.addEventListener("message", onMessage);
409
+ }
410
+ function onMessage(e) {
411
+ const d = e.data;
412
+ if (!d || typeof d !== "object" || !d.ual) return;
413
+ const f = frames.get(d.ual);
414
+ if (!f || e.source !== f.iframe.contentWindow) return;
415
+ switch (d.cmd) {
416
+ case "resize": {
417
+ const w = Math.min(+d.w || f.original.w, MAX_DIM);
418
+ const h = Math.min(+d.h || f.original.h, MAX_DIM);
419
+ f.iframe.style.width = w + "px";
420
+ f.iframe.style.height = h + "px";
421
+ break;
422
+ }
423
+ case "collapse":
424
+ f.iframe.style.width = f.original.w + "px";
425
+ f.iframe.style.height = f.original.h + "px";
426
+ break;
427
+ case "close":
428
+ if (f.onClose) f.onClose();
429
+ break;
430
+ case "click":
431
+ f.onClick();
432
+ break;
433
+ default:
434
+ }
435
+ }
436
+
437
+ // src/core/viewability.js
438
+ function watchViewability(el, { threshold = 0.5, durationMs = 1e3 } = {}, onViewable) {
439
+ if (!("IntersectionObserver" in window)) return () => {
440
+ };
441
+ let timer = null;
442
+ let done = false;
443
+ let inView = false;
444
+ const start = () => {
445
+ if (done || timer || document.visibilityState !== "visible") return;
446
+ timer = setTimeout(() => {
447
+ done = true;
448
+ cleanup();
449
+ onViewable();
450
+ }, durationMs);
451
+ };
452
+ const stop = () => {
453
+ clearTimeout(timer);
454
+ timer = null;
455
+ };
456
+ const io = new IntersectionObserver((entries) => {
457
+ entries.forEach((en) => {
458
+ inView = en.intersectionRatio >= threshold;
459
+ if (inView) start();
460
+ else stop();
461
+ });
462
+ }, { threshold: [0, threshold, 1] });
463
+ const onVis = () => document.visibilityState === "visible" && inView ? start() : stop();
464
+ document.addEventListener("visibilitychange", onVis);
465
+ io.observe(el);
466
+ function cleanup() {
467
+ stop();
468
+ io.disconnect();
469
+ document.removeEventListener("visibilitychange", onVis);
470
+ }
471
+ return cleanup;
472
+ }
473
+
474
+ // src/core/engine.js
475
+ var FINAL_STATUS = /* @__PURE__ */ new Set(["unconfigured", "disabled", "closed"]);
476
+ var LARGE_AD_AREA = 242500;
477
+ function createEngine(adapterRegistry) {
478
+ const bus = createEmitter();
479
+ let cfg = null;
480
+ let rawCfg = null;
481
+ const userTargeting = {};
482
+ let consent = { gdprApplies: void 0, tcString: "", purposes: {}, granted: false, source: "pending" };
483
+ let consentReady = Promise.resolve(consent);
484
+ let ready = false;
485
+ let initPromise = null;
486
+ const slots = /* @__PURE__ */ new Map();
487
+ const providerInit = /* @__PURE__ */ new Map();
488
+ let seq2 = 0;
489
+ let lazyIO = null;
490
+ let mo = null;
491
+ let pending = /* @__PURE__ */ new Set();
492
+ let flushTimer = null;
493
+ function ctx() {
494
+ return {
495
+ site: cfg.site,
496
+ page: { url: location.href, path: location.pathname, ref: document.referrer, title: document.title },
497
+ device: deviceType(),
498
+ viewport: { w: window.innerWidth, h: window.innerHeight },
499
+ targeting: { ...cfg.targeting },
500
+ consent,
501
+ debug: isDebug()
502
+ };
503
+ }
504
+ function resolveCfg() {
505
+ const c = resolveForPage(rawCfg);
506
+ Object.assign(c.targeting, userTargeting);
507
+ return c;
508
+ }
509
+ function setConsent(c) {
510
+ const prev = consent;
511
+ if (prev.granted === c.granted && prev.tcString === c.tcString && prev.source === c.source) return;
512
+ consent = c;
513
+ bus.emit("consent", consent);
514
+ if (!prev.granted && c.granted) {
515
+ for (const s of [...slots.values()]) if (s.consentSkipped) refreshWhenIdle(s);
516
+ }
517
+ }
518
+ function refreshWhenIdle(s, tries = 20) {
519
+ if (slots.get(s.instanceId) !== s) return;
520
+ if (s.status === "loading" || s.status === "queued") {
521
+ if (tries > 0) setTimeout(() => refreshWhenIdle(s, tries - 1), 250);
522
+ return;
523
+ }
524
+ refreshSlot(s);
525
+ }
526
+ function init(source) {
527
+ if (initPromise) {
528
+ warn("init gi\xE0 eseguito");
529
+ return initPromise;
530
+ }
531
+ initPromise = doInit(source).catch((e) => {
532
+ initPromise = null;
533
+ throw e;
534
+ });
535
+ return initPromise;
536
+ }
537
+ async function doInit(source) {
538
+ rawCfg = await loadConfig(source);
539
+ if (rawCfg.debug) setDebug(true);
540
+ validateConfig(rawCfg, adapterRegistry);
541
+ cfg = resolveCfg();
542
+ log("Config", cfg);
543
+ consentReady = getConsent(cfg.consent, setConsent).then((c) => {
544
+ setConsent(c);
545
+ return c;
546
+ });
547
+ if (document.readyState === "loading") {
548
+ await new Promise((r) => document.addEventListener("DOMContentLoaded", r, { once: true }));
549
+ }
550
+ ready = true;
551
+ setupLazy();
552
+ scan(document);
553
+ observeDom();
554
+ bus.emit("ready", { site: cfg.site });
555
+ }
556
+ function scan(root) {
557
+ const list = [];
558
+ if (root.nodeType === 1 && root.matches && root.matches("[data-ad-slot]")) list.push(root);
559
+ if (root.querySelectorAll) root.querySelectorAll("[data-ad-slot]").forEach((el) => list.push(el));
560
+ list.forEach(maybeRegister);
561
+ }
562
+ function maybeRegister(el) {
563
+ const id = el.dataset.ualId;
564
+ if (!id) return register(el);
565
+ if (slots.has(id)) return;
566
+ if (FINAL_STATUS.has(el.dataset.ualStatus)) return;
567
+ resetEl(el);
568
+ register(el);
569
+ }
570
+ function resetEl(el) {
571
+ delete el.dataset.ualId;
572
+ delete el.dataset.ualStatus;
573
+ el.querySelectorAll(":scope > .ual-label, :scope > .ual-creative").forEach((n) => n.remove());
574
+ el.classList.remove("ual-slot", "ual-filled");
575
+ el.style.display = "";
576
+ el.style.minHeight = "";
577
+ }
578
+ function register(el) {
579
+ const name = el.getAttribute("data-ad-slot");
580
+ const slotCfg = cfg.slots[name];
581
+ el.dataset.ualId = name + "#" + ++seq2;
582
+ if (!slotCfg) {
583
+ log("Slot non configurato, ignorato:", name);
584
+ el.dataset.ualStatus = "unconfigured";
585
+ return;
586
+ }
587
+ if (slotCfg.enabled === false) {
588
+ log("Slot disabilitato:", name);
589
+ el.dataset.ualStatus = "disabled";
590
+ collapse(el, slotCfg);
591
+ return;
592
+ }
593
+ let params = {};
594
+ try {
595
+ params = el.dataset.adParams ? JSON.parse(el.dataset.adParams) : {};
596
+ } catch (e) {
597
+ warn("data-ad-params non \xE8 JSON valido", el);
598
+ }
599
+ const slot = {
600
+ instanceId: el.dataset.ualId,
601
+ name,
602
+ el,
603
+ cfg: slotCfg,
604
+ params,
605
+ status: "idle",
606
+ cleanup: [],
607
+ tries: 0,
608
+ consentSkipped: false
609
+ };
610
+ slots.set(slot.instanceId, slot);
611
+ prepare(slot);
612
+ bus.emit("slotRegistered", publicSlot(slot));
613
+ const lazy = slotCfg.lazy !== void 0 ? slotCfg.lazy : cfg.lazy.enabled;
614
+ if (lazy && lazyIO) lazyIO.observe(el);
615
+ else enqueue(slot);
616
+ }
617
+ function prepare(slot) {
618
+ const { el, cfg: c } = slot;
619
+ if (c.minHeight) el.style.minHeight = c.minHeight + "px";
620
+ el.classList.add("ual-slot");
621
+ if (c.label) {
622
+ const l = document.createElement("div");
623
+ l.className = "ual-label";
624
+ l.textContent = c.label;
625
+ l.style.cssText = "font:10px/1.4 sans-serif;color:#888;text-align:center;text-transform:uppercase;letter-spacing:.05em";
626
+ el.appendChild(l);
627
+ }
628
+ const box = document.createElement("div");
629
+ box.className = "ual-creative";
630
+ el.appendChild(box);
631
+ slot.box = box;
632
+ }
633
+ function observeDom() {
634
+ if (!("MutationObserver" in window)) return;
635
+ mo = new MutationObserver((muts) => {
636
+ for (const m of muts) {
637
+ m.removedNodes.forEach((n) => {
638
+ if (n.nodeType !== 1) return;
639
+ for (const s of [...slots.values()]) if (n === s.el || n.contains(s.el)) destroySlot(s);
640
+ });
641
+ m.addedNodes.forEach((n) => {
642
+ if (n.nodeType === 1) scan(n);
643
+ });
644
+ }
645
+ });
646
+ mo.observe(document.body || document.documentElement, { childList: true, subtree: true });
647
+ }
648
+ function setupLazy() {
649
+ if (!("IntersectionObserver" in window)) return;
650
+ lazyIO = new IntersectionObserver((entries) => {
651
+ entries.forEach((en) => {
652
+ if (!en.isIntersecting) return;
653
+ lazyIO.unobserve(en.target);
654
+ const s = slots.get(en.target.dataset.ualId);
655
+ if (s) enqueue(s);
656
+ });
657
+ }, { rootMargin: cfg.lazy.fetchMargin });
658
+ }
659
+ function enqueue(slot) {
660
+ if (slot.status === "loading") return;
661
+ slot.status = "queued";
662
+ pending.add(slot);
663
+ clearTimeout(flushTimer);
664
+ flushTimer = setTimeout(flush, 30);
665
+ }
666
+ function flush() {
667
+ const batch = [...pending];
668
+ pending = /* @__PURE__ */ new Set();
669
+ if (batch.length) runTier(batch, 0);
670
+ }
671
+ async function runTier(batch, tier) {
672
+ const groups = /* @__PURE__ */ new Map();
673
+ const exhausted = [];
674
+ for (const s of batch) {
675
+ const entry = (s.cfg.providers || [])[tier];
676
+ if (!entry) {
677
+ exhausted.push(s);
678
+ continue;
679
+ }
680
+ const pid = typeof entry === "string" ? entry : entry.id;
681
+ if (!groups.has(pid)) groups.set(pid, []);
682
+ groups.get(pid).push(s);
683
+ }
684
+ exhausted.forEach(markEmpty);
685
+ await Promise.all([...groups.entries()].map(async ([pid, group]) => {
686
+ const unfilled = await runProvider(pid, group, tier);
687
+ if (unfilled.length) await runTier(unfilled, tier + 1);
688
+ }));
689
+ }
690
+ async function ensureProvider(pid) {
691
+ const pcfg = cfg.providers[pid];
692
+ if (!pcfg) throw new Error("Provider non definito: " + pid);
693
+ const adapter = adapterRegistry.get(pcfg.adapter);
694
+ if (!adapter) throw new Error("Adapter non registrato: " + pcfg.adapter);
695
+ if (!providerInit.has(pid)) {
696
+ providerInit.set(pid, Promise.resolve(adapter.init ? adapter.init(pcfg, ctx()) : null));
697
+ }
698
+ await providerInit.get(pid);
699
+ return { adapter, pcfg };
700
+ }
701
+ async function runProvider(pid, group, tier) {
702
+ const peek = cfg.providers[pid];
703
+ if (!peek || peek.consent !== "contextual") await consentReady;
704
+ group = group.filter((s) => slots.get(s.instanceId) === s);
705
+ if (!group.length) return [];
706
+ let adapter, pcfg;
707
+ try {
708
+ ({ adapter, pcfg } = await ensureProvider(pid));
709
+ } catch (e) {
710
+ warn(e.message);
711
+ bus.emit("error", { provider: pid, error: e });
712
+ return group;
713
+ }
714
+ if (pcfg.consent === "required" && !consent.granted) {
715
+ log(`Provider ${pid} saltato: consenso non concesso`);
716
+ group.forEach((s) => {
717
+ s.consentSkipped = true;
718
+ });
719
+ return group;
720
+ }
721
+ const c = ctx();
722
+ const req = group.map((s) => {
723
+ s.status = "loading";
724
+ s.provider = pid;
725
+ const entry = s.cfg.providers[tier];
726
+ return {
727
+ id: s.instanceId,
728
+ name: s.name,
729
+ el: s.box,
730
+ sizes: sizesFor(s.cfg),
731
+ params: { ...typeof entry === "object" ? entry.params : {}, ...s.params },
732
+ cfg: s.cfg
733
+ };
734
+ });
735
+ req.forEach((r) => bus.emit("slotRequested", { slot: r.name, id: r.id, provider: pid }));
736
+ log(`Richiesta a ${pid} (${pcfg.adapter})`, req.map((r) => r.name));
737
+ const timeout = pcfg.timeoutMs || cfg.providerTimeoutMs;
738
+ const unfilled = [];
739
+ try {
740
+ if (adapter.fill) {
741
+ const res = await withTimeout(adapter.fill(req, pcfg, c), timeout + 2e3, {});
742
+ group.forEach((s, i) => {
743
+ const r = res[req[i].id];
744
+ if (r && r.filled) onFilled(s, { width: r.width, height: r.height, selfRendered: true, meta: r.meta });
745
+ else {
746
+ callDestroy(s);
747
+ clear(s);
748
+ unfilled.push(s);
749
+ }
750
+ });
751
+ } else {
752
+ const res = await withTimeout(adapter.request(req, pcfg, c), timeout, {});
753
+ group.forEach((s, i) => {
754
+ const raw = res[req[i].id];
755
+ if (!raw || !raw.html && !raw.src && !raw.url) {
756
+ unfilled.push(s);
757
+ return;
758
+ }
759
+ const n = normalizeAd(raw);
760
+ if (!n.ok) {
761
+ warn(`Annuncio scartato (${pid}, slot ${s.name}): ${n.reason}`);
762
+ bus.emit("error", { provider: pid, slot: s.name, error: new Error(n.reason) });
763
+ unfilled.push(s);
764
+ return;
765
+ }
766
+ const ad = n.ad;
767
+ const handle = renderInFrame(s.box, ad, {
768
+ slotId: s.instanceId,
769
+ onClick: () => bus.emit("slotClick", publicSlot(s)),
770
+ onClose: () => {
771
+ destroySlot(s);
772
+ s.el.dataset.ualStatus = "closed";
773
+ collapse(s.el, s.cfg, true);
774
+ }
775
+ });
776
+ s.cleanup.push(handle.destroy);
777
+ fire(ad.trackers.impression);
778
+ onFilled(s, { width: ad.width, height: ad.height, ad });
779
+ });
780
+ }
781
+ } catch (e) {
782
+ warn(`Errore provider ${pid}`, e);
783
+ bus.emit("error", { provider: pid, error: e });
784
+ group.forEach((s) => {
785
+ if (s.status === "loading") {
786
+ callDestroy(s);
787
+ clear(s);
788
+ unfilled.push(s);
789
+ }
790
+ });
791
+ }
792
+ return unfilled;
793
+ }
794
+ function onFilled(s, info) {
795
+ s.status = "filled";
796
+ s.el.dataset.ualStatus = "filled";
797
+ s.el.classList.add("ual-filled");
798
+ s.el.style.display = "";
799
+ bus.emit("slotRendered", { ...publicSlot(s), width: info.width, height: info.height, meta: info.meta });
800
+ const vcfg = { ...cfg.viewability, ...s.cfg.viewability };
801
+ const area = (info.width || 0) * (info.height || 0);
802
+ if (area > LARGE_AD_AREA && !(s.cfg.viewability && s.cfg.viewability.threshold !== void 0)) {
803
+ vcfg.threshold = Math.min(vcfg.threshold, 0.3);
804
+ }
805
+ const target = s.box.firstElementChild || s.box;
806
+ const stop = watchViewability(target, vcfg, () => {
807
+ if (info.ad) fire(info.ad.trackers.viewable);
808
+ bus.emit("slotViewable", publicSlot(s));
809
+ scheduleRefresh(s);
810
+ });
811
+ s.cleanup.push(stop);
812
+ }
813
+ function markEmpty(s) {
814
+ s.status = "empty";
815
+ s.el.dataset.ualStatus = "empty";
816
+ clear(s);
817
+ collapse(s.el, s.cfg);
818
+ bus.emit("slotEmpty", publicSlot(s));
819
+ }
820
+ function collapse(el, c, force) {
821
+ const doCollapse = force || (c.collapseEmpty !== void 0 ? c.collapseEmpty : cfg && cfg.collapseEmpty);
822
+ if (doCollapse) {
823
+ el.style.display = "none";
824
+ el.style.minHeight = "";
825
+ }
826
+ }
827
+ function clear(s) {
828
+ s.cleanup.forEach((fn) => {
829
+ try {
830
+ fn();
831
+ } catch (e) {
832
+ }
833
+ });
834
+ s.cleanup = [];
835
+ if (s.box) s.box.innerHTML = "";
836
+ }
837
+ function callDestroy(s) {
838
+ const pc = s.provider && cfg.providers[s.provider];
839
+ const adapter = pc && adapterRegistry.get(pc.adapter);
840
+ if (adapter && adapter.destroy) {
841
+ try {
842
+ adapter.destroy({ id: s.instanceId, el: s.box }, pc);
843
+ } catch (e) {
844
+ }
845
+ }
846
+ }
847
+ function scheduleRefresh(s) {
848
+ const sec = s.cfg.refreshSec;
849
+ if (!sec || s.cfg.maxRefresh && s.tries >= s.cfg.maxRefresh) return;
850
+ const arm = () => {
851
+ s.refreshTimer = setTimeout(() => {
852
+ if (!slots.has(s.instanceId)) return;
853
+ const r = s.el.getBoundingClientRect();
854
+ const onScreen = r.bottom > 0 && r.top < window.innerHeight;
855
+ if (onScreen && document.visibilityState === "visible") {
856
+ s.tries++;
857
+ refreshSlot(s);
858
+ } else arm();
859
+ }, sec * 1e3);
860
+ };
861
+ arm();
862
+ s.cleanup.push(() => clearTimeout(s.refreshTimer));
863
+ }
864
+ function refreshSlot(s) {
865
+ if (s.status === "loading" || s.status === "queued") return;
866
+ callDestroy(s);
867
+ clear(s);
868
+ s.status = "idle";
869
+ s.consentSkipped = false;
870
+ enqueue(s);
871
+ }
872
+ function destroySlot(s) {
873
+ callDestroy(s);
874
+ clear(s);
875
+ if (lazyIO) lazyIO.unobserve(s.el);
876
+ slots.delete(s.instanceId);
877
+ }
878
+ function publicSlot(s) {
879
+ return { slot: s.name, id: s.instanceId, provider: s.provider, status: s.status, el: s.el };
880
+ }
881
+ return {
882
+ init,
883
+ // true se init è già stato avviato (anche se non ancora completato)
884
+ isInitialized: () => initPromise !== null,
885
+ // promessa dell'init in corso o completato (null se mai avviato): permette di attendere senza richiamare init
886
+ whenReady: () => initPromise,
887
+ on: bus.on.bind(bus),
888
+ off: bus.off.bind(bus),
889
+ registerAdapter: (name, impl) => adapterRegistry.set(name, impl),
890
+ // Targeting key-value: vale anche se chiamato prima dell'init e resta dopo pageView().
891
+ // Un valore null/undefined rimuove la chiave.
892
+ setTargeting(kv) {
893
+ Object.entries(kv || {}).forEach(([k, v]) => {
894
+ if (v == null) {
895
+ delete userTargeting[k];
896
+ if (cfg) delete cfg.targeting[k];
897
+ } else {
898
+ userTargeting[k] = v;
899
+ if (cfg) cfg.targeting[k] = v;
900
+ }
901
+ });
902
+ },
903
+ refresh(names) {
904
+ for (const s of slots.values()) if (!names || names.includes(s.name)) refreshSlot(s);
905
+ },
906
+ // Per SPA: chiamare dopo il cambio di route
907
+ pageView() {
908
+ if (!ready) return;
909
+ for (const s of [...slots.values()]) destroySlot(s);
910
+ document.querySelectorAll("[data-ual-id]").forEach(resetEl);
911
+ cfg = resolveCfg();
912
+ scan(document);
913
+ },
914
+ getSlots: () => [...slots.values()].map(publicSlot),
915
+ getConsent: () => consent,
916
+ debug: setDebug
917
+ };
918
+ }
919
+
920
+ // src/adapters/json.js
921
+ var json_default = {
922
+ name: "json",
923
+ async request(slots, p, ctx) {
924
+ const body = {
925
+ site: ctx.site,
926
+ page: ctx.page,
927
+ device: ctx.device,
928
+ viewport: ctx.viewport,
929
+ targeting: ctx.targeting,
930
+ consent: { gdprApplies: ctx.consent.gdprApplies, tcString: ctx.consent.tcString, granted: ctx.consent.granted },
931
+ slots: slots.map((s) => ({ id: s.id, name: s.name, sizes: s.sizes, params: s.params }))
932
+ };
933
+ const res = await fetch(p.endpoint, {
934
+ method: "POST",
935
+ headers: { "Content-Type": "application/json", ...p.headers },
936
+ body: JSON.stringify(body),
937
+ credentials: p.withCredentials && ctx.consent.granted ? "include" : "omit",
938
+ keepalive: false
939
+ });
940
+ if (res.status === 204) return {};
941
+ if (!res.ok) throw new Error("decide HTTP " + res.status);
942
+ let data = await res.json();
943
+ if (p.map && typeof window[p.map] === "function") data = window[p.map](data, slots);
944
+ const out = {};
945
+ (data.ads || []).forEach((ad) => {
946
+ if (ad && ad.slotId) out[ad.slotId] = ad;
947
+ });
948
+ return out;
949
+ }
950
+ };
951
+
952
+ // src/adapters/iframe.js
953
+ var iframe_default = {
954
+ name: "iframe",
955
+ async request(slots, p, ctx) {
956
+ const out = {};
957
+ slots.forEach((s) => {
958
+ const [w, h] = s.sizes && s.sizes[0] || [s.params.width || 300, s.params.height || 250];
959
+ const url = template(p.url, {
960
+ params: s.params,
961
+ width: w,
962
+ height: h,
963
+ site: ctx.site,
964
+ slot: s.name,
965
+ device: ctx.device,
966
+ cb: cb(),
967
+ page: ctx.page,
968
+ consent: { tcString: ctx.consent.tcString, gdpr: ctx.consent.gdprApplies ? 1 : 0 }
969
+ });
970
+ out[s.id] = { type: "url", url, width: w, height: h, sandbox: p.sandbox || void 0 };
971
+ });
972
+ return out;
973
+ }
974
+ };
975
+
976
+ // src/adapters/revive.js
977
+ function deliveryBase(p) {
978
+ return p.server.replace(/\/$/, "") + (p.deliveryPath || "/www/delivery");
979
+ }
980
+ var revive_default = {
981
+ name: "revive",
982
+ // modalità iframe
983
+ async request(slots, p, ctx) {
984
+ const out = {};
985
+ slots.forEach((s) => {
986
+ if (s.params.zoneid == null) {
987
+ warn(`revive: zoneid mancante per lo slot "${s.name}", slot saltato`);
988
+ return;
989
+ }
990
+ const [w, h] = s.sizes && s.sizes[0] || [300, 250];
991
+ const q = new URLSearchParams({ zoneid: s.params.zoneid, cb: cb() });
992
+ if (ctx.page.ref) q.set("referer", ctx.page.ref);
993
+ Object.entries(s.params.vars || {}).forEach(([k, v]) => q.set(k, v));
994
+ out[s.id] = { type: "url", url: `${deliveryBase(p)}/afr.php?${q}`, width: w, height: h };
995
+ });
996
+ return out;
997
+ }
998
+ };
999
+ var reviveAsync = {
1000
+ name: "revive-async",
1001
+ async fill(slots, p) {
1002
+ const res = {};
1003
+ const src = `${deliveryBase(p)}/asyncjs.php`;
1004
+ const valid = slots.filter((s) => {
1005
+ if (s.params.zoneid != null) return true;
1006
+ warn(`revive-async: zoneid mancante per lo slot "${s.name}", slot saltato`);
1007
+ res[s.id] = { filled: false };
1008
+ return false;
1009
+ });
1010
+ if (!valid.length) return res;
1011
+ const waits = valid.map((s) => {
1012
+ const ins = document.createElement("ins");
1013
+ ins.setAttribute("data-revive-zoneid", s.params.zoneid);
1014
+ ins.setAttribute("data-revive-id", p.reviveId);
1015
+ Object.entries(s.params.vars || {}).forEach(([k, v]) => ins.setAttribute("data-revive-" + k, v));
1016
+ s.el.appendChild(ins);
1017
+ return new Promise((resolve) => {
1018
+ const done = (filled) => {
1019
+ mo.disconnect();
1020
+ clearTimeout(t);
1021
+ const r = ins.getBoundingClientRect();
1022
+ res[s.id] = { filled, width: Math.round(r.width), height: Math.round(r.height) };
1023
+ resolve();
1024
+ };
1025
+ const mo = new MutationObserver(() => {
1026
+ if (ins.querySelector("img,iframe,a,div,embed")) done(true);
1027
+ });
1028
+ mo.observe(ins, { childList: true, subtree: true });
1029
+ const t = setTimeout(() => done(ins.children.length > 0), p.fillTimeoutMs || 2500);
1030
+ });
1031
+ });
1032
+ const ra = window.reviveAsync && window.reviveAsync[p.reviveId];
1033
+ if (ra && typeof ra.refresh === "function") ra.refresh();
1034
+ else await loadScript(src);
1035
+ await Promise.all(waits);
1036
+ return res;
1037
+ }
1038
+ };
1039
+
1040
+ // src/adapters/gpt.js
1041
+ var seq = 0;
1042
+ var gptSlots = /* @__PURE__ */ new Map();
1043
+ function path(tpl, vars) {
1044
+ return tpl.replace(/\{([\w.]+)\}/g, (_, k) => {
1045
+ const v = k.split(".").reduce((o, x) => o == null ? void 0 : o[x], vars);
1046
+ return v == null ? "" : String(v);
1047
+ });
1048
+ }
1049
+ var appliedKeys = /* @__PURE__ */ new Set();
1050
+ function applyTargeting(pa, targeting) {
1051
+ const next = targeting || {};
1052
+ appliedKeys.forEach((k) => {
1053
+ if (!(k in next)) {
1054
+ try {
1055
+ pa.clearTargeting(k);
1056
+ } catch (e) {
1057
+ }
1058
+ appliedKeys.delete(k);
1059
+ }
1060
+ });
1061
+ Object.entries(next).forEach(([k, v]) => {
1062
+ pa.setTargeting(k, v);
1063
+ appliedKeys.add(k);
1064
+ });
1065
+ }
1066
+ var gpt_default = {
1067
+ name: "gpt",
1068
+ async init(p, ctx) {
1069
+ window.googletag = window.googletag || { cmd: [] };
1070
+ const gt = window.googletag;
1071
+ gt.cmd.push(() => {
1072
+ const pa = gt.pubads();
1073
+ if (p.singleRequest !== false) pa.enableSingleRequest();
1074
+ pa.disableInitialLoad();
1075
+ pa.collapseEmptyDivs(false);
1076
+ if (p.nonPersonalizedWithoutConsent && ctx.consent && !ctx.consent.granted) {
1077
+ if (pa.setPrivacySettings) pa.setPrivacySettings({ nonPersonalizedAds: true });
1078
+ }
1079
+ gt.enableServices();
1080
+ });
1081
+ await loadScript(p.src || "https://securepubads.g.doubleclick.net/tag/js/gpt.js", { crossorigin: "anonymous" });
1082
+ },
1083
+ fill(slots, p, ctx) {
1084
+ return new Promise((resolve) => {
1085
+ const gt = window.googletag;
1086
+ const res = {};
1087
+ let left = slots.length;
1088
+ gt.cmd.push(() => {
1089
+ const pa = gt.pubads();
1090
+ applyTargeting(pa, ctx.targeting);
1091
+ const defined = [];
1092
+ const byDiv = {};
1093
+ slots.forEach((s) => {
1094
+ const div = document.createElement("div");
1095
+ div.id = "ual-gpt-" + ++seq;
1096
+ s.el.appendChild(div);
1097
+ const unit = s.params.adUnitPath || path(p.adUnitPath, {
1098
+ network: p.network,
1099
+ site: ctx.site,
1100
+ slot: s.name,
1101
+ section: s.params.section,
1102
+ params: s.params
1103
+ });
1104
+ const sizes = s.sizes.length ? s.sizes : ["fluid"];
1105
+ const slot = gt.defineSlot(unit, sizes, div.id);
1106
+ if (!slot) {
1107
+ res[s.id] = { filled: false };
1108
+ left--;
1109
+ return;
1110
+ }
1111
+ slot.addService(pa);
1112
+ Object.entries(s.params.targeting || {}).forEach(([k, v]) => slot.setTargeting(k, v));
1113
+ gptSlots.set(s.id, slot);
1114
+ byDiv[div.id] = s.id;
1115
+ defined.push(slot);
1116
+ gt.display(div.id);
1117
+ });
1118
+ if (!left) return resolve(res);
1119
+ let guard;
1120
+ const finish = () => {
1121
+ clearTimeout(guard);
1122
+ pa.removeEventListener("slotRenderEnded", onEnd);
1123
+ Object.values(byDiv).forEach((id) => {
1124
+ if (!res[id]) res[id] = { filled: false };
1125
+ });
1126
+ resolve(res);
1127
+ };
1128
+ const onEnd = (e) => {
1129
+ const id = byDiv[e.slot.getSlotElementId()];
1130
+ if (!id || res[id]) return;
1131
+ const [w, h] = Array.isArray(e.size) ? e.size : [0, 0];
1132
+ res[id] = {
1133
+ filled: !e.isEmpty,
1134
+ width: w,
1135
+ height: h,
1136
+ meta: { advertiserId: e.advertiserId, lineItemId: e.lineItemId, creativeId: e.creativeId }
1137
+ };
1138
+ if (--left === 0) finish();
1139
+ };
1140
+ pa.addEventListener("slotRenderEnded", onEnd);
1141
+ guard = setTimeout(finish, p.fillTimeoutMs || 4e3);
1142
+ pa.refresh(defined);
1143
+ });
1144
+ });
1145
+ },
1146
+ destroy(s) {
1147
+ const slot = gptSlots.get(s.id);
1148
+ if (slot && window.googletag && window.googletag.destroySlots) {
1149
+ window.googletag.destroySlots([slot]);
1150
+ gptSlots.delete(s.id);
1151
+ }
1152
+ }
1153
+ };
1154
+
1155
+ // src/adapters/adsense.js
1156
+ var adsense_default = {
1157
+ name: "adsense",
1158
+ async init(p) {
1159
+ await loadScript(
1160
+ `https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=${encodeURIComponent(p.client)}`,
1161
+ { crossorigin: "anonymous" }
1162
+ );
1163
+ },
1164
+ fill(slots, p) {
1165
+ return Promise.all(slots.map((s) => new Promise((resolve) => {
1166
+ const ins = document.createElement("ins");
1167
+ ins.className = "adsbygoogle";
1168
+ ins.setAttribute("data-ad-client", p.client);
1169
+ ins.setAttribute("data-ad-slot", s.params.slot);
1170
+ const [w, h] = s.sizes[0] || [];
1171
+ if (s.params.format || !w) {
1172
+ ins.style.display = "block";
1173
+ ins.setAttribute("data-ad-format", s.params.format || "auto");
1174
+ if (s.params.responsive !== false) ins.setAttribute("data-full-width-responsive", "true");
1175
+ } else {
1176
+ ins.style.cssText = `display:inline-block;width:${w}px;height:${h}px`;
1177
+ }
1178
+ if (p.test) ins.setAttribute("data-adtest", "on");
1179
+ s.el.appendChild(ins);
1180
+ const finish = (filled) => {
1181
+ mo.disconnect();
1182
+ clearTimeout(t);
1183
+ const r = ins.getBoundingClientRect();
1184
+ resolve([s.id, { filled, width: Math.round(r.width), height: Math.round(r.height) }]);
1185
+ };
1186
+ const mo = new MutationObserver(() => {
1187
+ const st = ins.getAttribute("data-ad-status");
1188
+ if (st === "filled") finish(true);
1189
+ else if (st === "unfilled") finish(false);
1190
+ });
1191
+ mo.observe(ins, { attributes: true, attributeFilter: ["data-ad-status"] });
1192
+ const t = setTimeout(() => finish(!!ins.querySelector("iframe")), p.fillTimeoutMs || 4e3);
1193
+ try {
1194
+ (window.adsbygoogle = window.adsbygoogle || []).push({});
1195
+ } catch (e) {
1196
+ finish(false);
1197
+ }
1198
+ }))).then((pairs) => Object.fromEntries(pairs));
1199
+ }
1200
+ };
1201
+
1202
+ // src/adapters/house.js
1203
+ function pick(list) {
1204
+ const total = list.reduce((a, c) => a + (c.weight || 1), 0);
1205
+ let r = Math.random() * total;
1206
+ for (const c of list) {
1207
+ r -= c.weight || 1;
1208
+ if (r <= 0) return c;
1209
+ }
1210
+ return list[list.length - 1];
1211
+ }
1212
+ var house_default = {
1213
+ name: "house",
1214
+ async request(slots, p, ctx) {
1215
+ const now = Date.now();
1216
+ const out = {};
1217
+ slots.forEach((s) => {
1218
+ const ok = (p.creatives || []).filter((c) => {
1219
+ if (c.slots && !c.slots.includes(s.name)) return false;
1220
+ if (c.devices && !c.devices.includes(ctx.device)) return false;
1221
+ if (c.from && now < Date.parse(c.from)) return false;
1222
+ if (c.to && now > Date.parse(c.to) + 864e5) return false;
1223
+ if (s.sizes.length && !s.sizes.some(([w, h]) => w === c.width && h === c.height)) return false;
1224
+ return true;
1225
+ });
1226
+ if (ok.length) {
1227
+ const c = pick(ok);
1228
+ out[s.id] = { ...c, type: c.type || (c.html ? "html" : "image") };
1229
+ }
1230
+ });
1231
+ return out;
1232
+ }
1233
+ };
1234
+
1235
+ // src/lib.js
1236
+ var version = true ? "0.1.0" : "dev";
1237
+ function createUal() {
1238
+ const registry = new Map(Object.entries({
1239
+ json: json_default,
1240
+ iframe: iframe_default,
1241
+ revive: revive_default,
1242
+ "revive-async": reviveAsync,
1243
+ gpt: gpt_default,
1244
+ adsense: adsense_default,
1245
+ house: house_default
1246
+ }));
1247
+ const engine = createEngine(registry);
1248
+ return { ...engine, version };
1249
+ }
1250
+ var shared = null;
1251
+ function getUal() {
1252
+ if (typeof window === "undefined" || typeof document === "undefined") {
1253
+ throw new Error("UAL: getUal() funziona solo nel browser. Chiamalo dopo il montaggio (es. dentro useEffect / onMounted).");
1254
+ }
1255
+ if (!shared) shared = createUal();
1256
+ return shared;
1257
+ }
1258
+ function defineConfig(config) {
1259
+ return config;
1260
+ }
1261
+ export {
1262
+ createUal,
1263
+ defineConfig,
1264
+ getUal,
1265
+ version
1266
+ };