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