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