@adhese/sdk 0.7.0 → 0.8.1

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,4 +1,4 @@
1
- import { effectScope, shallowReactive, watchEffect, watch, reactive, ref, computed, toValue } from "@vue/runtime-core";
1
+ import { toValue, ref, computed, watch, effectScope, reactive, shallowReactive, watchEffect } from "@vue/runtime-core";
2
2
  import { debounce, round, isDeepEqual } from "remeda";
3
3
  import { union, coerce, literal, number, string, ZodIssueCode, NEVER, object, unknown, lazy } from "zod";
4
4
  async function waitForDomLoad() {
@@ -121,6 +121,56 @@ function createSafeFrame({
121
121
  dispose
122
122
  };
123
123
  }
124
+ const name = "@adhese/sdk";
125
+ const type = "module";
126
+ const version = "0.8.1";
127
+ const description = "Adhese SDK";
128
+ const license = "GPL-3.0";
129
+ const repository = {
130
+ type: "git",
131
+ url: "git+https://github.com/adhese/sdk_typescript.git"
132
+ };
133
+ const main = "./dist/index.cjs";
134
+ const module = "./dist/index.js";
135
+ const types = "./dist/index.d.ts";
136
+ const files = [
137
+ "LICENSE",
138
+ "README.md",
139
+ "dist"
140
+ ];
141
+ const scripts = {
142
+ dev: "vitest --config vite.config.npm.ts --silent",
143
+ "dev:verbose": "vitest --config vite.config.npm.ts",
144
+ test: "vitest --config vite.config.npm.ts --silent --run --coverage",
145
+ "test:verbose": "vitest --config vite.config.npm.ts --run --coverage",
146
+ build: "npm run build:npm && npm run build:standalone",
147
+ "build:npm": "vite build --config vite.config.npm.ts && tsup src/index.ts --dts-only --minify --format esm",
148
+ "build:standalone": "vite build --config vite.config.standalone.ts",
149
+ lint: "eslint . --ignore-pattern **/*.md/**/*",
150
+ "lint:fix": "eslint --fix . --ignore-pattern **/*.md/**/*",
151
+ clean: "rm -rf dist",
152
+ typecheck: "tsc --noEmit",
153
+ prepareRelease: "npm run build"
154
+ };
155
+ const dependencies = {
156
+ "@vue/runtime-core": "^3.4.21",
157
+ remeda: "^1.61.0",
158
+ zod: "^3.23.0"
159
+ };
160
+ const packageJson = {
161
+ name,
162
+ type,
163
+ version,
164
+ description,
165
+ license,
166
+ repository,
167
+ main,
168
+ module,
169
+ types,
170
+ files,
171
+ scripts,
172
+ dependencies
173
+ };
124
174
  function renderIframe(ad, element) {
125
175
  const iframe = document.createElement("iframe");
126
176
  iframe.srcdoc = `
@@ -150,109 +200,16 @@ function renderInline(ad, element) {
150
200
  function generateName(location, format, slot) {
151
201
  return `${location}${slot ? `${slot}` : ""}-${format}`;
152
202
  }
153
- async function findDomSlots(context) {
154
- await waitForDomLoad();
155
- return Array.from(document.querySelectorAll(".adunit")).filter((element) => {
156
- var _a;
157
- if (!element.dataset.format)
158
- return false;
159
- const name = generateName(
160
- context.location,
161
- element.dataset.format,
162
- element.dataset.slot
163
- );
164
- return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === name));
165
- }).map((element) => createSlot({
166
- format: element.dataset.format,
167
- containingElement: element,
168
- slot: element.dataset.slot,
169
- context
170
- })).filter((slot) => {
171
- var _a;
172
- return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === slot.name.value));
173
- });
174
- }
175
- function createSlotManager({
176
- initialSlots = [],
177
- context
178
- }) {
179
- const scope = effectScope();
180
- return scope.run(() => {
181
- const slots = shallowReactive(/* @__PURE__ */ new Map());
182
- watchEffect(() => {
183
- var _a;
184
- (_a = context.events) == null ? void 0 : _a.changeSlots.dispatch(Array.from(slots.values()));
185
- });
186
- function getAll() {
187
- return Array.from(slots).map(([, slot]) => slot);
188
- }
189
- function add(options) {
190
- var _a;
191
- const slot = createSlot({
192
- ...options,
193
- onDispose: onDispose2,
194
- context
195
- });
196
- function onDispose2() {
197
- var _a2;
198
- slots.delete(slot.name.value);
199
- logger.debug("Slot removed", {
200
- slot,
201
- slots: Array.from(slots)
202
- });
203
- (_a2 = context.events) == null ? void 0 : _a2.removeSlot.dispatch(slot);
204
- }
205
- slots.set(slot.name.value, slot);
206
- watch(slot.name, (newName, previousName) => {
207
- slots.set(newName, slot);
208
- slots.delete(previousName);
209
- });
210
- logger.debug("Slot added", {
211
- slot,
212
- slots: Array.from(slots.values())
213
- });
214
- (_a = context.events) == null ? void 0 : _a.addSlot.dispatch(slot);
215
- return slot;
216
- }
217
- async function findDomSlots$1() {
218
- const domSlots = await findDomSlots(
219
- context
220
- );
221
- for (const slot of domSlots)
222
- slots.set(slot.name.value, slot);
223
- return domSlots;
224
- }
225
- function get(name) {
226
- return slots.get(name);
227
- }
228
- function dispose() {
229
- for (const slot of slots.values())
230
- slot.dispose();
231
- slots.clear();
232
- scope.stop();
233
- }
234
- for (const options of initialSlots) {
235
- add({
236
- ...options,
237
- lazyLoading: false
238
- });
239
- }
240
- return {
241
- getAll,
242
- add,
243
- findDomSlots: findDomSlots$1,
244
- get,
245
- dispose
246
- };
247
- });
248
- }
249
- function onTcfConsentChange(callback) {
250
- var _a;
251
- (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, callback);
252
- return () => {
253
- var _a2;
254
- return (_a2 = window.__tcfapi) == null ? void 0 : _a2.call(window, "removeEventListener", 2, callback);
255
- };
203
+ function addTrackingPixel(url) {
204
+ const img = document.createElement("img");
205
+ img.src = url.toString();
206
+ img.style.height = "1px";
207
+ img.style.width = "1px";
208
+ img.style.margin = "-1px";
209
+ img.style.border = "0";
210
+ img.style.position = "absolute";
211
+ img.style.top = "0";
212
+ return document.body.appendChild(img);
256
213
  }
257
214
  function createQueryDetector({
258
215
  onChange,
@@ -292,6 +249,53 @@ function createQueryDetector({
292
249
  dispose
293
250
  };
294
251
  }
252
+ const hookMap = /* @__PURE__ */ new Map();
253
+ function clearAllHooks() {
254
+ hookMap.clear();
255
+ }
256
+ function createHook(name2, {
257
+ onRun,
258
+ onAdd
259
+ } = {}) {
260
+ hookMap.set(name2, /* @__PURE__ */ new Set());
261
+ const run = async (arg) => {
262
+ let latestResult = arg;
263
+ for (const callback of hookMap.get(name2) ?? [])
264
+ latestResult = await callback(latestResult) ?? latestResult;
265
+ onRun == null ? void 0 : onRun(hookMap.get(name2));
266
+ return latestResult;
267
+ };
268
+ function add(callback) {
269
+ const hookSet = hookMap.get(name2);
270
+ if (hookSet)
271
+ hookSet.add(callback);
272
+ else
273
+ hookMap.set(name2, /* @__PURE__ */ new Set([callback]));
274
+ onAdd == null ? void 0 : onAdd(hookSet);
275
+ return () => {
276
+ var _a;
277
+ (_a = hookMap.get(name2)) == null ? void 0 : _a.delete(callback);
278
+ };
279
+ }
280
+ return [run, add];
281
+ }
282
+ let resolveOnInitPromise = () => {
283
+ };
284
+ let isInit = false;
285
+ const waitOnInit = new Promise((resolve) => {
286
+ resolveOnInitPromise = resolve;
287
+ });
288
+ const [runOnInit, onInit] = createHook("onInit", {
289
+ onRun(callbacks) {
290
+ isInit = true;
291
+ resolveOnInitPromise();
292
+ callbacks == null ? void 0 : callbacks.clear();
293
+ },
294
+ onAdd() {
295
+ if (isInit)
296
+ runOnInit().catch(console.error);
297
+ }
298
+ });
295
299
  const defaultLogLevels = ["trace", "debug", "info", "warn", "error"];
296
300
  function createLogger({
297
301
  scope,
@@ -365,302 +369,302 @@ function createLogger({
365
369
  const logger = createLogger({
366
370
  scope: "Adhese SDK"
367
371
  });
368
- function createParameters(options, queryDetector) {
369
- const parameters = /* @__PURE__ */ new Map();
370
- if (options.logReferrer)
371
- parameters.set("re", btoa(document.referrer));
372
- if (options.logUrl)
373
- parameters.set("ur", btoa(window.location.href));
374
- for (const [key, value] of Object.entries({
375
- ...options.parameters ?? {},
376
- tl: options.consent ? "all" : "none",
377
- dt: queryDetector.getQuery(),
378
- br: queryDetector.getQuery(),
379
- rn: Math.round(Math.random() * 1e4).toString()
380
- }))
381
- parameters.set(key, value);
382
- return parameters;
383
- }
384
- function setupLogging(mergedOptions) {
385
- if (mergedOptions.debug || window.location.search.includes("adhese_debug=true")) {
386
- logger.setMinLogLevelThreshold("debug");
387
- logger.debug("Debug logging enabled");
388
- }
389
- logger.debug("Created Adhese SDK instance", {
390
- mergedOptions
391
- });
392
- }
393
- function isPreviewMode() {
394
- return window.location.search.includes("adhesePreviewCreativeId");
395
- }
396
- function createHook({
397
- onRun,
398
- onAdd,
399
- onDispose: onDispose2
400
- }) {
401
- const callbacks = /* @__PURE__ */ new Set();
402
- function run() {
403
- for (const callback of callbacks)
404
- callback();
405
- onRun == null ? void 0 : onRun(callbacks);
406
- }
407
- function add(callback) {
408
- callbacks.add(callback);
409
- onAdd == null ? void 0 : onAdd(callbacks);
410
- }
411
- function dispose() {
412
- onDispose2 == null ? void 0 : onDispose2(callbacks);
413
- callbacks.clear();
372
+ const [runOnRequest, onRequest] = createHook("onRequest");
373
+ const [runOnResponse, onResponse] = createHook("onResponse");
374
+ const numberLike = union([coerce.string().regex(/^\d+$/), literal("")]).transform((value) => value === "" ? void 0 : Number(value));
375
+ const booleanLike = union([coerce.boolean(), literal("")]);
376
+ const urlLike = union([coerce.string(), literal("")]).transform((value) => {
377
+ try {
378
+ return new URL(value);
379
+ } catch {
380
+ return void 0;
414
381
  }
415
- return [run, add, dispose];
416
- }
417
- let resolveOnInitPromise = () => {
418
- };
419
- let isInit = false;
420
- const waitOnInit = new Promise((resolve) => {
421
- resolveOnInitPromise = resolve;
422
382
  });
423
- const [runOnInit, onInit, disposeOnInit] = createHook({
424
- onRun(callbacks) {
425
- isInit = true;
426
- resolveOnInitPromise();
427
- logger.debug("Initialization completed");
428
- callbacks.clear();
429
- },
430
- onAdd() {
431
- if (isInit)
432
- runOnInit();
433
- }
383
+ const dateLike = union([coerce.string(), literal("")]).transform((value) => {
384
+ if (value === "")
385
+ return void 0;
386
+ const date = new Date(numberLike.safeParse(value).success ? Number(value) : value);
387
+ if (Number.isNaN(date.getTime()))
388
+ return void 0;
389
+ return date;
434
390
  });
435
- let resolveOnDisposePromise = () => {
436
- };
437
- let isDisposed = false;
438
- new Promise((resolve) => {
439
- resolveOnDisposePromise = resolve;
391
+ const cssValueLike = union([coerce.string(), literal(""), number()]).transform((value) => {
392
+ if (value === "" || value === 0 || value === "0")
393
+ return void 0;
394
+ if (numberLike.parse(value))
395
+ return `${numberLike.parse(value)}px`;
396
+ return String(value);
440
397
  });
441
- const [runOnDispose, onDispose, disposeOnDispose] = createHook({
442
- onRun(callbacks) {
443
- isDisposed = true;
444
- resolveOnDisposePromise();
445
- logger.debug("Disposal completed");
446
- callbacks.clear();
447
- },
448
- onAdd() {
449
- if (isDisposed)
450
- runOnDispose();
398
+ const isJson = string().transform((value, { addIssue }) => {
399
+ try {
400
+ return JSON.parse(value.replaceAll("'", '"'));
401
+ } catch (error) {
402
+ addIssue({
403
+ code: ZodIssueCode.custom,
404
+ message: `Invalid JSON: ${error.message}`
405
+ });
406
+ return NEVER;
451
407
  }
452
408
  });
453
- function createAdhese(options) {
454
- const scope = effectScope();
455
- return scope.run(() => {
456
- const mergedOptions = {
457
- host: `https://ads-${options.account}.adhese.com`,
458
- poolHost: `https://pool-${options.account}.adhese.com`,
459
- location: "homepage",
460
- requestType: "POST",
461
- debug: false,
462
- initialSlots: [],
463
- findDomSlotsOnLoad: false,
464
- consent: false,
465
- logReferrer: true,
466
- logUrl: true,
467
- safeFrame: false,
468
- eagerRendering: false,
469
- viewabilityTracking: true,
470
- plugins: [],
471
- ...options
472
- };
473
- setupLogging(mergedOptions);
474
- const context = reactive({
475
- location: mergedOptions.location,
476
- consent: mergedOptions.consent,
477
- debug: mergedOptions.debug,
478
- getAll,
479
- get,
480
- options: mergedOptions,
481
- logger
482
- });
483
- context.events = createEventManager();
484
- context.safeFrame = options.safeFrame ? createSafeFrame({
485
- renderFile: `${mergedOptions.poolHost}/sf/r.html`,
486
- context
487
- }) : void 0;
488
- function getLocation() {
489
- return context.location;
490
- }
491
- function setLocation(newLocation) {
492
- var _a;
493
- context.location = newLocation;
494
- (_a = context.events) == null ? void 0 : _a.locationChange.dispatch(newLocation);
495
- }
496
- const queryDetector = createQueryDetector({
497
- onChange: onQueryChange,
498
- queries: mergedOptions.queries
409
+ const isHtmlString = string().transform((value, { addIssue }) => {
410
+ var _a;
411
+ const htmlParser = new DOMParser();
412
+ try {
413
+ const html = htmlParser.parseFromString(value, "text/html");
414
+ if (((_a = html.body) == null ? void 0 : _a.children.length) === 0)
415
+ throw new Error("Invalid HTML");
416
+ return value;
417
+ } catch (error) {
418
+ addIssue({
419
+ code: ZodIssueCode.custom,
420
+ message: error.message
499
421
  });
500
- context.parameters = createParameters(mergedOptions, queryDetector);
501
- watch(
502
- context.parameters,
503
- onParametersChange,
504
- {
505
- deep: true,
506
- immediate: true
507
- }
422
+ return NEVER;
423
+ }
424
+ });
425
+ const isJsonOrHtmlString = union([isJson, isHtmlString]);
426
+ const isJsonOrHtmlOptionalString = union([coerce.string(), isJsonOrHtmlString]).transform((value) => {
427
+ if (value === "")
428
+ return void 0;
429
+ return value;
430
+ }).optional();
431
+ const baseSchema = object({
432
+ adDuration: numberLike.optional(),
433
+ adFormat: string().optional(),
434
+ adType: string(),
435
+ additionalCreativeTracker: urlLike.optional(),
436
+ additionalViewableTracker: string().optional(),
437
+ adspaceEnd: dateLike.optional(),
438
+ adspaceId: string().optional(),
439
+ adspaceKey: string().optional(),
440
+ adspaceStart: dateLike.optional(),
441
+ advertiserId: string().optional(),
442
+ altText: string().optional(),
443
+ auctionable: booleanLike.optional(),
444
+ body: isJsonOrHtmlOptionalString,
445
+ clickTag: urlLike.optional(),
446
+ comment: string().optional(),
447
+ creativeName: string().optional(),
448
+ deliveryGroupId: string().optional(),
449
+ deliveryMultiples: string().optional(),
450
+ ext: string().optional(),
451
+ extension: object({
452
+ mediaType: string(),
453
+ prebid: unknown().optional()
454
+ }).optional(),
455
+ height: numberLike.optional(),
456
+ id: string(),
457
+ impressionCounter: urlLike.optional(),
458
+ libId: string().optional(),
459
+ orderId: string().optional(),
460
+ orderName: string().optional(),
461
+ orderProperty: string().optional(),
462
+ origin: union([literal("JERLICIA"), literal("DALE")]),
463
+ originData: unknown().optional(),
464
+ originInstance: string().optional(),
465
+ poolPath: urlLike.optional(),
466
+ preview: booleanLike.optional(),
467
+ priority: numberLike.optional(),
468
+ sfSrc: urlLike.optional(),
469
+ share: string().optional(),
470
+ // eslint-disable-next-line ts/naming-convention
471
+ slotID: string(),
472
+ slotName: string(),
473
+ swfSrc: urlLike.optional(),
474
+ tag: isJsonOrHtmlOptionalString,
475
+ tagUrl: urlLike.optional(),
476
+ timeStamp: dateLike.optional(),
477
+ trackedImpressionCounter: urlLike.optional(),
478
+ tracker: urlLike.optional(),
479
+ trackingUrl: urlLike.optional(),
480
+ url: urlLike.optional(),
481
+ viewableImpressionCounter: urlLike.optional(),
482
+ width: numberLike.optional(),
483
+ widthLarge: cssValueLike.optional()
484
+ });
485
+ const jerliciaSchema = object({
486
+ origin: literal("JERLICIA"),
487
+ tag: isJsonOrHtmlString
488
+ }).passthrough();
489
+ const daleSchema = object({
490
+ origin: literal("DALE"),
491
+ body: isJsonOrHtmlString
492
+ }).passthrough().transform(({ body, ...data }) => ({
493
+ ...data,
494
+ tag: body
495
+ }));
496
+ const adResponseSchema = baseSchema.extend({
497
+ additionalCreatives: lazy(() => union([adResponseSchema.array(), string()]).optional())
498
+ });
499
+ const adSchema = adResponseSchema.transform(({
500
+ additionalCreatives,
501
+ ...data
502
+ }) => {
503
+ const filteredValue = Object.fromEntries(
504
+ Object.entries(data).filter(([, value]) => Boolean(value) && JSON.stringify(value) !== "{}" && JSON.stringify(value) !== "[]")
505
+ );
506
+ return {
507
+ ...filteredValue,
508
+ additionalCreatives: Array.isArray(additionalCreatives) ? additionalCreatives.map((creative) => adSchema.parse(creative)) : additionalCreatives
509
+ };
510
+ });
511
+ function parseResponse(response) {
512
+ const schemaMap = {
513
+ /* eslint-disable ts/naming-convention */
514
+ JERLICIA: jerliciaSchema,
515
+ DALE: daleSchema
516
+ /* eslint-enable ts/naming-convention */
517
+ };
518
+ const preParsed = adResponseSchema.array().parse(response);
519
+ return preParsed.map((item) => {
520
+ const schema = schemaMap[item.origin];
521
+ if (!schema)
522
+ return adSchema.parse(item);
523
+ return schema.parse(item);
524
+ });
525
+ }
526
+ async function requestPreviews(account) {
527
+ const previewObjects = getPreviewObjects();
528
+ const list = (await Promise.allSettled(previewObjects.filter((previewObject) => "adhesePreviewCreativeId" in previewObject).map(async (previewObject) => {
529
+ const endpoint = new URL(`https://${account}-preview.adhese.org/creatives/preview/json/tag.do`);
530
+ endpoint.searchParams.set(
531
+ "id",
532
+ previewObject.adhesePreviewCreativeId
508
533
  );
509
- function onParametersChange() {
510
- var _a;
511
- if (context.parameters)
512
- (_a = context.events) == null ? void 0 : _a.parametersChange.dispatch(context.parameters);
513
- }
514
- async function onQueryChange() {
515
- var _a, _b;
516
- const query = queryDetector.getQuery();
517
- (_a = context.parameters) == null ? void 0 : _a.set("dt", query);
518
- (_b = context.parameters) == null ? void 0 : _b.set("br", query);
519
- await fetchAndRenderAllSlots();
520
- }
521
- function getConsent() {
522
- return context.consent;
523
- }
524
- function setConsent(newConsent) {
525
- var _a, _b;
526
- (_a = context.parameters) == null ? void 0 : _a.set("tl", newConsent ? "all" : "none");
527
- context.consent = newConsent;
528
- (_b = context.events) == null ? void 0 : _b.consentChange.dispatch(newConsent);
529
- }
530
- const slotManager = createSlotManager({
531
- initialSlots: mergedOptions.initialSlots,
532
- context
533
- });
534
- function getAll() {
535
- return slotManager.getAll() ?? [];
536
- }
537
- function get(name) {
538
- return slotManager.get(name);
539
- }
540
- async function addSlot(slotOptions) {
541
- if (!slotManager)
542
- throw new Error("Slot manager not initialized");
543
- const slot = slotManager.add(slotOptions);
544
- if (!slot.lazyLoading) {
545
- slot.ad.value = await requestAd({
546
- slot,
547
- context
548
- });
534
+ const response = await fetch(endpoint.href, {
535
+ method: "GET",
536
+ headers: {
537
+ accept: "application/json"
549
538
  }
550
- return slot;
539
+ });
540
+ if (!response.ok)
541
+ return Promise.reject(new Error(`Failed to request preview ad with ID: ${previewObject.adhesePreviewCreativeId}`));
542
+ return await response.json();
543
+ }))).filter((response) => {
544
+ if (response.status === "rejected") {
545
+ logger.error(response.reason);
546
+ return false;
551
547
  }
552
- async function findDomSlots2() {
553
- const domSlots = (await slotManager.findDomSlots() ?? []).filter((slot) => !slot.lazyLoading);
554
- if (domSlots.length <= 0)
555
- return [];
556
- const ads = await requestAds({
557
- slots: domSlots,
558
- context
559
- });
560
- for (const ad of ads) {
561
- const slot = slotManager.get(ad.slotName);
562
- if (slot)
563
- slot.ad.value = ad;
564
- }
565
- return domSlots;
548
+ return response.status === "fulfilled";
549
+ }).map((response) => response.value.map((item) => ({
550
+ ...item,
551
+ preview: true
552
+ })));
553
+ return adSchema.array().parse(list.flat());
554
+ }
555
+ function getPreviewObjects() {
556
+ const currentUrl = new URL(window.location.href);
557
+ const previewObjects = [];
558
+ let currentObject = {};
559
+ for (const [key, value] of currentUrl.searchParams.entries()) {
560
+ if (key === "adhesePreviewCreativeId" && Object.keys(currentObject).length > 0) {
561
+ previewObjects.push(currentObject);
562
+ currentObject = {};
566
563
  }
567
- async function toggleDebug() {
568
- var _a, _b;
569
- context.debug = !context.debug;
570
- if (context.debug) {
571
- logger.setMinLogLevelThreshold("debug");
572
- logger.debug("Debug mode enabled");
573
- (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
574
- } else {
575
- logger.debug("Debug mode disabled");
576
- logger.setMinLogLevelThreshold("info");
577
- (_b = context.events) == null ? void 0 : _b.debugChange.dispatch(false);
578
- }
579
- return context.debug;
564
+ currentObject[key] = value;
565
+ }
566
+ if (Object.keys(currentObject).length > 0)
567
+ previewObjects.push(currentObject);
568
+ return previewObjects;
569
+ }
570
+ function requestWithPost({
571
+ context: { options: { host }, parameters },
572
+ ...options
573
+ }) {
574
+ const payload = {
575
+ ...options,
576
+ slots: options.slots.map((slot) => ({
577
+ slotname: toValue(slot.name),
578
+ parameters: parseParameters(slot.parameters)
579
+ })),
580
+ parameters: parameters && parseParameters(parameters)
581
+ };
582
+ return fetch(`${new URL(host).href}json`, {
583
+ method: "POST",
584
+ body: JSON.stringify(payload),
585
+ headers: {
586
+ // eslint-disable-next-line ts/naming-convention
587
+ "Content-Type": "application/json"
580
588
  }
581
- async function fetchAndRenderAllSlots() {
582
- const slots = (slotManager.getAll() ?? []).filter((slot) => !slot.lazyLoading);
583
- if (slots.length === 0)
584
- return;
585
- const ads = await requestAds({
586
- slots,
587
- context
588
- });
589
- for (const ad of ads) {
590
- const slot = slotManager.get(ad.slotName);
591
- if (slot)
592
- slot.ad.value = ad;
593
- }
589
+ });
590
+ }
591
+ async function requestWithGet({ context, slots }) {
592
+ return fetch(new URL(`${context.options.host}/json/sl${slots.map((slot) => toValue(slot.name)).join("/sl")}`), {
593
+ method: "GET",
594
+ headers: {
595
+ // eslint-disable-next-line ts/naming-convention
596
+ "Content-Type": "application/json"
594
597
  }
595
- const disposeOnTcfConsentChange = onTcfConsentChange(async (data) => {
596
- var _a, _b;
597
- if (!data.tcString)
598
- return;
599
- logger.debug("TCF v2 consent data received", {
600
- data
601
- });
602
- (_a = context.parameters) == null ? void 0 : _a.set("xt", data.tcString);
603
- (_b = context.parameters) == null ? void 0 : _b.delete("tl");
604
- await fetchAndRenderAllSlots();
598
+ });
599
+ }
600
+ function parseParameters(parameters) {
601
+ return Object.fromEntries(Array.from(parameters.entries()).filter(([key]) => {
602
+ if (key.length === 2)
603
+ return true;
604
+ logger.warn(`Invalid parameter key: ${key}. Key should be exactly 2 characters long. Key will be ignored.`);
605
+ return false;
606
+ }).map(([key, value]) => {
607
+ if (typeof value === "string")
608
+ return [key, filterSpecialChars(value)];
609
+ return [key, value.map(filterSpecialChars)];
610
+ }));
611
+ }
612
+ function filterSpecialChars(value) {
613
+ const specialRegex = /[^\p{L}\p{N}_]/gu;
614
+ return value.replaceAll(specialRegex, "_");
615
+ }
616
+ async function requestAds(requestOptions) {
617
+ var _a, _b, _c, _d, _e;
618
+ const options = await runOnRequest(requestOptions);
619
+ const { context } = options;
620
+ try {
621
+ (_a = context.events) == null ? void 0 : _a.requestAd.dispatch({
622
+ ...options,
623
+ context
605
624
  });
606
- function dispose() {
607
- var _a, _b;
608
- queryDetector.dispose();
609
- slotManager.dispose();
610
- queryDetector.dispose();
611
- disposeOnTcfConsentChange();
612
- (_a = context.parameters) == null ? void 0 : _a.clear();
613
- logger.resetLogs();
614
- (_b = context.events) == null ? void 0 : _b.dispose();
615
- logger.info("Adhese instance disposed");
616
- runOnDispose();
617
- disposeOnInit();
618
- disposeOnDispose();
619
- scope.stop();
620
- }
621
- for (const plugin of mergedOptions.plugins)
622
- plugin(context);
623
- onInit(async () => {
624
- var _a;
625
- if ((slotManager.getAll().length ?? 0) > 0)
626
- await fetchAndRenderAllSlots().catch(logger.error);
627
- if (mergedOptions.findDomSlotsOnLoad)
628
- await findDomSlots2();
629
- if (mergedOptions.debug || window.location.search.includes("adhese_debug=true") || isPreviewMode())
630
- (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
631
- if (!scope.active)
632
- dispose();
625
+ const [response, previews] = await Promise.all([((_b = context.options.requestType) == null ? void 0 : _b.toUpperCase()) === "POST" ? requestWithPost(options) : requestWithGet(options), requestPreviews(context.options.account)]);
626
+ logger.debug("Received response", response);
627
+ if (!response.ok)
628
+ throw new Error(`Failed to request ad: ${response.status} ${response.statusText}`);
629
+ const result = parseResponse(await response.json());
630
+ logger.debug("Parsed ad", result);
631
+ if (previews.length > 0)
632
+ logger.info(`Found ${previews.length} ${previews.length === 1 ? "preview" : "previews"}. Replacing ads in response with preview items`, previews);
633
+ const matchedPreviews = previews.map(({ slotName, ...preview }) => {
634
+ const partnerAd = result.find((ad) => ad.libId === preview.libId);
635
+ return {
636
+ slotName: `${(partnerAd == null ? void 0 : partnerAd.slotName) ?? slotName}`,
637
+ ...preview
638
+ };
633
639
  });
634
- runOnInit();
635
- return {
636
- ...mergedOptions,
637
- ...slotManager,
638
- parameters: context.parameters,
639
- events: context.events,
640
- getLocation,
641
- setLocation,
642
- getConsent,
643
- setConsent,
644
- addSlot,
645
- findDomSlots: findDomSlots2,
646
- dispose,
647
- toggleDebug,
648
- context
649
- };
650
- });
640
+ if (matchedPreviews.length > 0)
641
+ (_c = context.events) == null ? void 0 : _c.previewReceived.dispatch(matchedPreviews);
642
+ const mergedResult = await runOnResponse([
643
+ ...result.filter((ad) => !previews.some((preview) => preview.libId === ad.libId)),
644
+ ...matchedPreviews
645
+ ]);
646
+ if (mergedResult.length === 0)
647
+ throw new Error("No ads found");
648
+ (_d = context.events) == null ? void 0 : _d.responseReceived.dispatch(mergedResult);
649
+ return mergedResult;
650
+ } catch (error) {
651
+ logger.error(String(error));
652
+ (_e = context.events) == null ? void 0 : _e.requestError.dispatch(error);
653
+ throw error;
654
+ }
651
655
  }
652
- function addTrackingPixel(url) {
653
- const img = document.createElement("img");
654
- img.src = url.toString();
655
- img.style.height = "1px";
656
- img.style.width = "1px";
657
- img.style.margin = "-1px";
658
- img.style.border = "0";
659
- img.style.position = "absolute";
660
- img.style.top = "0";
661
- return document.body.appendChild(img);
656
+ async function requestAd({
657
+ slot,
658
+ ...options
659
+ }) {
660
+ const [ad] = await requestAds({
661
+ slots: [slot],
662
+ ...options
663
+ });
664
+ return ad;
662
665
  }
663
- function useViewabilityObserver({ context, ad, name, element }) {
666
+ const [runOnRender, onRender] = createHook("onRender");
667
+ function useViewabilityObserver({ context, ad, name: name2, element }) {
664
668
  let timeoutId = null;
665
669
  const {
666
670
  threshold,
@@ -683,7 +687,7 @@ function useViewabilityObserver({ context, ad, name, element }) {
683
687
  timeoutId = null;
684
688
  if ((_a = ad.value) == null ? void 0 : _a.viewableImpressionCounter) {
685
689
  trackingPixel.value = addTrackingPixel(ad.value.viewableImpressionCounter);
686
- logger.debug(`Viewability tracking pixel fired for ${name.value}`);
690
+ logger.debug(`Viewability tracking pixel fired for ${name2.value}`);
687
691
  (_c = context.events) == null ? void 0 : _c.changeSlots.dispatch(Array.from(((_b = context.getAll) == null ? void 0 : _b.call(context)) ?? []));
688
692
  }
689
693
  }, duration);
@@ -773,8 +777,8 @@ function createSlot(options) {
773
777
  }
774
778
  const ad = ref(null);
775
779
  const originalAd = ref(ad.value);
776
- const name = computed(() => generateName(context.location, format.value, slot));
777
- watch(name, async (newName, oldName) => {
780
+ const name2 = computed(() => generateName(context.location, format.value, slot));
781
+ watch(name2, async (newName, oldName) => {
778
782
  var _a;
779
783
  if (newName === oldName)
780
784
  return;
@@ -820,7 +824,7 @@ function createSlot(options) {
820
824
  ] = useViewabilityObserver({
821
825
  context,
822
826
  ad,
823
- name,
827
+ name: name2,
824
828
  element
825
829
  });
826
830
  const impressionTrackingPixelElement = ref(null);
@@ -828,7 +832,7 @@ function createSlot(options) {
828
832
  async function requestAd$1() {
829
833
  const response = await requestAd({
830
834
  slot: {
831
- name: name.value,
835
+ name: name2.value,
832
836
  parameters
833
837
  },
834
838
  context
@@ -840,10 +844,10 @@ function createSlot(options) {
840
844
  var _a, _b;
841
845
  await waitForDomLoad();
842
846
  await waitOnInit;
843
- const renderAd = adToRender ?? ad.value ?? await requestAd$1();
844
- if (originalAd.value) {
845
- ad.value = ((_a = options.onBeforeRender) == null ? void 0 : _a.call(options, adToRender ?? originalAd.value)) ?? renderAd;
846
- }
847
+ let renderAd = adToRender ?? ad.value ?? originalAd.value ?? await requestAd$1();
848
+ if (renderAd)
849
+ renderAd = ((_a = options.onBeforeRender) == null ? void 0 : _a.call(options, renderAd)) ?? renderAd;
850
+ renderAd = await runOnRender(renderAd);
847
851
  if (!element.value) {
848
852
  const error = `Could not create slot for format ${format.value}. No element found.`;
849
853
  logger.error(error, options);
@@ -851,15 +855,15 @@ function createSlot(options) {
851
855
  }
852
856
  if (context.debug)
853
857
  element.value.style.position = "relative";
854
- if (context.safeFrame && ad.value && renderMode === "iframe") {
855
- const position = context.safeFrame.addPosition(ad.value, element.value);
858
+ if (context.safeFrame && renderAd && renderMode === "iframe") {
859
+ const position = context.safeFrame.addPosition(renderAd, element.value);
856
860
  await context.safeFrame.render(position);
857
861
  } else {
858
862
  renderFunctions[renderMode](renderAd, element.value);
859
863
  }
860
864
  if (renderAd.impressionCounter && !impressionTrackingPixelElement.value) {
861
865
  impressionTrackingPixelElement.value = addTrackingPixel(renderAd.impressionCounter);
862
- logger.debug(`Impression tracking pixel fired for ${name.value}`);
866
+ logger.debug(`Impression tracking pixel fired for ${name2.value}`);
863
867
  }
864
868
  logger.debug("Slot rendered", {
865
869
  renderedElement: element,
@@ -891,323 +895,378 @@ function createSlot(options) {
891
895
  scope.stop();
892
896
  }
893
897
  return {
894
- location: context.location,
895
- lazyLoading: options.lazyLoading ?? false,
896
- slot,
897
- parameters,
898
- format,
899
- name,
900
- ad,
901
- isViewabilityTracked,
902
- isImpressionTracked,
903
- render,
904
- getElement,
898
+ location: context.location,
899
+ lazyLoading: options.lazyLoading ?? false,
900
+ slot,
901
+ parameters,
902
+ format,
903
+ name: name2,
904
+ ad,
905
+ isViewabilityTracked,
906
+ isImpressionTracked,
907
+ render,
908
+ getElement,
909
+ dispose
910
+ };
911
+ });
912
+ }
913
+ function useDomLoaded() {
914
+ const isDomLoaded = ref(false);
915
+ onInit(async () => {
916
+ await waitForDomLoad();
917
+ isDomLoaded.value = true;
918
+ });
919
+ return isDomLoaded;
920
+ }
921
+ async function findDomSlots(context) {
922
+ await waitForDomLoad();
923
+ return Array.from(document.querySelectorAll(".adunit")).filter((element) => {
924
+ var _a;
925
+ if (!element.dataset.format)
926
+ return false;
927
+ const name2 = generateName(
928
+ context.location,
929
+ element.dataset.format,
930
+ element.dataset.slot
931
+ );
932
+ return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === name2));
933
+ }).map((element) => createSlot({
934
+ format: element.dataset.format,
935
+ containingElement: element,
936
+ slot: element.dataset.slot,
937
+ context
938
+ })).filter((slot) => {
939
+ var _a;
940
+ return !((_a = context.getAll) == null ? void 0 : _a.call(context).some((activeSlot) => activeSlot.name.value === slot.name.value));
941
+ });
942
+ }
943
+ function createSlotManager({
944
+ initialSlots = [],
945
+ context
946
+ }) {
947
+ const scope = effectScope();
948
+ return scope.run(() => {
949
+ const slots = shallowReactive(/* @__PURE__ */ new Map());
950
+ watchEffect(() => {
951
+ var _a;
952
+ (_a = context.events) == null ? void 0 : _a.changeSlots.dispatch(Array.from(slots.values()));
953
+ });
954
+ function getAll() {
955
+ return Array.from(slots).map(([, slot]) => slot);
956
+ }
957
+ function add(options) {
958
+ var _a;
959
+ const slot = createSlot({
960
+ ...options,
961
+ onDispose: onDispose2,
962
+ context
963
+ });
964
+ function onDispose2() {
965
+ var _a2;
966
+ slots.delete(slot.name.value);
967
+ logger.debug("Slot removed", {
968
+ slot,
969
+ slots: Array.from(slots)
970
+ });
971
+ (_a2 = context.events) == null ? void 0 : _a2.removeSlot.dispatch(slot);
972
+ }
973
+ slots.set(slot.name.value, slot);
974
+ watch(slot.name, (newName, previousName) => {
975
+ slots.set(newName, slot);
976
+ slots.delete(previousName);
977
+ });
978
+ logger.debug("Slot added", {
979
+ slot,
980
+ slots: Array.from(slots.values())
981
+ });
982
+ (_a = context.events) == null ? void 0 : _a.addSlot.dispatch(slot);
983
+ return slot;
984
+ }
985
+ async function findDomSlots$1() {
986
+ const domSlots = await findDomSlots(
987
+ context
988
+ );
989
+ for (const slot of domSlots)
990
+ slots.set(slot.name.value, slot);
991
+ return domSlots;
992
+ }
993
+ function get(name2) {
994
+ return slots.get(name2);
995
+ }
996
+ function dispose() {
997
+ for (const slot of slots.values())
998
+ slot.dispose();
999
+ slots.clear();
1000
+ scope.stop();
1001
+ }
1002
+ for (const options of initialSlots) {
1003
+ add({
1004
+ ...options,
1005
+ lazyLoading: false
1006
+ });
1007
+ }
1008
+ return {
1009
+ getAll,
1010
+ add,
1011
+ findDomSlots: findDomSlots$1,
1012
+ get,
905
1013
  dispose
906
1014
  };
907
1015
  });
908
1016
  }
909
- function useDomLoaded() {
910
- const isDomLoaded = ref(false);
911
- onInit(async () => {
912
- await waitForDomLoad();
913
- isDomLoaded.value = true;
914
- });
915
- return isDomLoaded;
916
- }
917
- const numberLike = union([coerce.string().regex(/^\d+$/), literal("")]).transform((value) => value === "" ? void 0 : Number(value));
918
- const booleanLike = union([coerce.boolean(), literal("")]);
919
- const urlLike = union([coerce.string(), literal("")]).transform((value) => {
920
- try {
921
- return new URL(value);
922
- } catch {
923
- return void 0;
924
- }
925
- });
926
- const dateLike = union([coerce.string(), literal("")]).transform((value) => {
927
- if (value === "")
928
- return void 0;
929
- const date = new Date(numberLike.safeParse(value).success ? Number(value) : value);
930
- if (Number.isNaN(date.getTime()))
931
- return void 0;
932
- return date;
933
- });
934
- const cssValueLike = union([coerce.string(), literal(""), number()]).transform((value) => {
935
- if (value === "" || value === 0 || value === "0")
936
- return void 0;
937
- if (numberLike.parse(value))
938
- return `${numberLike.parse(value)}px`;
939
- return String(value);
940
- });
941
- const isJson = string().transform((value, { addIssue }) => {
942
- try {
943
- return JSON.parse(value.replaceAll("'", '"'));
944
- } catch (error) {
945
- addIssue({
946
- code: ZodIssueCode.custom,
947
- message: `Invalid JSON: ${error.message}`
948
- });
949
- return NEVER;
950
- }
951
- });
952
- const isHtmlString = string().transform((value, { addIssue }) => {
1017
+ function onTcfConsentChange(callback) {
953
1018
  var _a;
954
- const htmlParser = new DOMParser();
955
- try {
956
- const html = htmlParser.parseFromString(value, "text/html");
957
- if (((_a = html.body) == null ? void 0 : _a.children.length) === 0)
958
- throw new Error("Invalid HTML");
959
- return value;
960
- } catch (error) {
961
- addIssue({
962
- code: ZodIssueCode.custom,
963
- message: error.message
964
- });
965
- return NEVER;
966
- }
967
- });
968
- const isJsonOrHtmlString = union([isJson, isHtmlString]);
969
- const isJsonOrHtmlOptionalString = union([coerce.string(), isJsonOrHtmlString]).transform((value) => {
970
- if (value === "")
971
- return void 0;
972
- return value;
973
- }).optional();
974
- const baseSchema = object({
975
- adDuration: numberLike.optional(),
976
- adFormat: string().optional(),
977
- adType: string(),
978
- additionalCreativeTracker: urlLike.optional(),
979
- additionalViewableTracker: string().optional(),
980
- adspaceEnd: dateLike.optional(),
981
- adspaceId: string().optional(),
982
- adspaceKey: string().optional(),
983
- adspaceStart: dateLike.optional(),
984
- advertiserId: string().optional(),
985
- altText: string().optional(),
986
- auctionable: booleanLike.optional(),
987
- body: isJsonOrHtmlOptionalString,
988
- clickTag: urlLike.optional(),
989
- comment: string().optional(),
990
- creativeName: string().optional(),
991
- deliveryGroupId: string().optional(),
992
- deliveryMultiples: string().optional(),
993
- ext: string().optional(),
994
- extension: object({
995
- mediaType: string(),
996
- prebid: unknown().optional()
997
- }).optional(),
998
- height: numberLike.optional(),
999
- id: string(),
1000
- impressionCounter: urlLike.optional(),
1001
- libId: string().optional(),
1002
- orderId: string().optional(),
1003
- orderName: string().optional(),
1004
- orderProperty: string().optional(),
1005
- origin: union([literal("JERLICIA"), literal("DALE")]),
1006
- originData: unknown().optional(),
1007
- originInstance: string().optional(),
1008
- poolPath: urlLike.optional(),
1009
- preview: booleanLike.optional(),
1010
- priority: numberLike.optional(),
1011
- sfSrc: urlLike.optional(),
1012
- share: string().optional(),
1013
- // eslint-disable-next-line ts/naming-convention
1014
- slotID: string(),
1015
- slotName: string(),
1016
- swfSrc: urlLike.optional(),
1017
- tag: isJsonOrHtmlOptionalString,
1018
- tagUrl: urlLike.optional(),
1019
- timeStamp: dateLike.optional(),
1020
- trackedImpressionCounter: urlLike.optional(),
1021
- tracker: urlLike.optional(),
1022
- trackingUrl: urlLike.optional(),
1023
- url: urlLike.optional(),
1024
- viewableImpressionCounter: urlLike.optional(),
1025
- width: numberLike.optional(),
1026
- widthLarge: cssValueLike.optional()
1027
- });
1028
- const jerliciaSchema = object({
1029
- origin: literal("JERLICIA"),
1030
- tag: isJsonOrHtmlString
1031
- }).passthrough();
1032
- const daleSchema = object({
1033
- origin: literal("DALE"),
1034
- body: isJsonOrHtmlString
1035
- }).passthrough().transform(({ body, ...data }) => ({
1036
- ...data,
1037
- tag: body
1038
- }));
1039
- const adResponseSchema = baseSchema.extend({
1040
- additionalCreatives: lazy(() => union([adResponseSchema.array(), string()]).optional())
1041
- });
1042
- const adSchema = adResponseSchema.transform(({
1043
- additionalCreatives,
1044
- ...data
1045
- }) => {
1046
- const filteredValue = Object.fromEntries(
1047
- Object.entries(data).filter(([, value]) => Boolean(value) && JSON.stringify(value) !== "{}" && JSON.stringify(value) !== "[]")
1048
- );
1049
- return {
1050
- ...filteredValue,
1051
- additionalCreatives: Array.isArray(additionalCreatives) ? additionalCreatives.map((creative) => adSchema.parse(creative)) : additionalCreatives
1052
- };
1053
- });
1054
- function parseResponse(response) {
1055
- const schemaMap = {
1056
- /* eslint-disable ts/naming-convention */
1057
- JERLICIA: jerliciaSchema,
1058
- DALE: daleSchema
1059
- /* eslint-enable ts/naming-convention */
1019
+ (_a = window.__tcfapi) == null ? void 0 : _a.call(window, "addEventListener", 2, callback);
1020
+ return () => {
1021
+ var _a2;
1022
+ return (_a2 = window.__tcfapi) == null ? void 0 : _a2.call(window, "removeEventListener", 2, callback);
1060
1023
  };
1061
- const preParsed = adResponseSchema.array().parse(response);
1062
- return preParsed.map((item) => {
1063
- const schema = schemaMap[item.origin];
1064
- if (!schema)
1065
- return adSchema.parse(item);
1066
- return schema.parse(item);
1024
+ }
1025
+ function createParameters(options, queryDetector) {
1026
+ const parameters = /* @__PURE__ */ new Map();
1027
+ if (options.logReferrer)
1028
+ parameters.set("re", btoa(document.referrer));
1029
+ if (options.logUrl)
1030
+ parameters.set("ur", btoa(window.location.href));
1031
+ for (const [key, value] of Object.entries({
1032
+ ...options.parameters ?? {},
1033
+ tl: options.consent ? "all" : "none",
1034
+ dt: queryDetector.getQuery(),
1035
+ br: queryDetector.getQuery(),
1036
+ rn: Math.round(Math.random() * 1e4).toString()
1037
+ }))
1038
+ parameters.set(key, value);
1039
+ return parameters;
1040
+ }
1041
+ function setupLogging(mergedOptions) {
1042
+ if (mergedOptions.debug || window.location.search.includes("adhese_debug=true")) {
1043
+ logger.setMinLogLevelThreshold("debug");
1044
+ logger.debug("Debug logging enabled");
1045
+ }
1046
+ logger.debug("Created Adhese SDK instance", {
1047
+ mergedOptions
1067
1048
  });
1068
1049
  }
1069
- async function requestPreviews(account) {
1070
- const previewObjects = getPreviewObjects();
1071
- const list = (await Promise.allSettled(previewObjects.filter((previewObject) => "adhesePreviewCreativeId" in previewObject).map(async (previewObject) => {
1072
- const endpoint = new URL(`https://${account}-preview.adhese.org/creatives/preview/json/tag.do`);
1073
- endpoint.searchParams.set(
1074
- "id",
1075
- previewObject.adhesePreviewCreativeId
1076
- );
1077
- const response = await fetch(endpoint.href, {
1078
- method: "GET",
1079
- headers: {
1080
- accept: "application/json"
1081
- }
1050
+ function isPreviewMode() {
1051
+ return window.location.search.includes("adhesePreviewCreativeId");
1052
+ }
1053
+ let isDisposed = false;
1054
+ const [runOnDispose, onDispose] = createHook("onDispose", {
1055
+ onRun(callbacks) {
1056
+ isDisposed = true;
1057
+ callbacks == null ? void 0 : callbacks.clear();
1058
+ },
1059
+ onAdd() {
1060
+ if (isDisposed)
1061
+ runOnDispose().catch(console.error);
1062
+ }
1063
+ });
1064
+ function createAdhese(options) {
1065
+ const scope = effectScope();
1066
+ return scope.run(() => {
1067
+ const mergedOptions = {
1068
+ host: `https://ads-${options.account}.adhese.com`,
1069
+ poolHost: `https://pool-${options.account}.adhese.com`,
1070
+ location: "homepage",
1071
+ requestType: "POST",
1072
+ debug: false,
1073
+ initialSlots: [],
1074
+ findDomSlotsOnLoad: false,
1075
+ consent: false,
1076
+ logReferrer: true,
1077
+ logUrl: true,
1078
+ safeFrame: false,
1079
+ eagerRendering: false,
1080
+ viewabilityTracking: true,
1081
+ plugins: [],
1082
+ ...options
1083
+ };
1084
+ setupLogging(mergedOptions);
1085
+ const context = reactive({
1086
+ location: mergedOptions.location,
1087
+ consent: mergedOptions.consent,
1088
+ debug: mergedOptions.debug,
1089
+ getAll,
1090
+ get,
1091
+ options: mergedOptions,
1092
+ logger,
1093
+ addSlot
1082
1094
  });
1083
- if (!response.ok)
1084
- return Promise.reject(new Error(`Failed to request preview ad with ID: ${previewObject.adhesePreviewCreativeId}`));
1085
- return await response.json();
1086
- }))).filter((response) => {
1087
- if (response.status === "rejected") {
1088
- logger.error(response.reason);
1089
- return false;
1095
+ for (const [index, plugin] of mergedOptions.plugins.entries()) {
1096
+ plugin(context, {
1097
+ index,
1098
+ version: packageJson.version,
1099
+ onInit,
1100
+ onDispose,
1101
+ onRender,
1102
+ onRequest,
1103
+ onResponse
1104
+ });
1090
1105
  }
1091
- return response.status === "fulfilled";
1092
- }).map((response) => response.value.map((item) => ({
1093
- ...item,
1094
- preview: true
1095
- })));
1096
- return adSchema.array().parse(list.flat());
1097
- }
1098
- function getPreviewObjects() {
1099
- const currentUrl = new URL(window.location.href);
1100
- const previewObjects = [];
1101
- let currentObject = {};
1102
- for (const [key, value] of currentUrl.searchParams.entries()) {
1103
- if (key === "adhesePreviewCreativeId" && Object.keys(currentObject).length > 0) {
1104
- previewObjects.push(currentObject);
1105
- currentObject = {};
1106
+ context.events = createEventManager();
1107
+ context.safeFrame = options.safeFrame ? createSafeFrame({
1108
+ renderFile: `${mergedOptions.poolHost}/sf/r.html`,
1109
+ context
1110
+ }) : void 0;
1111
+ function getLocation() {
1112
+ return context.location;
1106
1113
  }
1107
- currentObject[key] = value;
1108
- }
1109
- if (Object.keys(currentObject).length > 0)
1110
- previewObjects.push(currentObject);
1111
- return previewObjects;
1112
- }
1113
- function requestWithPost({
1114
- context: { options: { host }, parameters },
1115
- ...options
1116
- }) {
1117
- const payload = {
1118
- ...options,
1119
- slots: options.slots.map((slot) => ({
1120
- slotname: toValue(slot.name),
1121
- parameters: parseParameters(slot.parameters)
1122
- })),
1123
- parameters: parameters && parseParameters(parameters)
1124
- };
1125
- return fetch(`${new URL(host).href}json`, {
1126
- method: "POST",
1127
- body: JSON.stringify(payload),
1128
- headers: {
1129
- // eslint-disable-next-line ts/naming-convention
1130
- "Content-Type": "application/json"
1114
+ function setLocation(newLocation) {
1115
+ var _a;
1116
+ context.location = newLocation;
1117
+ (_a = context.events) == null ? void 0 : _a.locationChange.dispatch(newLocation);
1131
1118
  }
1132
- });
1133
- }
1134
- async function requestWithGet({ context, slots }) {
1135
- return fetch(new URL(`${context.options.host}/json/sl${slots.map((slot) => toValue(slot.name)).join("/sl")}`), {
1136
- method: "GET",
1137
- headers: {
1138
- // eslint-disable-next-line ts/naming-convention
1139
- "Content-Type": "application/json"
1119
+ const queryDetector = createQueryDetector({
1120
+ onChange: onQueryChange,
1121
+ queries: mergedOptions.queries
1122
+ });
1123
+ context.parameters = createParameters(mergedOptions, queryDetector);
1124
+ watch(
1125
+ context.parameters,
1126
+ onParametersChange,
1127
+ {
1128
+ deep: true,
1129
+ immediate: true
1130
+ }
1131
+ );
1132
+ function onParametersChange() {
1133
+ var _a;
1134
+ if (context.parameters)
1135
+ (_a = context.events) == null ? void 0 : _a.parametersChange.dispatch(context.parameters);
1140
1136
  }
1141
- });
1142
- }
1143
- function parseParameters(parameters) {
1144
- return Object.fromEntries(Array.from(parameters.entries()).filter(([key]) => {
1145
- if (key.length === 2)
1146
- return true;
1147
- logger.warn(`Invalid parameter key: ${key}. Key should be exactly 2 characters long. Key will be ignored.`);
1148
- return false;
1149
- }).map(([key, value]) => {
1150
- if (typeof value === "string")
1151
- return [key, filterSpecialChars(value)];
1152
- return [key, value.map(filterSpecialChars)];
1153
- }));
1154
- }
1155
- function filterSpecialChars(value) {
1156
- const specialRegex = /[^\p{L}\p{N}_]/gu;
1157
- return value.replaceAll(specialRegex, "_");
1158
- }
1159
- async function requestAds(options) {
1160
- var _a, _b, _c, _d, _e;
1161
- const { context } = options;
1162
- try {
1163
- (_a = context.events) == null ? void 0 : _a.requestAd.dispatch({
1164
- ...options,
1137
+ async function onQueryChange() {
1138
+ var _a, _b;
1139
+ const query = queryDetector.getQuery();
1140
+ (_a = context.parameters) == null ? void 0 : _a.set("dt", query);
1141
+ (_b = context.parameters) == null ? void 0 : _b.set("br", query);
1142
+ await fetchAndRenderAllSlots();
1143
+ }
1144
+ function getConsent() {
1145
+ return context.consent;
1146
+ }
1147
+ function setConsent(newConsent) {
1148
+ var _a, _b;
1149
+ (_a = context.parameters) == null ? void 0 : _a.set("tl", newConsent ? "all" : "none");
1150
+ context.consent = newConsent;
1151
+ (_b = context.events) == null ? void 0 : _b.consentChange.dispatch(newConsent);
1152
+ }
1153
+ const slotManager = createSlotManager({
1154
+ initialSlots: mergedOptions.initialSlots,
1165
1155
  context
1166
1156
  });
1167
- const [response, previews] = await Promise.all([((_b = context.options.requestType) == null ? void 0 : _b.toUpperCase()) === "POST" ? requestWithPost(options) : requestWithGet(options), requestPreviews(context.options.account)]);
1168
- logger.debug("Received response", response);
1169
- if (!response.ok)
1170
- throw new Error(`Failed to request ad: ${response.status} ${response.statusText}`);
1171
- const result = parseResponse(await response.json());
1172
- logger.debug("Parsed ad", result);
1173
- if (previews.length > 0)
1174
- logger.info(`Found ${previews.length} ${previews.length === 1 ? "preview" : "previews"}. Replacing ads in response with preview items`, previews);
1175
- const matchedPreviews = previews.map(({ slotName, ...preview }) => {
1176
- const partnerAd = result.find((ad) => ad.libId === preview.libId);
1177
- return {
1178
- slotName: `${(partnerAd == null ? void 0 : partnerAd.slotName) ?? slotName}`,
1179
- ...preview
1180
- };
1157
+ function getAll() {
1158
+ return slotManager.getAll() ?? [];
1159
+ }
1160
+ function get(name2) {
1161
+ return slotManager.get(name2);
1162
+ }
1163
+ function addSlot(slotOptions) {
1164
+ if (!slotManager)
1165
+ throw new Error("Slot manager not initialized");
1166
+ return slotManager.add(slotOptions);
1167
+ }
1168
+ async function findDomSlots2() {
1169
+ const domSlots = (await slotManager.findDomSlots() ?? []).filter((slot) => !slot.lazyLoading);
1170
+ if (domSlots.length <= 0)
1171
+ return [];
1172
+ const ads = await requestAds({
1173
+ slots: domSlots,
1174
+ context
1175
+ });
1176
+ for (const ad of ads) {
1177
+ const slot = slotManager.get(ad.slotName);
1178
+ if (slot)
1179
+ slot.ad.value = ad;
1180
+ }
1181
+ return domSlots;
1182
+ }
1183
+ async function toggleDebug() {
1184
+ var _a, _b;
1185
+ context.debug = !context.debug;
1186
+ if (context.debug) {
1187
+ logger.setMinLogLevelThreshold("debug");
1188
+ logger.debug("Debug mode enabled");
1189
+ (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
1190
+ } else {
1191
+ logger.debug("Debug mode disabled");
1192
+ logger.setMinLogLevelThreshold("info");
1193
+ (_b = context.events) == null ? void 0 : _b.debugChange.dispatch(false);
1194
+ }
1195
+ return context.debug;
1196
+ }
1197
+ async function fetchAndRenderAllSlots() {
1198
+ const slots = (slotManager.getAll() ?? []).filter((slot) => !slot.lazyLoading);
1199
+ if (slots.length === 0)
1200
+ return;
1201
+ const ads = await requestAds({
1202
+ slots,
1203
+ context
1204
+ });
1205
+ for (const ad of ads) {
1206
+ const slot = slotManager.get(ad.slotName);
1207
+ if (slot)
1208
+ slot.ad.value = ad;
1209
+ }
1210
+ }
1211
+ const disposeOnTcfConsentChange = onTcfConsentChange(async (data) => {
1212
+ var _a, _b;
1213
+ if (!data.tcString)
1214
+ return;
1215
+ logger.debug("TCF v2 consent data received", {
1216
+ data
1217
+ });
1218
+ (_a = context.parameters) == null ? void 0 : _a.set("xt", data.tcString);
1219
+ (_b = context.parameters) == null ? void 0 : _b.delete("tl");
1220
+ await fetchAndRenderAllSlots();
1181
1221
  });
1182
- if (matchedPreviews.length > 0)
1183
- (_c = context.events) == null ? void 0 : _c.previewReceived.dispatch(matchedPreviews);
1184
- const mergedResult = [
1185
- ...result.filter((ad) => !previews.some((preview) => preview.libId === ad.libId)),
1186
- ...matchedPreviews
1187
- ];
1188
- if (mergedResult.length === 0)
1189
- throw new Error("No ads found");
1190
- (_d = context.events) == null ? void 0 : _d.responseReceived.dispatch(mergedResult);
1191
- return mergedResult;
1192
- } catch (error) {
1193
- logger.error(String(error));
1194
- (_e = context.events) == null ? void 0 : _e.requestError.dispatch(error);
1195
- throw error;
1196
- }
1197
- }
1198
- async function requestAd({
1199
- slot,
1200
- ...options
1201
- }) {
1202
- const [ad] = await requestAds({
1203
- slots: [slot],
1204
- ...options
1222
+ function dispose() {
1223
+ var _a, _b;
1224
+ queryDetector.dispose();
1225
+ slotManager.dispose();
1226
+ queryDetector.dispose();
1227
+ disposeOnTcfConsentChange();
1228
+ (_a = context.parameters) == null ? void 0 : _a.clear();
1229
+ logger.resetLogs();
1230
+ (_b = context.events) == null ? void 0 : _b.dispose();
1231
+ logger.info("Adhese instance disposed");
1232
+ runOnDispose().catch(logger.error);
1233
+ clearAllHooks();
1234
+ scope.stop();
1235
+ }
1236
+ onInit(async () => {
1237
+ var _a;
1238
+ if ((slotManager.getAll().length ?? 0) > 0)
1239
+ await fetchAndRenderAllSlots().catch(logger.error);
1240
+ if (mergedOptions.findDomSlotsOnLoad)
1241
+ await findDomSlots2();
1242
+ if (mergedOptions.debug || window.location.search.includes("adhese_debug=true") || isPreviewMode())
1243
+ (_a = context.events) == null ? void 0 : _a.debugChange.dispatch(true);
1244
+ if (!scope.active)
1245
+ dispose();
1246
+ });
1247
+ runOnInit().catch(logger.error);
1248
+ return {
1249
+ parameters: context.parameters,
1250
+ events: context.events,
1251
+ getLocation,
1252
+ setLocation,
1253
+ getConsent,
1254
+ setConsent,
1255
+ addSlot,
1256
+ findDomSlots: findDomSlots2,
1257
+ dispose,
1258
+ toggleDebug,
1259
+ get: slotManager.get,
1260
+ getAll: slotManager.getAll,
1261
+ context,
1262
+ options: mergedOptions
1263
+ };
1205
1264
  });
1206
- return ad;
1207
1265
  }
1208
1266
  export {
1209
1267
  createAdhese,
1210
- onDispose,
1211
- onInit
1268
+ logger,
1269
+ requestAd,
1270
+ requestAds
1212
1271
  };
1213
1272
  //# sourceMappingURL=index.js.map