@adhese/sdk 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.
@@ -0,0 +1,1012 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+ import { debounce, uniqueId, random, round } from "lodash-es";
8
+ import { union, coerce, literal, object, string, unknown, lazy } from "zod";
9
+ async function waitForDomLoad() {
10
+ return new Promise((resolve) => {
11
+ function onDomLoad() {
12
+ resolve();
13
+ window.removeEventListener("DOMContentLoaded", onDomLoad);
14
+ }
15
+ if (document.readyState === "loading")
16
+ document.addEventListener("DOMContentLoaded", onDomLoad);
17
+ else
18
+ resolve();
19
+ });
20
+ }
21
+ function createEventManager() {
22
+ const disposables = /* @__PURE__ */ new Set();
23
+ function dispose() {
24
+ for (const disposable of disposables)
25
+ disposable();
26
+ }
27
+ return new Proxy({
28
+ dispose
29
+ }, {
30
+ // eslint-disable-next-line ts/explicit-function-return-type
31
+ get(target, key, receiver) {
32
+ if (!(key in target) && typeof key === "string") {
33
+ const event = createEvent();
34
+ disposables.add(() => {
35
+ event.listeners.clear();
36
+ });
37
+ Reflect.set(target, key, event, receiver);
38
+ }
39
+ return Reflect.get(target, key, receiver);
40
+ }
41
+ });
42
+ }
43
+ function createEvent() {
44
+ const listeners = /* @__PURE__ */ new Set();
45
+ function dispatch(data) {
46
+ for (const listener of listeners)
47
+ void listener(data);
48
+ }
49
+ async function dispatchAsync(data) {
50
+ await Promise.allSettled(
51
+ Array.from(listeners).map((listener) => listener(data))
52
+ );
53
+ }
54
+ function addListener(listener) {
55
+ listeners.add(listener);
56
+ }
57
+ function removeListener(listener) {
58
+ listeners.delete(listener);
59
+ }
60
+ return {
61
+ listeners,
62
+ dispatch,
63
+ dispatchAsync,
64
+ addListener,
65
+ removeListener
66
+ };
67
+ }
68
+ async function findDomSlots(context) {
69
+ await waitForDomLoad();
70
+ return (await Promise.all(Array.from(document.querySelectorAll(".adunit")).filter((element) => Boolean(element.dataset.format)).map((element) => createSlot({
71
+ format: element.dataset.format,
72
+ containingElement: element,
73
+ slot: element.dataset.slot,
74
+ context
75
+ })))).filter((slot) => {
76
+ var _a;
77
+ return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.getName() === slot.getName()));
78
+ });
79
+ }
80
+ async function createSlotManager({
81
+ initialSlots = [],
82
+ context
83
+ }) {
84
+ const slots = /* @__PURE__ */ new Map();
85
+ await Promise.allSettled(initialSlots.map(async (slot) => add({
86
+ ...slot,
87
+ lazyLoading: false
88
+ })));
89
+ function getAll() {
90
+ return Array.from(slots).map(([, slot]) => slot);
91
+ }
92
+ async function add(options) {
93
+ var _a, _b;
94
+ const slot = await createSlot({
95
+ ...options,
96
+ onDispose,
97
+ onNameChange,
98
+ context
99
+ });
100
+ function onDispose() {
101
+ var _a2, _b2;
102
+ slots.delete(slot.getName());
103
+ logger.debug("Slot removed", {
104
+ slot,
105
+ slots: Array.from(slots)
106
+ });
107
+ (_a2 = context.events) == null ? void 0 : _a2.removeSlot.dispatch(slot);
108
+ (_b2 = context.events) == null ? void 0 : _b2.changeSlots.dispatch(Array.from(slots.values()));
109
+ }
110
+ slots.set(slot.getName(), slot);
111
+ function onNameChange(newName, previousName) {
112
+ var _a2;
113
+ slots.set(newName, slot);
114
+ slots.delete(previousName);
115
+ (_a2 = context.events) == null ? void 0 : _a2.changeSlots.dispatch(Array.from(slots.values()));
116
+ }
117
+ logger.debug("Slot added", {
118
+ slot,
119
+ slots: Array.from(slots.values())
120
+ });
121
+ (_a = context.events) == null ? void 0 : _a.addSlot.dispatch(slot);
122
+ (_b = context.events) == null ? void 0 : _b.changeSlots.dispatch(Array.from(slots.values()));
123
+ return slot;
124
+ }
125
+ async function findDomSlots$1() {
126
+ var _a;
127
+ const domSlots = await findDomSlots(
128
+ context
129
+ );
130
+ for (const slot of domSlots) {
131
+ slots.set(slot.getName(), slot);
132
+ (_a = context.events) == null ? void 0 : _a.changeSlots.dispatch(Array.from(slots.values()));
133
+ }
134
+ return domSlots;
135
+ }
136
+ function get(name) {
137
+ return slots.get(name);
138
+ }
139
+ function dispose() {
140
+ var _a;
141
+ for (const slot of slots.values())
142
+ slot.dispose();
143
+ slots.clear();
144
+ (_a = context.events) == null ? void 0 : _a.changeSlots.dispatch(Array.from(slots.values()));
145
+ }
146
+ return {
147
+ getAll,
148
+ add,
149
+ findDomSlots: findDomSlots$1,
150
+ get,
151
+ dispose
152
+ };
153
+ }
154
+ function onTcfConsentChange(callback) {
155
+ var _a;
156
+ (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, callback);
157
+ return () => {
158
+ var _a2;
159
+ return (_a2 = window.__tcfapi) == null ? void 0 : _a2.call(window, "removeEventListener", 2, callback);
160
+ };
161
+ }
162
+ function createQueryDetector({
163
+ onChange,
164
+ queries = {
165
+ mobile: "(max-width: 768px) and (pointer: coarse)",
166
+ tablet: "(min-width: 769px) and (max-width: 1024px) and (pointer: coarse)",
167
+ desktop: "(min-width: 1025px) and (pointer: fine)"
168
+ }
169
+ } = {}) {
170
+ const mediaMap = new Map(
171
+ Object.entries(queries).map(([key, query]) => [key, window.matchMedia(query)])
172
+ );
173
+ function getQuery() {
174
+ for (const [device, query] of Object.entries(queries)) {
175
+ if (window.matchMedia(query).matches)
176
+ return device;
177
+ }
178
+ return "unknown";
179
+ }
180
+ const handleOnChange = debounce(() => {
181
+ void (onChange == null ? void 0 : onChange(getQuery()));
182
+ logger.debug(`Change device ${getQuery()}`);
183
+ }, 50);
184
+ if (onChange) {
185
+ for (const query of mediaMap.values())
186
+ query.addEventListener("change", handleOnChange);
187
+ }
188
+ function dispose() {
189
+ for (const query of mediaMap.values())
190
+ query.removeEventListener("change", handleOnChange);
191
+ }
192
+ return {
193
+ queries: mediaMap,
194
+ getQuery,
195
+ dispose
196
+ };
197
+ }
198
+ const defaultLogLevels = ["trace", "debug", "info", "warn", "error"];
199
+ function createLogger({
200
+ scope,
201
+ logLevels = defaultLogLevels,
202
+ minLogLevelThreshold = logLevels[2]
203
+ }) {
204
+ const logs = /* @__PURE__ */ new Set();
205
+ let currentMinLogLevelThreshold = minLogLevelThreshold;
206
+ const events = createEventManager();
207
+ const logFunctions = Object.fromEntries(logLevels.map((level, index) => {
208
+ const logFunction = (message, attributes) => {
209
+ logs.add({
210
+ scope,
211
+ level,
212
+ message,
213
+ attributes,
214
+ timestamp: Date.now(),
215
+ id: uniqueId()
216
+ });
217
+ events.log.dispatch({
218
+ scope,
219
+ level,
220
+ message,
221
+ attributes,
222
+ timestamp: Date.now(),
223
+ id: uniqueId()
224
+ });
225
+ if (index >= logLevels.indexOf(currentMinLogLevelThreshold)) {
226
+ if (["warn", "error", "trace"].includes(level)) {
227
+ console[level](...[
228
+ `%c${scope}`,
229
+ "color: red; font-weight: bold;",
230
+ message,
231
+ attributes
232
+ ].filter(Boolean));
233
+ } else {
234
+ console.log(...[
235
+ `%c${scope} %c${level.toUpperCase()}`,
236
+ "color: red; font-weight: bold;",
237
+ "font-weight: bold;",
238
+ message,
239
+ attributes
240
+ ].filter(Boolean));
241
+ }
242
+ }
243
+ };
244
+ return [level, logFunction];
245
+ }));
246
+ return {
247
+ ...logFunctions,
248
+ scope,
249
+ events,
250
+ setMinLogLevelThreshold(level) {
251
+ currentMinLogLevelThreshold = level;
252
+ },
253
+ resetMinLogLevelThreshold() {
254
+ currentMinLogLevelThreshold = minLogLevelThreshold;
255
+ },
256
+ getMinLogLevelThreshold() {
257
+ return currentMinLogLevelThreshold;
258
+ },
259
+ getLogs() {
260
+ return Array.from(logs);
261
+ },
262
+ resetLogs() {
263
+ events.reset.dispatch();
264
+ logs.clear();
265
+ }
266
+ };
267
+ }
268
+ const logger = createLogger({
269
+ scope: "Adhese SDK"
270
+ });
271
+ function createParameters(options, queryDetector) {
272
+ const parameters = new MapWithEvents();
273
+ if (options.logReferrer)
274
+ parameters.set("re", btoa(document.referrer));
275
+ if (options.logUrl)
276
+ parameters.set("ur", btoa(window.location.href));
277
+ for (const [key, value] of Object.entries({
278
+ ...options.parameters ?? {},
279
+ tl: options.consent ? "all" : "none",
280
+ dt: queryDetector.getQuery(),
281
+ br: queryDetector.getQuery(),
282
+ rn: random(1e4).toString()
283
+ }))
284
+ parameters.set(key, value);
285
+ return parameters;
286
+ }
287
+ function setupLogging(mergedOptions) {
288
+ if (mergedOptions.debug || window.location.search.includes("adhese_debug=true")) {
289
+ logger.setMinLogLevelThreshold("debug");
290
+ logger.debug("Debug logging enabled");
291
+ }
292
+ logger.debug("Created Adhese SDK instance", {
293
+ mergedOptions
294
+ });
295
+ }
296
+ function isPreviewMode() {
297
+ return window.location.search.includes("adhesePreviewCreativeId");
298
+ }
299
+ class MapWithEvents extends Map {
300
+ constructor() {
301
+ super(...arguments);
302
+ __publicField(this, "listeners", /* @__PURE__ */ new Set());
303
+ }
304
+ addEventListener(listener) {
305
+ this.listeners.add(listener);
306
+ }
307
+ removeEventListener(listener) {
308
+ this.listeners.delete(listener);
309
+ }
310
+ set(key, value) {
311
+ const set = super.set(key, value);
312
+ this.listeners.forEach((listener) => {
313
+ listener();
314
+ });
315
+ return set;
316
+ }
317
+ clear() {
318
+ super.clear();
319
+ this.listeners.forEach((listener) => {
320
+ listener();
321
+ });
322
+ }
323
+ delete(key) {
324
+ const deleted = super.delete(key);
325
+ this.listeners.forEach((listener) => {
326
+ listener();
327
+ });
328
+ return deleted;
329
+ }
330
+ /**
331
+ * Remove all listeners and clear the map.
332
+ */
333
+ dispose() {
334
+ this.listeners.clear();
335
+ super.clear();
336
+ }
337
+ }
338
+ async function createDevtools(context) {
339
+ const devtools = await import("./devtools.js");
340
+ const wrapperElement = document.createElement("div");
341
+ document.body.appendChild(wrapperElement);
342
+ const unmount = devtools.createAdheseDevtools(wrapperElement, context);
343
+ return () => {
344
+ unmount();
345
+ wrapperElement.outerHTML = "";
346
+ };
347
+ }
348
+ async function createAdhese(options) {
349
+ const mergedOptions = {
350
+ host: `https://ads-${options.account}.adhese.com`,
351
+ poolHost: `https://pool-${options.account}.adhese.com`,
352
+ location: "homepage",
353
+ requestType: "POST",
354
+ debug: false,
355
+ initialSlots: [],
356
+ findDomSlotsOnLoad: false,
357
+ consent: false,
358
+ logReferrer: true,
359
+ logUrl: true,
360
+ eagerRendering: false,
361
+ viewabilityTracking: true,
362
+ ...options
363
+ };
364
+ setupLogging(mergedOptions);
365
+ const context = new Proxy({
366
+ location: mergedOptions.location,
367
+ consent: mergedOptions.consent,
368
+ debug: mergedOptions.debug,
369
+ getAll,
370
+ get,
371
+ options: mergedOptions,
372
+ logger
373
+ }, {});
374
+ context.events = createEventManager();
375
+ function getLocation() {
376
+ return context.location;
377
+ }
378
+ function setLocation(newLocation) {
379
+ var _a;
380
+ context.location = newLocation;
381
+ (_a = context.events) == null ? void 0 : _a.locationChange.dispatch(newLocation);
382
+ }
383
+ const queryDetector = createQueryDetector({
384
+ onChange: onQueryChange,
385
+ queries: mergedOptions.queries
386
+ });
387
+ context.parameters = createParameters(mergedOptions, queryDetector);
388
+ context.parameters.addEventListener(onParametersChange);
389
+ let unmountDevtools;
390
+ if (mergedOptions.debug || window.location.search.includes("adhese_debug=true") || isPreviewMode())
391
+ unmountDevtools = await createDevtools(context);
392
+ function onParametersChange() {
393
+ var _a;
394
+ if (context.parameters)
395
+ (_a = context.events) == null ? void 0 : _a.parametersChange.dispatch(context.parameters);
396
+ }
397
+ async function onQueryChange() {
398
+ var _a, _b;
399
+ const query = queryDetector.getQuery();
400
+ (_a = context.parameters) == null ? void 0 : _a.set("dt", query);
401
+ (_b = context.parameters) == null ? void 0 : _b.set("br", query);
402
+ await fetchAndRenderAllSlots();
403
+ }
404
+ function getConsent() {
405
+ return context.consent;
406
+ }
407
+ function setConsent(newConsent) {
408
+ var _a, _b;
409
+ (_a = context.parameters) == null ? void 0 : _a.set("tl", newConsent ? "all" : "none");
410
+ context.consent = newConsent;
411
+ (_b = context.events) == null ? void 0 : _b.consentChange.dispatch(newConsent);
412
+ }
413
+ const slotManager = await createSlotManager({
414
+ initialSlots: mergedOptions.initialSlots,
415
+ context
416
+ });
417
+ function getAll() {
418
+ return slotManager.getAll();
419
+ }
420
+ function get(name) {
421
+ return slotManager.get(name);
422
+ }
423
+ async function addSlot(slotOptions) {
424
+ const slot = await slotManager.add(slotOptions);
425
+ if (!slot.lazyLoading) {
426
+ const ad = await requestAd({
427
+ slot,
428
+ host: mergedOptions.host,
429
+ parameters: context.parameters,
430
+ account: mergedOptions.account,
431
+ context
432
+ });
433
+ await slot.setAd(ad);
434
+ }
435
+ return slot;
436
+ }
437
+ async function findDomSlots2() {
438
+ const domSlots = (await slotManager.findDomSlots()).filter((slot) => !slot.lazyLoading);
439
+ const ads = await requestAds({
440
+ host: mergedOptions.host,
441
+ slots: domSlots,
442
+ method: mergedOptions.requestType,
443
+ account: mergedOptions.account,
444
+ parameters: context.parameters,
445
+ context
446
+ });
447
+ await Promise.allSettled(ads.map((ad) => {
448
+ var _a;
449
+ return (_a = slotManager.get(ad.slotName)) == null ? void 0 : _a.setAd(ad);
450
+ }));
451
+ return domSlots;
452
+ }
453
+ async function toggleDebug() {
454
+ context.debug = !context.debug;
455
+ if (context.debug && !unmountDevtools) {
456
+ unmountDevtools = await createDevtools(context);
457
+ logger.setMinLogLevelThreshold("debug");
458
+ logger.debug("Debug mode enabled");
459
+ } else {
460
+ logger.debug("Debug mode disabled");
461
+ unmountDevtools == null ? void 0 : unmountDevtools();
462
+ unmountDevtools = void 0;
463
+ logger.setMinLogLevelThreshold("info");
464
+ }
465
+ return context.debug;
466
+ }
467
+ async function fetchAndRenderAllSlots() {
468
+ const slots = slotManager.getAll().filter((slot) => !slot.lazyLoading);
469
+ if (slots.length === 0)
470
+ return;
471
+ const ads = await requestAds({
472
+ host: mergedOptions.host,
473
+ slots,
474
+ method: mergedOptions.requestType,
475
+ account: mergedOptions.account,
476
+ parameters: context.parameters,
477
+ context
478
+ });
479
+ await Promise.allSettled(ads.map((ad) => {
480
+ var _a;
481
+ return (_a = slotManager.get(ad.slotName)) == null ? void 0 : _a.setAd(ad);
482
+ }));
483
+ }
484
+ const disposeOnTcfConsentChange = onTcfConsentChange(async (data) => {
485
+ var _a, _b;
486
+ if (!data.tcString)
487
+ return;
488
+ logger.debug("TCF v2 consent data received", {
489
+ data
490
+ });
491
+ (_a = context.parameters) == null ? void 0 : _a.set("xt", data.tcString);
492
+ (_b = context.parameters) == null ? void 0 : _b.delete("tl");
493
+ await fetchAndRenderAllSlots();
494
+ });
495
+ if (slotManager.getAll().length > 0)
496
+ await fetchAndRenderAllSlots().catch(logger.error);
497
+ function dispose() {
498
+ var _a, _b, _c;
499
+ queryDetector.dispose();
500
+ slotManager.dispose();
501
+ queryDetector.dispose();
502
+ disposeOnTcfConsentChange();
503
+ (_a = context.parameters) == null ? void 0 : _a.dispose();
504
+ (_b = context.parameters) == null ? void 0 : _b.clear();
505
+ logger.resetLogs();
506
+ (_c = context.events) == null ? void 0 : _c.dispose();
507
+ unmountDevtools == null ? void 0 : unmountDevtools();
508
+ logger.info("Adhese instance disposed");
509
+ }
510
+ if (mergedOptions.findDomSlotsOnLoad)
511
+ await slotManager.findDomSlots();
512
+ return {
513
+ ...mergedOptions,
514
+ ...slotManager,
515
+ parameters: context.parameters,
516
+ events: context.events,
517
+ getLocation,
518
+ setLocation,
519
+ getConsent,
520
+ setConsent,
521
+ addSlot,
522
+ findDomSlots: findDomSlots2,
523
+ dispose,
524
+ toggleDebug
525
+ };
526
+ }
527
+ function addTrackingPixel(url) {
528
+ const img = document.createElement("img");
529
+ img.src = url.toString();
530
+ img.style.height = "1px";
531
+ img.style.width = "1px";
532
+ img.style.margin = "-1px";
533
+ img.style.border = "0";
534
+ img.style.position = "absolute";
535
+ img.style.top = "0";
536
+ return document.body.appendChild(img);
537
+ }
538
+ function renderIframe(ad, element) {
539
+ const iframe = document.createElement("iframe");
540
+ iframe.srcdoc = `
541
+ <!DOCTYPE html>
542
+ <html>
543
+ <head>
544
+ <style>
545
+ body {
546
+ margin: 0;
547
+ padding: 0;
548
+ overflow: hidden;
549
+ }
550
+ </style>
551
+ </head>
552
+ <body>
553
+ ${ad.tag}
554
+ </body>
555
+ `.replaceAll(/\s+/g, " ").trim();
556
+ iframe.style.border = "none";
557
+ iframe.style.width = ad.width ? `${ad.width}px` : "100%";
558
+ iframe.style.height = ad.height ? `${ad.height}px` : "100%";
559
+ element.replaceChildren(iframe);
560
+ }
561
+ function renderInline(ad, element) {
562
+ element.style.width = ad.width ? `${ad.width}px` : "100%";
563
+ element.style.height = ad.height ? `${ad.height}px` : "100%";
564
+ element.innerHTML = ad.tag;
565
+ }
566
+ const renderFunctions = {
567
+ iframe: renderIframe,
568
+ inline: renderInline
569
+ };
570
+ async function createSlot(options) {
571
+ var _a;
572
+ const {
573
+ containingElement,
574
+ slot,
575
+ context,
576
+ renderMode = "iframe"
577
+ } = options;
578
+ await waitForDomLoad();
579
+ const parameters = new Map(Object.entries(options.parameters ?? {}));
580
+ let format;
581
+ let queryDetector = null;
582
+ if (typeof options.format === "string") {
583
+ format = options.format;
584
+ } else {
585
+ queryDetector = createQueryDetector({
586
+ onChange: setFormat,
587
+ queries: Object.fromEntries(options.format.map((item) => [item.format, item.query]))
588
+ });
589
+ format = queryDetector.getQuery();
590
+ }
591
+ async function setFormat(newFormat) {
592
+ var _a2;
593
+ const oldName = getName();
594
+ format = newFormat;
595
+ (_a2 = options.onNameChange) == null ? void 0 : _a2.call(options, getName(), oldName);
596
+ const newAd = await requestAd({
597
+ slot: {
598
+ getName,
599
+ parameters
600
+ },
601
+ account: context.options.account,
602
+ host: context.options.host,
603
+ parameters: context.parameters,
604
+ context
605
+ });
606
+ cleanElement();
607
+ await setAd(newAd);
608
+ }
609
+ function getFormat() {
610
+ return format;
611
+ }
612
+ let element = typeof containingElement === "string" || !containingElement ? document.querySelector(`.adunit[data-format="${format}"]#${containingElement}${slot ? `[data-slot="${slot}"]` : ""}`) : containingElement;
613
+ function getElement() {
614
+ if (renderMode === "iframe")
615
+ return (element == null ? void 0 : element.querySelector("iframe")) ?? null;
616
+ return (element == null ? void 0 : element.innerHTML) ? element.firstElementChild : null;
617
+ }
618
+ let impressionTrackingPixelElement = null;
619
+ let viewabilityTrackingPixelElement = null;
620
+ let isInViewport = false;
621
+ let ad = null;
622
+ function getAd() {
623
+ return ad;
624
+ }
625
+ async function setAd(newAd) {
626
+ var _a2, _b;
627
+ ad = newAd;
628
+ if (isInViewport || context.options.eagerRendering)
629
+ await render(ad);
630
+ if (element) {
631
+ element.style.width = `${ad.width}px`;
632
+ element.style.height = `${ad.height}px`;
633
+ }
634
+ await ((_b = context.events) == null ? void 0 : _b.changeSlots.dispatchAsync(Array.from(((_a2 = context.getAll) == null ? void 0 : _a2.call(context)) ?? [])));
635
+ }
636
+ const renderIntersectionObserver = new IntersectionObserver((entries) => {
637
+ isInViewport = entries.some((entry) => entry.isIntersecting);
638
+ if (isInViewport) {
639
+ (async () => {
640
+ if (!ad && options.lazyLoading)
641
+ await render();
642
+ else if (ad)
643
+ await render(ad);
644
+ })().catch(logger.error);
645
+ }
646
+ }, {
647
+ rootMargin: ((_a = options.lazyLoadingOptions) == null ? void 0 : _a.rootMargin) ?? "200px",
648
+ threshold: 0
649
+ });
650
+ let timeoutId = null;
651
+ const {
652
+ threshold,
653
+ duration,
654
+ rootMargin
655
+ } = {
656
+ threshold: 0.2,
657
+ duration: 1e3,
658
+ rootMargin: "0px",
659
+ ...context.options.viewabilityTrackingOptions
660
+ };
661
+ const viewabilityObserver = new IntersectionObserver(([entry]) => {
662
+ if (context.options.viewabilityTracking && !viewabilityTrackingPixelElement && ad) {
663
+ const ratio = round(entry.intersectionRatio, 1);
664
+ if (ratio >= threshold && !timeoutId) {
665
+ timeoutId = setTimeout(() => {
666
+ var _a2, _b;
667
+ timeoutId = null;
668
+ if (ad == null ? void 0 : ad.viewableImpressionCounter) {
669
+ viewabilityTrackingPixelElement = addTrackingPixel(ad.viewableImpressionCounter);
670
+ logger.debug(`Viewability tracking pixel fired for ${getName()}`);
671
+ (_b = context.events) == null ? void 0 : _b.changeSlots.dispatch(Array.from(((_a2 = context.getAll) == null ? void 0 : _a2.call(context)) ?? []));
672
+ }
673
+ }, duration);
674
+ } else if (ratio < threshold && timeoutId) {
675
+ clearTimeout(timeoutId);
676
+ timeoutId = null;
677
+ }
678
+ }
679
+ }, {
680
+ rootMargin,
681
+ threshold: Array.from({ length: 11 }, (_, i) => i * 0.1)
682
+ });
683
+ if (element && context.options.viewabilityTracking)
684
+ viewabilityObserver.observe(element);
685
+ if (element)
686
+ renderIntersectionObserver.observe(element);
687
+ async function render(adToRender) {
688
+ var _a2, _b;
689
+ await waitForDomLoad();
690
+ ad = adToRender ?? ad ?? await requestAd({
691
+ slot: {
692
+ getName,
693
+ parameters
694
+ },
695
+ account: context.options.account,
696
+ host: context.options.host,
697
+ parameters: context.parameters,
698
+ context
699
+ });
700
+ if (!element) {
701
+ const error = `Could not create slot for format ${format}. No element found.`;
702
+ logger.error(error, options);
703
+ throw new Error(error);
704
+ }
705
+ if (context.debug)
706
+ element.style.position = "relative";
707
+ renderFunctions[renderMode](ad, element);
708
+ if ((ad == null ? void 0 : ad.impressionCounter) && !impressionTrackingPixelElement) {
709
+ impressionTrackingPixelElement = addTrackingPixel(ad.impressionCounter);
710
+ logger.debug(`Impression tracking pixel fired for ${getName()}`);
711
+ }
712
+ logger.debug("Slot rendered", {
713
+ renderedElement: element,
714
+ location: context.location,
715
+ format,
716
+ containingElement
717
+ });
718
+ renderIntersectionObserver.disconnect();
719
+ await ((_b = context.events) == null ? void 0 : _b.changeSlots.dispatchAsync(Array.from(((_a2 = context.getAll) == null ? void 0 : _a2.call(context)) ?? [])));
720
+ return element;
721
+ }
722
+ function cleanElement() {
723
+ if (!element)
724
+ return;
725
+ element.innerHTML = "";
726
+ element.style.position = "";
727
+ element.style.width = "";
728
+ element.style.height = "";
729
+ }
730
+ function getName() {
731
+ return `${context.location}${slot ? `${slot}` : ""}-${format}`;
732
+ }
733
+ function dispose() {
734
+ var _a2;
735
+ cleanElement();
736
+ impressionTrackingPixelElement == null ? void 0 : impressionTrackingPixelElement.remove();
737
+ viewabilityTrackingPixelElement == null ? void 0 : viewabilityTrackingPixelElement.remove();
738
+ element = null;
739
+ ad = null;
740
+ renderIntersectionObserver.disconnect();
741
+ viewabilityObserver.disconnect();
742
+ (_a2 = options.onDispose) == null ? void 0 : _a2.call(options);
743
+ queryDetector == null ? void 0 : queryDetector.dispose();
744
+ }
745
+ function isViewabilityTracked() {
746
+ return Boolean(viewabilityTrackingPixelElement);
747
+ }
748
+ function isImpressionTracked() {
749
+ return Boolean(impressionTrackingPixelElement);
750
+ }
751
+ return {
752
+ location: context.location,
753
+ lazyLoading: options.lazyLoading ?? false,
754
+ slot,
755
+ parameters,
756
+ setFormat,
757
+ getFormat,
758
+ render,
759
+ getElement,
760
+ getName,
761
+ getAd,
762
+ setAd,
763
+ isViewabilityTracked,
764
+ isImpressionTracked,
765
+ dispose
766
+ };
767
+ }
768
+ const numberLike = union([coerce.string().regex(/^\d+$/), literal("")]).transform((value) => value === "" ? void 0 : Number(value));
769
+ const booleanLike = union([coerce.boolean(), literal("")]);
770
+ const urlLike = union([coerce.string(), literal("")]).transform((value) => {
771
+ try {
772
+ return new URL(value);
773
+ } catch {
774
+ return void 0;
775
+ }
776
+ });
777
+ const dateLike = union([coerce.string(), literal("")]).transform((value) => {
778
+ if (value === "")
779
+ return void 0;
780
+ const date = new Date(numberLike.safeParse(value).success ? Number(value) : value);
781
+ if (Number.isNaN(date.getTime()))
782
+ return void 0;
783
+ return date;
784
+ });
785
+ const baseAdResponseScheme = object({
786
+ adDuration: numberLike.optional(),
787
+ adDuration2nd: numberLike.optional(),
788
+ adDuration3rd: numberLike.optional(),
789
+ adDuration4th: numberLike.optional(),
790
+ adDuration5th: numberLike.optional(),
791
+ adDuration6th: numberLike.optional(),
792
+ adFormat: string().optional(),
793
+ adType: string(),
794
+ additionalCreativeTracker: urlLike.optional(),
795
+ additionalViewableTracker: string().optional(),
796
+ adspaceEnd: dateLike.optional(),
797
+ adspaceId: string().optional(),
798
+ adspaceKey: string().optional(),
799
+ adspaceStart: dateLike.optional(),
800
+ advertiserId: string().optional(),
801
+ altText: string().optional(),
802
+ auctionable: booleanLike.optional(),
803
+ body: string().optional(),
804
+ clickTag: urlLike.optional(),
805
+ comment: string().optional(),
806
+ creativeName: string().optional(),
807
+ deliveryGroupId: string().optional(),
808
+ deliveryMultiples: string().optional(),
809
+ dm: string().optional(),
810
+ ext: string().optional(),
811
+ extension: object({
812
+ mediaType: string(),
813
+ prebid: unknown().optional()
814
+ }).optional(),
815
+ extraField1: string().optional(),
816
+ extraField2: string().optional(),
817
+ height: numberLike.optional(),
818
+ height3rd: numberLike.optional(),
819
+ height4th: numberLike.optional(),
820
+ height5th: numberLike.optional(),
821
+ height6th: numberLike.optional(),
822
+ heightLarge: numberLike.optional(),
823
+ id: string().optional(),
824
+ impressionCounter: urlLike.optional(),
825
+ libId: string().optional(),
826
+ orderId: string().optional(),
827
+ orderName: string().optional(),
828
+ orderProperty: string().optional(),
829
+ origin: string().optional(),
830
+ originData: unknown().optional(),
831
+ poolPath: urlLike.optional(),
832
+ preview: booleanLike.optional(),
833
+ priority: numberLike.optional(),
834
+ share: string().optional(),
835
+ // eslint-disable-next-line ts/naming-convention
836
+ slotID: string(),
837
+ slotName: string(),
838
+ swfSrc: urlLike.optional(),
839
+ swfSrc2nd: string().optional(),
840
+ swfSrc3rd: string().optional(),
841
+ swfSrc4th: string().optional(),
842
+ swfSrc5th: string().optional(),
843
+ swfSrc6th: string().optional(),
844
+ tag: string(),
845
+ tagUrl: urlLike.optional(),
846
+ timeStamp: dateLike.optional(),
847
+ trackedImpressionCounter: urlLike.optional(),
848
+ tracker: urlLike.optional(),
849
+ trackingUrl: urlLike.optional(),
850
+ url: urlLike.optional(),
851
+ viewableImpressionCounter: urlLike.optional(),
852
+ width: numberLike.optional(),
853
+ width3rd: numberLike.optional(),
854
+ width4th: numberLike.optional(),
855
+ width5th: numberLike.optional(),
856
+ width6th: numberLike.optional(),
857
+ widthLarge: numberLike.optional()
858
+ });
859
+ const adResponseSchema = baseAdResponseScheme.extend({
860
+ additionalCreatives: lazy(() => union([adResponseSchema.array(), string()]).optional())
861
+ });
862
+ const adSchema = adResponseSchema.transform(({
863
+ additionalCreatives,
864
+ ...data
865
+ }) => {
866
+ const filteredValue = Object.fromEntries(
867
+ Object.entries(data).filter(([, value]) => Boolean(value) && JSON.stringify(value) !== "{}" && JSON.stringify(value) !== "[]")
868
+ );
869
+ return {
870
+ ...filteredValue,
871
+ additionalCreatives: Array.isArray(additionalCreatives) ? additionalCreatives.map((creative) => adSchema.parse(creative)) : additionalCreatives
872
+ };
873
+ });
874
+ async function requestPreviews(account) {
875
+ const previewObjects = getPreviewObjects();
876
+ const list = (await Promise.allSettled(previewObjects.filter((previewObject) => "adhesePreviewCreativeId" in previewObject).map(async (previewObject) => {
877
+ const endpoint = new URL(`https://${account}-preview.adhese.org/creatives/preview/json/tag.do`);
878
+ endpoint.searchParams.set(
879
+ "id",
880
+ previewObject.adhesePreviewCreativeId
881
+ );
882
+ const response = await fetch(endpoint.href, {
883
+ method: "GET",
884
+ headers: {
885
+ accept: "application/json"
886
+ }
887
+ });
888
+ if (!response.ok)
889
+ return Promise.reject(new Error(`Failed to request preview ad with ID: ${previewObject.adhesePreviewCreativeId}`));
890
+ return await response.json();
891
+ }))).filter((response) => {
892
+ if (response.status === "rejected") {
893
+ logger.error(response.reason);
894
+ return false;
895
+ }
896
+ return response.status === "fulfilled";
897
+ }).map((response) => response.value.map((item) => ({
898
+ ...item,
899
+ preview: true
900
+ })));
901
+ return adSchema.array().parse(list.flat());
902
+ }
903
+ function getPreviewObjects() {
904
+ const currentUrl = new URL(window.location.href);
905
+ const previewObjects = [];
906
+ let currentObject = {};
907
+ for (const [key, value] of currentUrl.searchParams.entries()) {
908
+ if (key === "adhesePreviewCreativeId" && Object.keys(currentObject).length > 0) {
909
+ previewObjects.push(currentObject);
910
+ currentObject = {};
911
+ }
912
+ currentObject[key] = value;
913
+ }
914
+ if (Object.keys(currentObject).length > 0)
915
+ previewObjects.push(currentObject);
916
+ return previewObjects;
917
+ }
918
+ function requestWithPost({
919
+ host,
920
+ ...options
921
+ }) {
922
+ const payload = {
923
+ ...options,
924
+ slots: options.slots.map((slot) => ({
925
+ slotname: slot.getName(),
926
+ parameters: parseParameters(slot.parameters)
927
+ })),
928
+ parameters: options.parameters && parseParameters(options.parameters)
929
+ };
930
+ return fetch(`${new URL(host).href}json`, {
931
+ method: "POST",
932
+ body: JSON.stringify(payload),
933
+ headers: {
934
+ // eslint-disable-next-line ts/naming-convention
935
+ "Content-Type": "application/json"
936
+ }
937
+ });
938
+ }
939
+ async function requestWithGet(options) {
940
+ return fetch(new URL(`${options.host}/json/sl${options.slots.map((slot) => slot.getName()).join("/sl")}`), {
941
+ method: "GET",
942
+ headers: {
943
+ // eslint-disable-next-line ts/naming-convention
944
+ "Content-Type": "application/json"
945
+ }
946
+ });
947
+ }
948
+ function parseParameters(parameters) {
949
+ return Object.fromEntries(Array.from(parameters.entries()).filter(([key]) => {
950
+ if (key.length === 2)
951
+ return true;
952
+ logger.warn(`Invalid parameter key: ${key}. Key should be exactly 2 characters long. Key will be ignored.`);
953
+ return false;
954
+ }));
955
+ }
956
+ async function requestAds({
957
+ method = "POST",
958
+ context,
959
+ ...options
960
+ }) {
961
+ var _a, _b, _c, _d;
962
+ try {
963
+ (_a = context.events) == null ? void 0 : _a.requestAd.dispatch({
964
+ ...options,
965
+ context,
966
+ method
967
+ });
968
+ const [response, previews] = await Promise.all([(method == null ? void 0 : method.toUpperCase()) === "POST" ? requestWithPost(options) : requestWithGet(options), requestPreviews(options.account)]);
969
+ logger.debug("Received response", response);
970
+ if (!response.ok)
971
+ throw new Error(`Failed to request ad: ${response.status} ${response.statusText}`);
972
+ const result = adSchema.array().parse(await response.json());
973
+ logger.debug("Parsed ad", result);
974
+ if (previews.length > 0)
975
+ logger.info(`Found ${previews.length} ${previews.length === 1 ? "preview" : "previews"}. Replacing ads in response with preview items`, previews);
976
+ const matchedPreviews = previews.map(({ slotName, ...preview }) => {
977
+ const partnerAd = result.find((ad) => ad.libId === preview.libId);
978
+ return {
979
+ slotName: `${(partnerAd == null ? void 0 : partnerAd.slotName) ?? slotName}`,
980
+ ...preview
981
+ };
982
+ });
983
+ if (matchedPreviews.length > 0)
984
+ (_b = context.events) == null ? void 0 : _b.previewReceived.dispatch(matchedPreviews);
985
+ const mergedResult = [
986
+ ...result.filter((ad) => !previews.some((preview) => preview.libId === ad.libId)),
987
+ ...matchedPreviews
988
+ ];
989
+ if (mergedResult.length === 0)
990
+ throw new Error("No ads found");
991
+ (_c = context.events) == null ? void 0 : _c.responseReceived.dispatch(mergedResult);
992
+ return mergedResult;
993
+ } catch (error) {
994
+ logger.error(String(error));
995
+ (_d = context.events) == null ? void 0 : _d.requestError.dispatch(error);
996
+ throw error;
997
+ }
998
+ }
999
+ async function requestAd({
1000
+ slot,
1001
+ ...options
1002
+ }) {
1003
+ const [ad] = await requestAds({
1004
+ slots: [slot],
1005
+ ...options
1006
+ });
1007
+ return ad;
1008
+ }
1009
+ export {
1010
+ createAdhese
1011
+ };
1012
+ //# sourceMappingURL=index.js.map