@adhese/sdk 0.7.0 → 0.8.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.cjs CHANGED
@@ -152,109 +152,16 @@ function renderInline(ad, element) {
152
152
  function generateName(location, format, slot) {
153
153
  return `${location}${slot ? `${slot}` : ""}-${format}`;
154
154
  }
155
- async function findDomSlots(context) {
156
- await waitForDomLoad();
157
- return Array.from(document.querySelectorAll(".adunit")).filter((element) => {
158
- var _a;
159
- if (!element.dataset.format)
160
- return false;
161
- const name = generateName(
162
- context.location,
163
- element.dataset.format,
164
- element.dataset.slot
165
- );
166
- return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === name));
167
- }).map((element) => createSlot({
168
- format: element.dataset.format,
169
- containingElement: element,
170
- slot: element.dataset.slot,
171
- context
172
- })).filter((slot) => {
173
- var _a;
174
- return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === slot.name.value));
175
- });
176
- }
177
- function createSlotManager({
178
- initialSlots = [],
179
- context
180
- }) {
181
- const scope = runtimeCore.effectScope();
182
- return scope.run(() => {
183
- const slots = runtimeCore.shallowReactive(/* @__PURE__ */ new Map());
184
- runtimeCore.watchEffect(() => {
185
- var _a;
186
- (_a = context.events) == null ? void 0 : _a.changeSlots.dispatch(Array.from(slots.values()));
187
- });
188
- function getAll() {
189
- return Array.from(slots).map(([, slot]) => slot);
190
- }
191
- function add(options) {
192
- var _a;
193
- const slot = createSlot({
194
- ...options,
195
- onDispose: onDispose2,
196
- context
197
- });
198
- function onDispose2() {
199
- var _a2;
200
- slots.delete(slot.name.value);
201
- logger.debug("Slot removed", {
202
- slot,
203
- slots: Array.from(slots)
204
- });
205
- (_a2 = context.events) == null ? void 0 : _a2.removeSlot.dispatch(slot);
206
- }
207
- slots.set(slot.name.value, slot);
208
- runtimeCore.watch(slot.name, (newName, previousName) => {
209
- slots.set(newName, slot);
210
- slots.delete(previousName);
211
- });
212
- logger.debug("Slot added", {
213
- slot,
214
- slots: Array.from(slots.values())
215
- });
216
- (_a = context.events) == null ? void 0 : _a.addSlot.dispatch(slot);
217
- return slot;
218
- }
219
- async function findDomSlots$1() {
220
- const domSlots = await findDomSlots(
221
- context
222
- );
223
- for (const slot of domSlots)
224
- slots.set(slot.name.value, slot);
225
- return domSlots;
226
- }
227
- function get(name) {
228
- return slots.get(name);
229
- }
230
- function dispose() {
231
- for (const slot of slots.values())
232
- slot.dispose();
233
- slots.clear();
234
- scope.stop();
235
- }
236
- for (const options of initialSlots) {
237
- add({
238
- ...options,
239
- lazyLoading: false
240
- });
241
- }
242
- return {
243
- getAll,
244
- add,
245
- findDomSlots: findDomSlots$1,
246
- get,
247
- dispose
248
- };
249
- });
250
- }
251
- function onTcfConsentChange(callback) {
252
- var _a;
253
- (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, callback);
254
- return () => {
255
- var _a2;
256
- return (_a2 = window.__tcfapi) == null ? void 0 : _a2.call(window, "removeEventListener", 2, callback);
257
- };
155
+ function addTrackingPixel(url) {
156
+ const img = document.createElement("img");
157
+ img.src = url.toString();
158
+ img.style.height = "1px";
159
+ img.style.width = "1px";
160
+ img.style.margin = "-1px";
161
+ img.style.border = "0";
162
+ img.style.position = "absolute";
163
+ img.style.top = "0";
164
+ return document.body.appendChild(img);
258
165
  }
259
166
  function createQueryDetector({
260
167
  onChange,
@@ -367,54 +274,35 @@ function createLogger({
367
274
  const logger = createLogger({
368
275
  scope: "Adhese SDK"
369
276
  });
370
- function createParameters(options, queryDetector) {
371
- const parameters = /* @__PURE__ */ new Map();
372
- if (options.logReferrer)
373
- parameters.set("re", btoa(document.referrer));
374
- if (options.logUrl)
375
- parameters.set("ur", btoa(window.location.href));
376
- for (const [key, value] of Object.entries({
377
- ...options.parameters ?? {},
378
- tl: options.consent ? "all" : "none",
379
- dt: queryDetector.getQuery(),
380
- br: queryDetector.getQuery(),
381
- rn: Math.round(Math.random() * 1e4).toString()
382
- }))
383
- parameters.set(key, value);
384
- return parameters;
277
+ const hookMap = /* @__PURE__ */ new Map();
278
+ function clearAllHooks() {
279
+ hookMap.clear();
385
280
  }
386
- function setupLogging(mergedOptions) {
387
- if (mergedOptions.debug || window.location.search.includes("adhese_debug=true")) {
388
- logger.setMinLogLevelThreshold("debug");
389
- logger.debug("Debug logging enabled");
390
- }
391
- logger.debug("Created Adhese SDK instance", {
392
- mergedOptions
393
- });
394
- }
395
- function isPreviewMode() {
396
- return window.location.search.includes("adhesePreviewCreativeId");
397
- }
398
- function createHook({
281
+ function createHook(name, {
399
282
  onRun,
400
- onAdd,
401
- onDispose: onDispose2
402
- }) {
403
- const callbacks = /* @__PURE__ */ new Set();
404
- function run() {
405
- for (const callback of callbacks)
406
- callback();
407
- onRun == null ? void 0 : onRun(callbacks);
408
- }
283
+ onAdd
284
+ } = {}) {
285
+ hookMap.set(name, /* @__PURE__ */ new Set());
286
+ const run = async (arg) => {
287
+ let latestResult = arg;
288
+ for (const callback of hookMap.get(name) ?? [])
289
+ latestResult = await callback(latestResult) ?? latestResult;
290
+ onRun == null ? void 0 : onRun(hookMap.get(name));
291
+ return latestResult;
292
+ };
409
293
  function add(callback) {
410
- callbacks.add(callback);
411
- onAdd == null ? void 0 : onAdd(callbacks);
412
- }
413
- function dispose() {
414
- onDispose2 == null ? void 0 : onDispose2(callbacks);
415
- callbacks.clear();
294
+ const hookSet = hookMap.get(name);
295
+ if (hookSet)
296
+ hookSet.add(callback);
297
+ else
298
+ hookMap.set(name, /* @__PURE__ */ new Set([callback]));
299
+ onAdd == null ? void 0 : onAdd(hookSet);
300
+ return () => {
301
+ var _a;
302
+ (_a = hookMap.get(name)) == null ? void 0 : _a.delete(callback);
303
+ };
416
304
  }
417
- return [run, add, dispose];
305
+ return [run, add];
418
306
  }
419
307
  let resolveOnInitPromise = () => {
420
308
  };
@@ -422,246 +310,313 @@ let isInit = false;
422
310
  const waitOnInit = new Promise((resolve) => {
423
311
  resolveOnInitPromise = resolve;
424
312
  });
425
- const [runOnInit, onInit, disposeOnInit] = createHook({
313
+ const [runOnInit, onInit] = createHook("onInit", {
426
314
  onRun(callbacks) {
427
315
  isInit = true;
428
316
  resolveOnInitPromise();
429
317
  logger.debug("Initialization completed");
430
- callbacks.clear();
318
+ callbacks == null ? void 0 : callbacks.clear();
431
319
  },
432
320
  onAdd() {
433
321
  if (isInit)
434
- runOnInit();
322
+ runOnInit().catch(logger.error);
435
323
  }
436
324
  });
437
- let resolveOnDisposePromise = () => {
438
- };
439
- let isDisposed = false;
440
- new Promise((resolve) => {
441
- resolveOnDisposePromise = resolve;
442
- });
443
- const [runOnDispose, onDispose, disposeOnDispose] = createHook({
444
- onRun(callbacks) {
445
- isDisposed = true;
446
- resolveOnDisposePromise();
447
- logger.debug("Disposal completed");
448
- callbacks.clear();
449
- },
450
- onAdd() {
451
- if (isDisposed)
452
- runOnDispose();
325
+ const [runOnRequest, onRequest] = createHook("onRequest");
326
+ const [runOnResponse, onResponse] = createHook("onResponse");
327
+ const numberLike = zod.union([zod.coerce.string().regex(/^\d+$/), zod.literal("")]).transform((value) => value === "" ? void 0 : Number(value));
328
+ const booleanLike = zod.union([zod.coerce.boolean(), zod.literal("")]);
329
+ const urlLike = zod.union([zod.coerce.string(), zod.literal("")]).transform((value) => {
330
+ try {
331
+ return new URL(value);
332
+ } catch {
333
+ return void 0;
453
334
  }
454
335
  });
455
- function createAdhese(options) {
456
- const scope = runtimeCore.effectScope();
457
- return scope.run(() => {
458
- const mergedOptions = {
459
- host: `https://ads-${options.account}.adhese.com`,
460
- poolHost: `https://pool-${options.account}.adhese.com`,
461
- location: "homepage",
462
- requestType: "POST",
463
- debug: false,
464
- initialSlots: [],
465
- findDomSlotsOnLoad: false,
466
- consent: false,
467
- logReferrer: true,
468
- logUrl: true,
469
- safeFrame: false,
470
- eagerRendering: false,
471
- viewabilityTracking: true,
472
- plugins: [],
473
- ...options
474
- };
475
- setupLogging(mergedOptions);
476
- const context = runtimeCore.reactive({
477
- location: mergedOptions.location,
478
- consent: mergedOptions.consent,
479
- debug: mergedOptions.debug,
480
- getAll,
481
- get,
482
- options: mergedOptions,
483
- logger
336
+ const dateLike = zod.union([zod.coerce.string(), zod.literal("")]).transform((value) => {
337
+ if (value === "")
338
+ return void 0;
339
+ const date = new Date(numberLike.safeParse(value).success ? Number(value) : value);
340
+ if (Number.isNaN(date.getTime()))
341
+ return void 0;
342
+ return date;
343
+ });
344
+ const cssValueLike = zod.union([zod.coerce.string(), zod.literal(""), zod.number()]).transform((value) => {
345
+ if (value === "" || value === 0 || value === "0")
346
+ return void 0;
347
+ if (numberLike.parse(value))
348
+ return `${numberLike.parse(value)}px`;
349
+ return String(value);
350
+ });
351
+ const isJson = zod.string().transform((value, { addIssue }) => {
352
+ try {
353
+ return JSON.parse(value.replaceAll("'", '"'));
354
+ } catch (error) {
355
+ addIssue({
356
+ code: zod.ZodIssueCode.custom,
357
+ message: `Invalid JSON: ${error.message}`
484
358
  });
485
- context.events = createEventManager();
486
- context.safeFrame = options.safeFrame ? createSafeFrame({
487
- renderFile: `${mergedOptions.poolHost}/sf/r.html`,
488
- context
489
- }) : void 0;
490
- function getLocation() {
491
- return context.location;
492
- }
493
- function setLocation(newLocation) {
494
- var _a;
495
- context.location = newLocation;
496
- (_a = context.events) == null ? void 0 : _a.locationChange.dispatch(newLocation);
497
- }
498
- const queryDetector = createQueryDetector({
499
- onChange: onQueryChange,
500
- queries: mergedOptions.queries
359
+ return zod.NEVER;
360
+ }
361
+ });
362
+ const isHtmlString = zod.string().transform((value, { addIssue }) => {
363
+ var _a;
364
+ const htmlParser = new DOMParser();
365
+ try {
366
+ const html = htmlParser.parseFromString(value, "text/html");
367
+ if (((_a = html.body) == null ? void 0 : _a.children.length) === 0)
368
+ throw new Error("Invalid HTML");
369
+ return value;
370
+ } catch (error) {
371
+ addIssue({
372
+ code: zod.ZodIssueCode.custom,
373
+ message: error.message
501
374
  });
502
- context.parameters = createParameters(mergedOptions, queryDetector);
503
- runtimeCore.watch(
504
- context.parameters,
505
- onParametersChange,
506
- {
507
- deep: true,
508
- immediate: true
509
- }
510
- );
511
- function onParametersChange() {
512
- var _a;
513
- if (context.parameters)
514
- (_a = context.events) == null ? void 0 : _a.parametersChange.dispatch(context.parameters);
515
- }
516
- async function onQueryChange() {
517
- var _a, _b;
518
- const query = queryDetector.getQuery();
519
- (_a = context.parameters) == null ? void 0 : _a.set("dt", query);
520
- (_b = context.parameters) == null ? void 0 : _b.set("br", query);
521
- await fetchAndRenderAllSlots();
522
- }
523
- function getConsent() {
524
- return context.consent;
525
- }
526
- function setConsent(newConsent) {
527
- var _a, _b;
528
- (_a = context.parameters) == null ? void 0 : _a.set("tl", newConsent ? "all" : "none");
529
- context.consent = newConsent;
530
- (_b = context.events) == null ? void 0 : _b.consentChange.dispatch(newConsent);
531
- }
532
- const slotManager = createSlotManager({
533
- initialSlots: mergedOptions.initialSlots,
534
- context
535
- });
536
- function getAll() {
537
- return slotManager.getAll() ?? [];
538
- }
539
- function get(name) {
540
- return slotManager.get(name);
541
- }
542
- async function addSlot(slotOptions) {
543
- if (!slotManager)
544
- throw new Error("Slot manager not initialized");
545
- const slot = slotManager.add(slotOptions);
546
- if (!slot.lazyLoading) {
547
- slot.ad.value = await requestAd({
548
- slot,
549
- context
550
- });
375
+ return zod.NEVER;
376
+ }
377
+ });
378
+ const isJsonOrHtmlString = zod.union([isJson, isHtmlString]);
379
+ const isJsonOrHtmlOptionalString = zod.union([zod.coerce.string(), isJsonOrHtmlString]).transform((value) => {
380
+ if (value === "")
381
+ return void 0;
382
+ return value;
383
+ }).optional();
384
+ const baseSchema = zod.object({
385
+ adDuration: numberLike.optional(),
386
+ adFormat: zod.string().optional(),
387
+ adType: zod.string(),
388
+ additionalCreativeTracker: urlLike.optional(),
389
+ additionalViewableTracker: zod.string().optional(),
390
+ adspaceEnd: dateLike.optional(),
391
+ adspaceId: zod.string().optional(),
392
+ adspaceKey: zod.string().optional(),
393
+ adspaceStart: dateLike.optional(),
394
+ advertiserId: zod.string().optional(),
395
+ altText: zod.string().optional(),
396
+ auctionable: booleanLike.optional(),
397
+ body: isJsonOrHtmlOptionalString,
398
+ clickTag: urlLike.optional(),
399
+ comment: zod.string().optional(),
400
+ creativeName: zod.string().optional(),
401
+ deliveryGroupId: zod.string().optional(),
402
+ deliveryMultiples: zod.string().optional(),
403
+ ext: zod.string().optional(),
404
+ extension: zod.object({
405
+ mediaType: zod.string(),
406
+ prebid: zod.unknown().optional()
407
+ }).optional(),
408
+ height: numberLike.optional(),
409
+ id: zod.string(),
410
+ impressionCounter: urlLike.optional(),
411
+ libId: zod.string().optional(),
412
+ orderId: zod.string().optional(),
413
+ orderName: zod.string().optional(),
414
+ orderProperty: zod.string().optional(),
415
+ origin: zod.union([zod.literal("JERLICIA"), zod.literal("DALE")]),
416
+ originData: zod.unknown().optional(),
417
+ originInstance: zod.string().optional(),
418
+ poolPath: urlLike.optional(),
419
+ preview: booleanLike.optional(),
420
+ priority: numberLike.optional(),
421
+ sfSrc: urlLike.optional(),
422
+ share: zod.string().optional(),
423
+ // eslint-disable-next-line ts/naming-convention
424
+ slotID: zod.string(),
425
+ slotName: zod.string(),
426
+ swfSrc: urlLike.optional(),
427
+ tag: isJsonOrHtmlOptionalString,
428
+ tagUrl: urlLike.optional(),
429
+ timeStamp: dateLike.optional(),
430
+ trackedImpressionCounter: urlLike.optional(),
431
+ tracker: urlLike.optional(),
432
+ trackingUrl: urlLike.optional(),
433
+ url: urlLike.optional(),
434
+ viewableImpressionCounter: urlLike.optional(),
435
+ width: numberLike.optional(),
436
+ widthLarge: cssValueLike.optional()
437
+ });
438
+ const jerliciaSchema = zod.object({
439
+ origin: zod.literal("JERLICIA"),
440
+ tag: isJsonOrHtmlString
441
+ }).passthrough();
442
+ const daleSchema = zod.object({
443
+ origin: zod.literal("DALE"),
444
+ body: isJsonOrHtmlString
445
+ }).passthrough().transform(({ body, ...data }) => ({
446
+ ...data,
447
+ tag: body
448
+ }));
449
+ const adResponseSchema = baseSchema.extend({
450
+ additionalCreatives: zod.lazy(() => zod.union([adResponseSchema.array(), zod.string()]).optional())
451
+ });
452
+ const adSchema = adResponseSchema.transform(({
453
+ additionalCreatives,
454
+ ...data
455
+ }) => {
456
+ const filteredValue = Object.fromEntries(
457
+ Object.entries(data).filter(([, value]) => Boolean(value) && JSON.stringify(value) !== "{}" && JSON.stringify(value) !== "[]")
458
+ );
459
+ return {
460
+ ...filteredValue,
461
+ additionalCreatives: Array.isArray(additionalCreatives) ? additionalCreatives.map((creative) => adSchema.parse(creative)) : additionalCreatives
462
+ };
463
+ });
464
+ function parseResponse(response) {
465
+ const schemaMap = {
466
+ /* eslint-disable ts/naming-convention */
467
+ JERLICIA: jerliciaSchema,
468
+ DALE: daleSchema
469
+ /* eslint-enable ts/naming-convention */
470
+ };
471
+ const preParsed = adResponseSchema.array().parse(response);
472
+ return preParsed.map((item) => {
473
+ const schema = schemaMap[item.origin];
474
+ if (!schema)
475
+ return adSchema.parse(item);
476
+ return schema.parse(item);
477
+ });
478
+ }
479
+ async function requestPreviews(account) {
480
+ const previewObjects = getPreviewObjects();
481
+ const list = (await Promise.allSettled(previewObjects.filter((previewObject) => "adhesePreviewCreativeId" in previewObject).map(async (previewObject) => {
482
+ const endpoint = new URL(`https://${account}-preview.adhese.org/creatives/preview/json/tag.do`);
483
+ endpoint.searchParams.set(
484
+ "id",
485
+ previewObject.adhesePreviewCreativeId
486
+ );
487
+ const response = await fetch(endpoint.href, {
488
+ method: "GET",
489
+ headers: {
490
+ accept: "application/json"
551
491
  }
552
- return slot;
492
+ });
493
+ if (!response.ok)
494
+ return Promise.reject(new Error(`Failed to request preview ad with ID: ${previewObject.adhesePreviewCreativeId}`));
495
+ return await response.json();
496
+ }))).filter((response) => {
497
+ if (response.status === "rejected") {
498
+ logger.error(response.reason);
499
+ return false;
553
500
  }
554
- async function findDomSlots2() {
555
- const domSlots = (await slotManager.findDomSlots() ?? []).filter((slot) => !slot.lazyLoading);
556
- if (domSlots.length <= 0)
557
- return [];
558
- const ads = await requestAds({
559
- slots: domSlots,
560
- context
561
- });
562
- for (const ad of ads) {
563
- const slot = slotManager.get(ad.slotName);
564
- if (slot)
565
- slot.ad.value = ad;
566
- }
567
- return domSlots;
501
+ return response.status === "fulfilled";
502
+ }).map((response) => response.value.map((item) => ({
503
+ ...item,
504
+ preview: true
505
+ })));
506
+ return adSchema.array().parse(list.flat());
507
+ }
508
+ function getPreviewObjects() {
509
+ const currentUrl = new URL(window.location.href);
510
+ const previewObjects = [];
511
+ let currentObject = {};
512
+ for (const [key, value] of currentUrl.searchParams.entries()) {
513
+ if (key === "adhesePreviewCreativeId" && Object.keys(currentObject).length > 0) {
514
+ previewObjects.push(currentObject);
515
+ currentObject = {};
568
516
  }
569
- async function toggleDebug() {
570
- var _a, _b;
571
- context.debug = !context.debug;
572
- if (context.debug) {
573
- logger.setMinLogLevelThreshold("debug");
574
- logger.debug("Debug mode enabled");
575
- (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
576
- } else {
577
- logger.debug("Debug mode disabled");
578
- logger.setMinLogLevelThreshold("info");
579
- (_b = context.events) == null ? void 0 : _b.debugChange.dispatch(false);
580
- }
581
- return context.debug;
517
+ currentObject[key] = value;
518
+ }
519
+ if (Object.keys(currentObject).length > 0)
520
+ previewObjects.push(currentObject);
521
+ return previewObjects;
522
+ }
523
+ function requestWithPost({
524
+ context: { options: { host }, parameters },
525
+ ...options
526
+ }) {
527
+ const payload = {
528
+ ...options,
529
+ slots: options.slots.map((slot) => ({
530
+ slotname: runtimeCore.toValue(slot.name),
531
+ parameters: parseParameters(slot.parameters)
532
+ })),
533
+ parameters: parameters && parseParameters(parameters)
534
+ };
535
+ return fetch(`${new URL(host).href}json`, {
536
+ method: "POST",
537
+ body: JSON.stringify(payload),
538
+ headers: {
539
+ // eslint-disable-next-line ts/naming-convention
540
+ "Content-Type": "application/json"
582
541
  }
583
- async function fetchAndRenderAllSlots() {
584
- const slots = (slotManager.getAll() ?? []).filter((slot) => !slot.lazyLoading);
585
- if (slots.length === 0)
586
- return;
587
- const ads = await requestAds({
588
- slots,
589
- context
590
- });
591
- for (const ad of ads) {
592
- const slot = slotManager.get(ad.slotName);
593
- if (slot)
594
- slot.ad.value = ad;
595
- }
542
+ });
543
+ }
544
+ async function requestWithGet({ context, slots }) {
545
+ return fetch(new URL(`${context.options.host}/json/sl${slots.map((slot) => runtimeCore.toValue(slot.name)).join("/sl")}`), {
546
+ method: "GET",
547
+ headers: {
548
+ // eslint-disable-next-line ts/naming-convention
549
+ "Content-Type": "application/json"
596
550
  }
597
- const disposeOnTcfConsentChange = onTcfConsentChange(async (data) => {
598
- var _a, _b;
599
- if (!data.tcString)
600
- return;
601
- logger.debug("TCF v2 consent data received", {
602
- data
603
- });
604
- (_a = context.parameters) == null ? void 0 : _a.set("xt", data.tcString);
605
- (_b = context.parameters) == null ? void 0 : _b.delete("tl");
606
- await fetchAndRenderAllSlots();
551
+ });
552
+ }
553
+ function parseParameters(parameters) {
554
+ return Object.fromEntries(Array.from(parameters.entries()).filter(([key]) => {
555
+ if (key.length === 2)
556
+ return true;
557
+ logger.warn(`Invalid parameter key: ${key}. Key should be exactly 2 characters long. Key will be ignored.`);
558
+ return false;
559
+ }).map(([key, value]) => {
560
+ if (typeof value === "string")
561
+ return [key, filterSpecialChars(value)];
562
+ return [key, value.map(filterSpecialChars)];
563
+ }));
564
+ }
565
+ function filterSpecialChars(value) {
566
+ const specialRegex = /[^\p{L}\p{N}_]/gu;
567
+ return value.replaceAll(specialRegex, "_");
568
+ }
569
+ async function requestAds(requestOptions) {
570
+ var _a, _b, _c, _d, _e;
571
+ const options = await runOnRequest(requestOptions);
572
+ const { context } = options;
573
+ try {
574
+ (_a = context.events) == null ? void 0 : _a.requestAd.dispatch({
575
+ ...options,
576
+ context
607
577
  });
608
- function dispose() {
609
- var _a, _b;
610
- queryDetector.dispose();
611
- slotManager.dispose();
612
- queryDetector.dispose();
613
- disposeOnTcfConsentChange();
614
- (_a = context.parameters) == null ? void 0 : _a.clear();
615
- logger.resetLogs();
616
- (_b = context.events) == null ? void 0 : _b.dispose();
617
- logger.info("Adhese instance disposed");
618
- runOnDispose();
619
- disposeOnInit();
620
- disposeOnDispose();
621
- scope.stop();
622
- }
623
- for (const plugin of mergedOptions.plugins)
624
- plugin(context);
625
- onInit(async () => {
626
- var _a;
627
- if ((slotManager.getAll().length ?? 0) > 0)
628
- await fetchAndRenderAllSlots().catch(logger.error);
629
- if (mergedOptions.findDomSlotsOnLoad)
630
- await findDomSlots2();
631
- if (mergedOptions.debug || window.location.search.includes("adhese_debug=true") || isPreviewMode())
632
- (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
633
- if (!scope.active)
634
- dispose();
578
+ const [response, previews] = await Promise.all([((_b = context.options.requestType) == null ? void 0 : _b.toUpperCase()) === "POST" ? requestWithPost(options) : requestWithGet(options), requestPreviews(context.options.account)]);
579
+ logger.debug("Received response", response);
580
+ if (!response.ok)
581
+ throw new Error(`Failed to request ad: ${response.status} ${response.statusText}`);
582
+ const result = parseResponse(await response.json());
583
+ logger.debug("Parsed ad", result);
584
+ if (previews.length > 0)
585
+ logger.info(`Found ${previews.length} ${previews.length === 1 ? "preview" : "previews"}. Replacing ads in response with preview items`, previews);
586
+ const matchedPreviews = previews.map(({ slotName, ...preview }) => {
587
+ const partnerAd = result.find((ad) => ad.libId === preview.libId);
588
+ return {
589
+ slotName: `${(partnerAd == null ? void 0 : partnerAd.slotName) ?? slotName}`,
590
+ ...preview
591
+ };
635
592
  });
636
- runOnInit();
637
- return {
638
- ...mergedOptions,
639
- ...slotManager,
640
- parameters: context.parameters,
641
- events: context.events,
642
- getLocation,
643
- setLocation,
644
- getConsent,
645
- setConsent,
646
- addSlot,
647
- findDomSlots: findDomSlots2,
648
- dispose,
649
- toggleDebug,
650
- context
651
- };
652
- });
593
+ if (matchedPreviews.length > 0)
594
+ (_c = context.events) == null ? void 0 : _c.previewReceived.dispatch(matchedPreviews);
595
+ const mergedResult = await runOnResponse([
596
+ ...result.filter((ad) => !previews.some((preview) => preview.libId === ad.libId)),
597
+ ...matchedPreviews
598
+ ]);
599
+ if (mergedResult.length === 0)
600
+ throw new Error("No ads found");
601
+ (_d = context.events) == null ? void 0 : _d.responseReceived.dispatch(mergedResult);
602
+ return mergedResult;
603
+ } catch (error) {
604
+ logger.error(String(error));
605
+ (_e = context.events) == null ? void 0 : _e.requestError.dispatch(error);
606
+ throw error;
607
+ }
653
608
  }
654
- function addTrackingPixel(url) {
655
- const img = document.createElement("img");
656
- img.src = url.toString();
657
- img.style.height = "1px";
658
- img.style.width = "1px";
659
- img.style.margin = "-1px";
660
- img.style.border = "0";
661
- img.style.position = "absolute";
662
- img.style.top = "0";
663
- return document.body.appendChild(img);
609
+ async function requestAd({
610
+ slot,
611
+ ...options
612
+ }) {
613
+ const [ad] = await requestAds({
614
+ slots: [slot],
615
+ ...options
616
+ });
617
+ return ad;
664
618
  }
619
+ const [runOnRender, onRender] = createHook("onRender");
665
620
  function useViewabilityObserver({ context, ad, name, element }) {
666
621
  let timeoutId = null;
667
622
  const {
@@ -842,10 +797,10 @@ function createSlot(options) {
842
797
  var _a, _b;
843
798
  await waitForDomLoad();
844
799
  await waitOnInit;
845
- const renderAd = adToRender ?? ad.value ?? await requestAd$1();
846
- if (originalAd.value) {
847
- ad.value = ((_a = options.onBeforeRender) == null ? void 0 : _a.call(options, adToRender ?? originalAd.value)) ?? renderAd;
848
- }
800
+ let renderAd = adToRender ?? ad.value ?? originalAd.value ?? await requestAd$1();
801
+ if (renderAd)
802
+ renderAd = ((_a = options.onBeforeRender) == null ? void 0 : _a.call(options, renderAd)) ?? renderAd;
803
+ renderAd = await runOnRender(renderAd);
849
804
  if (!element.value) {
850
805
  const error = `Could not create slot for format ${format.value}. No element found.`;
851
806
  logger.error(error, options);
@@ -853,8 +808,8 @@ function createSlot(options) {
853
808
  }
854
809
  if (context.debug)
855
810
  element.value.style.position = "relative";
856
- if (context.safeFrame && ad.value && renderMode === "iframe") {
857
- const position = context.safeFrame.addPosition(ad.value, element.value);
811
+ if (context.safeFrame && renderAd && renderMode === "iframe") {
812
+ const position = context.safeFrame.addPosition(renderAd, element.value);
858
813
  await context.safeFrame.render(position);
859
814
  } else {
860
815
  renderFunctions[renderMode](renderAd, element.value);
@@ -893,321 +848,376 @@ function createSlot(options) {
893
848
  scope.stop();
894
849
  }
895
850
  return {
896
- location: context.location,
897
- lazyLoading: options.lazyLoading ?? false,
898
- slot,
899
- parameters,
900
- format,
901
- name,
902
- ad,
903
- isViewabilityTracked,
904
- isImpressionTracked,
905
- render,
906
- getElement,
851
+ location: context.location,
852
+ lazyLoading: options.lazyLoading ?? false,
853
+ slot,
854
+ parameters,
855
+ format,
856
+ name,
857
+ ad,
858
+ isViewabilityTracked,
859
+ isImpressionTracked,
860
+ render,
861
+ getElement,
862
+ dispose
863
+ };
864
+ });
865
+ }
866
+ function useDomLoaded() {
867
+ const isDomLoaded = runtimeCore.ref(false);
868
+ onInit(async () => {
869
+ await waitForDomLoad();
870
+ isDomLoaded.value = true;
871
+ });
872
+ return isDomLoaded;
873
+ }
874
+ async function findDomSlots(context) {
875
+ await waitForDomLoad();
876
+ return Array.from(document.querySelectorAll(".adunit")).filter((element) => {
877
+ var _a;
878
+ if (!element.dataset.format)
879
+ return false;
880
+ const name = generateName(
881
+ context.location,
882
+ element.dataset.format,
883
+ element.dataset.slot
884
+ );
885
+ return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === name));
886
+ }).map((element) => createSlot({
887
+ format: element.dataset.format,
888
+ containingElement: element,
889
+ slot: element.dataset.slot,
890
+ context
891
+ })).filter((slot) => {
892
+ var _a;
893
+ return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === slot.name.value));
894
+ });
895
+ }
896
+ function createSlotManager({
897
+ initialSlots = [],
898
+ context
899
+ }) {
900
+ const scope = runtimeCore.effectScope();
901
+ return scope.run(() => {
902
+ const slots = runtimeCore.shallowReactive(/* @__PURE__ */ new Map());
903
+ runtimeCore.watchEffect(() => {
904
+ var _a;
905
+ (_a = context.events) == null ? void 0 : _a.changeSlots.dispatch(Array.from(slots.values()));
906
+ });
907
+ function getAll() {
908
+ return Array.from(slots).map(([, slot]) => slot);
909
+ }
910
+ function add(options) {
911
+ var _a;
912
+ const slot = createSlot({
913
+ ...options,
914
+ onDispose: onDispose2,
915
+ context
916
+ });
917
+ function onDispose2() {
918
+ var _a2;
919
+ slots.delete(slot.name.value);
920
+ logger.debug("Slot removed", {
921
+ slot,
922
+ slots: Array.from(slots)
923
+ });
924
+ (_a2 = context.events) == null ? void 0 : _a2.removeSlot.dispatch(slot);
925
+ }
926
+ slots.set(slot.name.value, slot);
927
+ runtimeCore.watch(slot.name, (newName, previousName) => {
928
+ slots.set(newName, slot);
929
+ slots.delete(previousName);
930
+ });
931
+ logger.debug("Slot added", {
932
+ slot,
933
+ slots: Array.from(slots.values())
934
+ });
935
+ (_a = context.events) == null ? void 0 : _a.addSlot.dispatch(slot);
936
+ return slot;
937
+ }
938
+ async function findDomSlots$1() {
939
+ const domSlots = await findDomSlots(
940
+ context
941
+ );
942
+ for (const slot of domSlots)
943
+ slots.set(slot.name.value, slot);
944
+ return domSlots;
945
+ }
946
+ function get(name) {
947
+ return slots.get(name);
948
+ }
949
+ function dispose() {
950
+ for (const slot of slots.values())
951
+ slot.dispose();
952
+ slots.clear();
953
+ scope.stop();
954
+ }
955
+ for (const options of initialSlots) {
956
+ add({
957
+ ...options,
958
+ lazyLoading: false
959
+ });
960
+ }
961
+ return {
962
+ getAll,
963
+ add,
964
+ findDomSlots: findDomSlots$1,
965
+ get,
907
966
  dispose
908
967
  };
909
968
  });
910
969
  }
911
- function useDomLoaded() {
912
- const isDomLoaded = runtimeCore.ref(false);
913
- onInit(async () => {
914
- await waitForDomLoad();
915
- isDomLoaded.value = true;
916
- });
917
- return isDomLoaded;
970
+ function onTcfConsentChange(callback) {
971
+ var _a;
972
+ (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, callback);
973
+ return () => {
974
+ var _a2;
975
+ return (_a2 = window.__tcfapi) == null ? void 0 : _a2.call(window, "removeEventListener", 2, callback);
976
+ };
918
977
  }
919
- const numberLike = zod.union([zod.coerce.string().regex(/^\d+$/), zod.literal("")]).transform((value) => value === "" ? void 0 : Number(value));
920
- const booleanLike = zod.union([zod.coerce.boolean(), zod.literal("")]);
921
- const urlLike = zod.union([zod.coerce.string(), zod.literal("")]).transform((value) => {
922
- try {
923
- return new URL(value);
924
- } catch {
925
- return void 0;
978
+ function createParameters(options, queryDetector) {
979
+ const parameters = /* @__PURE__ */ new Map();
980
+ if (options.logReferrer)
981
+ parameters.set("re", btoa(document.referrer));
982
+ if (options.logUrl)
983
+ parameters.set("ur", btoa(window.location.href));
984
+ for (const [key, value] of Object.entries({
985
+ ...options.parameters ?? {},
986
+ tl: options.consent ? "all" : "none",
987
+ dt: queryDetector.getQuery(),
988
+ br: queryDetector.getQuery(),
989
+ rn: Math.round(Math.random() * 1e4).toString()
990
+ }))
991
+ parameters.set(key, value);
992
+ return parameters;
993
+ }
994
+ function setupLogging(mergedOptions) {
995
+ if (mergedOptions.debug || window.location.search.includes("adhese_debug=true")) {
996
+ logger.setMinLogLevelThreshold("debug");
997
+ logger.debug("Debug logging enabled");
926
998
  }
927
- });
928
- const dateLike = zod.union([zod.coerce.string(), zod.literal("")]).transform((value) => {
929
- if (value === "")
930
- return void 0;
931
- const date = new Date(numberLike.safeParse(value).success ? Number(value) : value);
932
- if (Number.isNaN(date.getTime()))
933
- return void 0;
934
- return date;
935
- });
936
- const cssValueLike = zod.union([zod.coerce.string(), zod.literal(""), zod.number()]).transform((value) => {
937
- if (value === "" || value === 0 || value === "0")
938
- return void 0;
939
- if (numberLike.parse(value))
940
- return `${numberLike.parse(value)}px`;
941
- return String(value);
942
- });
943
- const isJson = zod.string().transform((value, { addIssue }) => {
944
- try {
945
- return JSON.parse(value.replaceAll("'", '"'));
946
- } catch (error) {
947
- addIssue({
948
- code: zod.ZodIssueCode.custom,
949
- message: `Invalid JSON: ${error.message}`
950
- });
951
- return zod.NEVER;
999
+ logger.debug("Created Adhese SDK instance", {
1000
+ mergedOptions
1001
+ });
1002
+ }
1003
+ function isPreviewMode() {
1004
+ return window.location.search.includes("adhesePreviewCreativeId");
1005
+ }
1006
+ let isDisposed = false;
1007
+ const [runOnDispose, onDispose] = createHook("onDispose", {
1008
+ onRun(callbacks) {
1009
+ isDisposed = true;
1010
+ logger.debug("Disposal completed");
1011
+ callbacks == null ? void 0 : callbacks.clear();
1012
+ },
1013
+ onAdd() {
1014
+ if (isDisposed)
1015
+ runOnDispose().catch(logger.error);
952
1016
  }
953
1017
  });
954
- const isHtmlString = zod.string().transform((value, { addIssue }) => {
955
- var _a;
956
- const htmlParser = new DOMParser();
957
- try {
958
- const html = htmlParser.parseFromString(value, "text/html");
959
- if (((_a = html.body) == null ? void 0 : _a.children.length) === 0)
960
- throw new Error("Invalid HTML");
961
- return value;
962
- } catch (error) {
963
- addIssue({
964
- code: zod.ZodIssueCode.custom,
965
- message: error.message
1018
+ function createAdhese(options) {
1019
+ const scope = runtimeCore.effectScope();
1020
+ return scope.run(() => {
1021
+ const mergedOptions = {
1022
+ host: `https://ads-${options.account}.adhese.com`,
1023
+ poolHost: `https://pool-${options.account}.adhese.com`,
1024
+ location: "homepage",
1025
+ requestType: "POST",
1026
+ debug: false,
1027
+ initialSlots: [],
1028
+ findDomSlotsOnLoad: false,
1029
+ consent: false,
1030
+ logReferrer: true,
1031
+ logUrl: true,
1032
+ safeFrame: false,
1033
+ eagerRendering: false,
1034
+ viewabilityTracking: true,
1035
+ plugins: [],
1036
+ ...options
1037
+ };
1038
+ setupLogging(mergedOptions);
1039
+ const context = runtimeCore.reactive({
1040
+ location: mergedOptions.location,
1041
+ consent: mergedOptions.consent,
1042
+ debug: mergedOptions.debug,
1043
+ getAll,
1044
+ get,
1045
+ options: mergedOptions,
1046
+ logger,
1047
+ addSlot
966
1048
  });
967
- return zod.NEVER;
968
- }
969
- });
970
- const isJsonOrHtmlString = zod.union([isJson, isHtmlString]);
971
- const isJsonOrHtmlOptionalString = zod.union([zod.coerce.string(), isJsonOrHtmlString]).transform((value) => {
972
- if (value === "")
973
- return void 0;
974
- return value;
975
- }).optional();
976
- const baseSchema = zod.object({
977
- adDuration: numberLike.optional(),
978
- adFormat: zod.string().optional(),
979
- adType: zod.string(),
980
- additionalCreativeTracker: urlLike.optional(),
981
- additionalViewableTracker: zod.string().optional(),
982
- adspaceEnd: dateLike.optional(),
983
- adspaceId: zod.string().optional(),
984
- adspaceKey: zod.string().optional(),
985
- adspaceStart: dateLike.optional(),
986
- advertiserId: zod.string().optional(),
987
- altText: zod.string().optional(),
988
- auctionable: booleanLike.optional(),
989
- body: isJsonOrHtmlOptionalString,
990
- clickTag: urlLike.optional(),
991
- comment: zod.string().optional(),
992
- creativeName: zod.string().optional(),
993
- deliveryGroupId: zod.string().optional(),
994
- deliveryMultiples: zod.string().optional(),
995
- ext: zod.string().optional(),
996
- extension: zod.object({
997
- mediaType: zod.string(),
998
- prebid: zod.unknown().optional()
999
- }).optional(),
1000
- height: numberLike.optional(),
1001
- id: zod.string(),
1002
- impressionCounter: urlLike.optional(),
1003
- libId: zod.string().optional(),
1004
- orderId: zod.string().optional(),
1005
- orderName: zod.string().optional(),
1006
- orderProperty: zod.string().optional(),
1007
- origin: zod.union([zod.literal("JERLICIA"), zod.literal("DALE")]),
1008
- originData: zod.unknown().optional(),
1009
- originInstance: zod.string().optional(),
1010
- poolPath: urlLike.optional(),
1011
- preview: booleanLike.optional(),
1012
- priority: numberLike.optional(),
1013
- sfSrc: urlLike.optional(),
1014
- share: zod.string().optional(),
1015
- // eslint-disable-next-line ts/naming-convention
1016
- slotID: zod.string(),
1017
- slotName: zod.string(),
1018
- swfSrc: urlLike.optional(),
1019
- tag: isJsonOrHtmlOptionalString,
1020
- tagUrl: urlLike.optional(),
1021
- timeStamp: dateLike.optional(),
1022
- trackedImpressionCounter: urlLike.optional(),
1023
- tracker: urlLike.optional(),
1024
- trackingUrl: urlLike.optional(),
1025
- url: urlLike.optional(),
1026
- viewableImpressionCounter: urlLike.optional(),
1027
- width: numberLike.optional(),
1028
- widthLarge: cssValueLike.optional()
1029
- });
1030
- const jerliciaSchema = zod.object({
1031
- origin: zod.literal("JERLICIA"),
1032
- tag: isJsonOrHtmlString
1033
- }).passthrough();
1034
- const daleSchema = zod.object({
1035
- origin: zod.literal("DALE"),
1036
- body: isJsonOrHtmlString
1037
- }).passthrough().transform(({ body, ...data }) => ({
1038
- ...data,
1039
- tag: body
1040
- }));
1041
- const adResponseSchema = baseSchema.extend({
1042
- additionalCreatives: zod.lazy(() => zod.union([adResponseSchema.array(), zod.string()]).optional())
1043
- });
1044
- const adSchema = adResponseSchema.transform(({
1045
- additionalCreatives,
1046
- ...data
1047
- }) => {
1048
- const filteredValue = Object.fromEntries(
1049
- Object.entries(data).filter(([, value]) => Boolean(value) && JSON.stringify(value) !== "{}" && JSON.stringify(value) !== "[]")
1050
- );
1051
- return {
1052
- ...filteredValue,
1053
- additionalCreatives: Array.isArray(additionalCreatives) ? additionalCreatives.map((creative) => adSchema.parse(creative)) : additionalCreatives
1054
- };
1055
- });
1056
- function parseResponse(response) {
1057
- const schemaMap = {
1058
- /* eslint-disable ts/naming-convention */
1059
- JERLICIA: jerliciaSchema,
1060
- DALE: daleSchema
1061
- /* eslint-enable ts/naming-convention */
1062
- };
1063
- const preParsed = adResponseSchema.array().parse(response);
1064
- return preParsed.map((item) => {
1065
- const schema = schemaMap[item.origin];
1066
- if (!schema)
1067
- return adSchema.parse(item);
1068
- return schema.parse(item);
1069
- });
1070
- }
1071
- async function requestPreviews(account) {
1072
- const previewObjects = getPreviewObjects();
1073
- const list = (await Promise.allSettled(previewObjects.filter((previewObject) => "adhesePreviewCreativeId" in previewObject).map(async (previewObject) => {
1074
- const endpoint = new URL(`https://${account}-preview.adhese.org/creatives/preview/json/tag.do`);
1075
- endpoint.searchParams.set(
1076
- "id",
1077
- previewObject.adhesePreviewCreativeId
1078
- );
1079
- const response = await fetch(endpoint.href, {
1080
- method: "GET",
1081
- headers: {
1082
- accept: "application/json"
1083
- }
1049
+ for (const [index, plugin] of mergedOptions.plugins.entries()) {
1050
+ plugin(context, {
1051
+ index
1052
+ });
1053
+ }
1054
+ context.events = createEventManager();
1055
+ context.safeFrame = options.safeFrame ? createSafeFrame({
1056
+ renderFile: `${mergedOptions.poolHost}/sf/r.html`,
1057
+ context
1058
+ }) : void 0;
1059
+ function getLocation() {
1060
+ return context.location;
1061
+ }
1062
+ function setLocation(newLocation) {
1063
+ var _a;
1064
+ context.location = newLocation;
1065
+ (_a = context.events) == null ? void 0 : _a.locationChange.dispatch(newLocation);
1066
+ }
1067
+ const queryDetector = createQueryDetector({
1068
+ onChange: onQueryChange,
1069
+ queries: mergedOptions.queries
1084
1070
  });
1085
- if (!response.ok)
1086
- return Promise.reject(new Error(`Failed to request preview ad with ID: ${previewObject.adhesePreviewCreativeId}`));
1087
- return await response.json();
1088
- }))).filter((response) => {
1089
- if (response.status === "rejected") {
1090
- logger.error(response.reason);
1091
- return false;
1071
+ context.parameters = createParameters(mergedOptions, queryDetector);
1072
+ runtimeCore.watch(
1073
+ context.parameters,
1074
+ onParametersChange,
1075
+ {
1076
+ deep: true,
1077
+ immediate: true
1078
+ }
1079
+ );
1080
+ function onParametersChange() {
1081
+ var _a;
1082
+ if (context.parameters)
1083
+ (_a = context.events) == null ? void 0 : _a.parametersChange.dispatch(context.parameters);
1092
1084
  }
1093
- return response.status === "fulfilled";
1094
- }).map((response) => response.value.map((item) => ({
1095
- ...item,
1096
- preview: true
1097
- })));
1098
- return adSchema.array().parse(list.flat());
1099
- }
1100
- function getPreviewObjects() {
1101
- const currentUrl = new URL(window.location.href);
1102
- const previewObjects = [];
1103
- let currentObject = {};
1104
- for (const [key, value] of currentUrl.searchParams.entries()) {
1105
- if (key === "adhesePreviewCreativeId" && Object.keys(currentObject).length > 0) {
1106
- previewObjects.push(currentObject);
1107
- currentObject = {};
1085
+ async function onQueryChange() {
1086
+ var _a, _b;
1087
+ const query = queryDetector.getQuery();
1088
+ (_a = context.parameters) == null ? void 0 : _a.set("dt", query);
1089
+ (_b = context.parameters) == null ? void 0 : _b.set("br", query);
1090
+ await fetchAndRenderAllSlots();
1108
1091
  }
1109
- currentObject[key] = value;
1110
- }
1111
- if (Object.keys(currentObject).length > 0)
1112
- previewObjects.push(currentObject);
1113
- return previewObjects;
1114
- }
1115
- function requestWithPost({
1116
- context: { options: { host }, parameters },
1117
- ...options
1118
- }) {
1119
- const payload = {
1120
- ...options,
1121
- slots: options.slots.map((slot) => ({
1122
- slotname: runtimeCore.toValue(slot.name),
1123
- parameters: parseParameters(slot.parameters)
1124
- })),
1125
- parameters: parameters && parseParameters(parameters)
1126
- };
1127
- return fetch(`${new URL(host).href}json`, {
1128
- method: "POST",
1129
- body: JSON.stringify(payload),
1130
- headers: {
1131
- // eslint-disable-next-line ts/naming-convention
1132
- "Content-Type": "application/json"
1092
+ function getConsent() {
1093
+ return context.consent;
1133
1094
  }
1134
- });
1135
- }
1136
- async function requestWithGet({ context, slots }) {
1137
- return fetch(new URL(`${context.options.host}/json/sl${slots.map((slot) => runtimeCore.toValue(slot.name)).join("/sl")}`), {
1138
- method: "GET",
1139
- headers: {
1140
- // eslint-disable-next-line ts/naming-convention
1141
- "Content-Type": "application/json"
1095
+ function setConsent(newConsent) {
1096
+ var _a, _b;
1097
+ (_a = context.parameters) == null ? void 0 : _a.set("tl", newConsent ? "all" : "none");
1098
+ context.consent = newConsent;
1099
+ (_b = context.events) == null ? void 0 : _b.consentChange.dispatch(newConsent);
1142
1100
  }
1143
- });
1144
- }
1145
- function parseParameters(parameters) {
1146
- return Object.fromEntries(Array.from(parameters.entries()).filter(([key]) => {
1147
- if (key.length === 2)
1148
- return true;
1149
- logger.warn(`Invalid parameter key: ${key}. Key should be exactly 2 characters long. Key will be ignored.`);
1150
- return false;
1151
- }).map(([key, value]) => {
1152
- if (typeof value === "string")
1153
- return [key, filterSpecialChars(value)];
1154
- return [key, value.map(filterSpecialChars)];
1155
- }));
1156
- }
1157
- function filterSpecialChars(value) {
1158
- const specialRegex = /[^\p{L}\p{N}_]/gu;
1159
- return value.replaceAll(specialRegex, "_");
1160
- }
1161
- async function requestAds(options) {
1162
- var _a, _b, _c, _d, _e;
1163
- const { context } = options;
1164
- try {
1165
- (_a = context.events) == null ? void 0 : _a.requestAd.dispatch({
1166
- ...options,
1101
+ const slotManager = createSlotManager({
1102
+ initialSlots: mergedOptions.initialSlots,
1167
1103
  context
1168
1104
  });
1169
- const [response, previews] = await Promise.all([((_b = context.options.requestType) == null ? void 0 : _b.toUpperCase()) === "POST" ? requestWithPost(options) : requestWithGet(options), requestPreviews(context.options.account)]);
1170
- logger.debug("Received response", response);
1171
- if (!response.ok)
1172
- throw new Error(`Failed to request ad: ${response.status} ${response.statusText}`);
1173
- const result = parseResponse(await response.json());
1174
- logger.debug("Parsed ad", result);
1175
- if (previews.length > 0)
1176
- logger.info(`Found ${previews.length} ${previews.length === 1 ? "preview" : "previews"}. Replacing ads in response with preview items`, previews);
1177
- const matchedPreviews = previews.map(({ slotName, ...preview }) => {
1178
- const partnerAd = result.find((ad) => ad.libId === preview.libId);
1179
- return {
1180
- slotName: `${(partnerAd == null ? void 0 : partnerAd.slotName) ?? slotName}`,
1181
- ...preview
1182
- };
1105
+ function getAll() {
1106
+ return slotManager.getAll() ?? [];
1107
+ }
1108
+ function get(name) {
1109
+ return slotManager.get(name);
1110
+ }
1111
+ function addSlot(slotOptions) {
1112
+ if (!slotManager)
1113
+ throw new Error("Slot manager not initialized");
1114
+ return slotManager.add(slotOptions);
1115
+ }
1116
+ async function findDomSlots2() {
1117
+ const domSlots = (await slotManager.findDomSlots() ?? []).filter((slot) => !slot.lazyLoading);
1118
+ if (domSlots.length <= 0)
1119
+ return [];
1120
+ const ads = await requestAds({
1121
+ slots: domSlots,
1122
+ context
1123
+ });
1124
+ for (const ad of ads) {
1125
+ const slot = slotManager.get(ad.slotName);
1126
+ if (slot)
1127
+ slot.ad.value = ad;
1128
+ }
1129
+ return domSlots;
1130
+ }
1131
+ async function toggleDebug() {
1132
+ var _a, _b;
1133
+ context.debug = !context.debug;
1134
+ if (context.debug) {
1135
+ logger.setMinLogLevelThreshold("debug");
1136
+ logger.debug("Debug mode enabled");
1137
+ (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
1138
+ } else {
1139
+ logger.debug("Debug mode disabled");
1140
+ logger.setMinLogLevelThreshold("info");
1141
+ (_b = context.events) == null ? void 0 : _b.debugChange.dispatch(false);
1142
+ }
1143
+ return context.debug;
1144
+ }
1145
+ async function fetchAndRenderAllSlots() {
1146
+ const slots = (slotManager.getAll() ?? []).filter((slot) => !slot.lazyLoading);
1147
+ if (slots.length === 0)
1148
+ return;
1149
+ const ads = await requestAds({
1150
+ slots,
1151
+ context
1152
+ });
1153
+ for (const ad of ads) {
1154
+ const slot = slotManager.get(ad.slotName);
1155
+ if (slot)
1156
+ slot.ad.value = ad;
1157
+ }
1158
+ }
1159
+ const disposeOnTcfConsentChange = onTcfConsentChange(async (data) => {
1160
+ var _a, _b;
1161
+ if (!data.tcString)
1162
+ return;
1163
+ logger.debug("TCF v2 consent data received", {
1164
+ data
1165
+ });
1166
+ (_a = context.parameters) == null ? void 0 : _a.set("xt", data.tcString);
1167
+ (_b = context.parameters) == null ? void 0 : _b.delete("tl");
1168
+ await fetchAndRenderAllSlots();
1183
1169
  });
1184
- if (matchedPreviews.length > 0)
1185
- (_c = context.events) == null ? void 0 : _c.previewReceived.dispatch(matchedPreviews);
1186
- const mergedResult = [
1187
- ...result.filter((ad) => !previews.some((preview) => preview.libId === ad.libId)),
1188
- ...matchedPreviews
1189
- ];
1190
- if (mergedResult.length === 0)
1191
- throw new Error("No ads found");
1192
- (_d = context.events) == null ? void 0 : _d.responseReceived.dispatch(mergedResult);
1193
- return mergedResult;
1194
- } catch (error) {
1195
- logger.error(String(error));
1196
- (_e = context.events) == null ? void 0 : _e.requestError.dispatch(error);
1197
- throw error;
1198
- }
1199
- }
1200
- async function requestAd({
1201
- slot,
1202
- ...options
1203
- }) {
1204
- const [ad] = await requestAds({
1205
- slots: [slot],
1206
- ...options
1170
+ function dispose() {
1171
+ var _a, _b;
1172
+ queryDetector.dispose();
1173
+ slotManager.dispose();
1174
+ queryDetector.dispose();
1175
+ disposeOnTcfConsentChange();
1176
+ (_a = context.parameters) == null ? void 0 : _a.clear();
1177
+ logger.resetLogs();
1178
+ (_b = context.events) == null ? void 0 : _b.dispose();
1179
+ logger.info("Adhese instance disposed");
1180
+ runOnDispose().catch(logger.error);
1181
+ clearAllHooks();
1182
+ scope.stop();
1183
+ }
1184
+ onInit(async () => {
1185
+ var _a;
1186
+ if ((slotManager.getAll().length ?? 0) > 0)
1187
+ await fetchAndRenderAllSlots().catch(logger.error);
1188
+ if (mergedOptions.findDomSlotsOnLoad)
1189
+ await findDomSlots2();
1190
+ if (mergedOptions.debug || window.location.search.includes("adhese_debug=true") || isPreviewMode())
1191
+ (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
1192
+ if (!scope.active)
1193
+ dispose();
1194
+ });
1195
+ runOnInit().catch(logger.error);
1196
+ return {
1197
+ parameters: context.parameters,
1198
+ events: context.events,
1199
+ getLocation,
1200
+ setLocation,
1201
+ getConsent,
1202
+ setConsent,
1203
+ addSlot,
1204
+ findDomSlots: findDomSlots2,
1205
+ dispose,
1206
+ toggleDebug,
1207
+ get: slotManager.get,
1208
+ getAll: slotManager.getAll,
1209
+ context,
1210
+ options: mergedOptions
1211
+ };
1207
1212
  });
1208
- return ad;
1209
1213
  }
1210
1214
  exports.createAdhese = createAdhese;
1215
+ exports.logger = logger;
1211
1216
  exports.onDispose = onDispose;
1212
1217
  exports.onInit = onInit;
1218
+ exports.onRender = onRender;
1219
+ exports.onRequest = onRequest;
1220
+ exports.onResponse = onResponse;
1221
+ exports.requestAd = requestAd;
1222
+ exports.requestAds = requestAds;
1213
1223
  //# sourceMappingURL=index.cjs.map