@adhese/sdk-lite 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @adhese/sdk-lite
2
2
 
3
+ ## 1.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 431b736: When not passing an element to a lite slot the parent element of the script where it's called is selected
8
+
9
+ ### Patch Changes
10
+
11
+ - 431b736: Improve documentation
12
+
3
13
  ## 1.0.0
4
14
 
5
15
  ### Major Changes
package/README.md CHANGED
@@ -1,20 +1,23 @@
1
1
  # @adhese/sdk-lite
2
- Lightweight Adhese SDK that can only render a single slot
3
-
4
- ## Installation
5
- ```bash
6
- npm install @adhese/sdk-lite
7
- ```
2
+ The Adhese SDK Lite is a lightweight version of the Adhese SDK. It is a smaller package that can only render a single
3
+ slot. This package is most useful for use in places like Google Tag Manager.
8
4
 
9
5
  ## Usage
10
- ```js
11
- import {createSlot} from '@adhese/sdk-lite';
6
+ To create a slot, you need to call the `createSlot` function with an object containing the account, location, and format.
7
+
8
+ The slot will be created alongside the script tag in which the function is called.
12
9
 
13
- const slot = createSlot({
14
- account: 'demo',
15
- location: 'location',
16
- format: 'format',
17
- containingElement: document.getElementById('container'),
18
- debug: true,
19
- });
10
+ ```html
11
+ <body>
12
+ <div>
13
+ <script src="adheseLite.js"></script>
14
+ <script>
15
+ AdheseLite.createSlot({
16
+ account: 'your-account-id',
17
+ location: 'location-name',
18
+ format: 'format-name',
19
+ })
20
+ </script>
21
+ </div>
22
+ </body>
20
23
  ```
@@ -2,12 +2,13 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const sdkShared = require("@adhese/sdk-shared");
4
4
  const name = "@adhese/sdk-lite";
5
- const version = "1.0.0";
5
+ const version = "1.1.0";
6
6
  const renderFunctions = {
7
7
  iframe: sdkShared.renderIframe,
8
8
  inline: sdkShared.renderInline
9
9
  };
10
10
  function createSlot(options) {
11
+ var _a;
11
12
  const logger = sdkShared.createLogger({
12
13
  scope: `${name}@${version}`,
13
14
  minLogLevelThreshold: options.debug ? "debug" : "info"
@@ -32,13 +33,23 @@ function createSlot(options) {
32
33
  ...consentParameters
33
34
  };
34
35
  let isImpressionTracked = false;
36
+ const element = options.containingElement ?? document.createElement("div");
37
+ if (!options.containingElement) {
38
+ element.id = `${name$1}:${sdkShared.uniqueId(3)}`;
39
+ element.classList.add("adunit");
40
+ element.dataset.format = options.format;
41
+ element.dataset.slot = options.format;
42
+ element.dataset.location = options.location;
43
+ (_a = document.currentScript) == null ? void 0 : _a.insertAdjacentElement("beforebegin", element);
44
+ }
35
45
  const context = {
36
46
  options,
37
47
  name: name$1,
38
48
  logger,
39
49
  parameters,
50
+ element,
40
51
  async request() {
41
- var _a;
52
+ var _a2;
42
53
  logger.debug(`Requesting ad for slot ${name$1}`, this);
43
54
  const response = await fetch(`${options.host ?? `https://ads-${options.account}.adhese.com`}/json`, {
44
55
  method: "POST",
@@ -58,21 +69,21 @@ function createSlot(options) {
58
69
  const [data] = await response.json();
59
70
  this.data = data;
60
71
  logger.debug(`Received ad data for slot ${name$1}`, this);
61
- (_a = options.onRequest) == null ? void 0 : _a.call(options, this);
72
+ (_a2 = options.onRequest) == null ? void 0 : _a2.call(options, this);
62
73
  return data;
63
74
  },
64
75
  async render() {
65
- var _a, _b;
76
+ var _a2, _b;
66
77
  const data = this.data ?? await this.request();
67
- options.containingElement.removeEventListener("click", onClick);
78
+ element.removeEventListener("click", onClick);
68
79
  if (!data) {
69
- (_a = options.onEmpty) == null ? void 0 : _a.call(options, this);
80
+ (_a2 = options.onEmpty) == null ? void 0 : _a2.call(options, this);
70
81
  logger.debug(`Slot ${name$1} is empty`, this);
71
- return options.containingElement;
82
+ return element;
72
83
  }
73
84
  if (typeof data.tag !== "string")
74
85
  throw new Error("Received invalid ad data, tag is not a string");
75
- renderFunctions[options.renderMode ?? "iframe"](data, options.containingElement);
86
+ renderFunctions[options.renderMode ?? "iframe"](data, element);
76
87
  (_b = options.onRender) == null ? void 0 : _b.call(options, this);
77
88
  logger.debug(`Rendered slot ${name$1}`, this);
78
89
  if (data.impressionCounter && !isImpressionTracked) {
@@ -80,22 +91,22 @@ function createSlot(options) {
80
91
  isImpressionTracked = true;
81
92
  }
82
93
  if (data.viewableImpressionCounter) {
83
- viewabilityTrackingIntersectionObserver.observe(options.containingElement);
94
+ viewabilityTrackingIntersectionObserver.observe(element);
84
95
  }
85
- options.containingElement.addEventListener("click", onClick);
86
- return options.containingElement;
96
+ element.addEventListener("click", onClick);
97
+ return element;
87
98
  },
88
99
  dispose() {
89
- var _a;
90
- (_a = options.onDispose) == null ? void 0 : _a.call(options, this);
91
- options.containingElement.innerHTML = "";
100
+ var _a2;
101
+ (_a2 = options.onDispose) == null ? void 0 : _a2.call(options, this);
102
+ element.innerHTML = "";
92
103
  for (const pixel of trackingPixels) {
93
104
  pixel.remove();
94
105
  }
95
106
  trackingPixels.clear();
96
107
  renderIntersectionObserver == null ? void 0 : renderIntersectionObserver.disconnect();
97
108
  viewabilityTrackingIntersectionObserver.disconnect();
98
- options.containingElement.removeEventListener("click", onClick);
109
+ element.removeEventListener("click", onClick);
99
110
  logger.debug(`Disposing slot ${name$1}`, this);
100
111
  logger.resetLogs();
101
112
  }
@@ -113,10 +124,10 @@ function createSlot(options) {
113
124
  const threshold = 0.2;
114
125
  if (ratio >= threshold && !timeoutId) {
115
126
  timeoutId = setTimeout(() => {
116
- var _a, _b;
127
+ var _a2, _b;
117
128
  timeoutId = null;
118
129
  viewabilityTrackingIntersectionObserver.disconnect();
119
- if ((_a = context.data) == null ? void 0 : _a.viewableImpressionCounter) {
130
+ if ((_a2 = context.data) == null ? void 0 : _a2.viewableImpressionCounter) {
120
131
  trackingPixels.add(sdkShared.addTrackingPixel((_b = context.data) == null ? void 0 : _b.viewableImpressionCounter));
121
132
  }
122
133
  }, 1e3);
@@ -131,7 +142,7 @@ function createSlot(options) {
131
142
  renderIntersectionObserver == null ? void 0 : renderIntersectionObserver.disconnect();
132
143
  }
133
144
  }
134
- renderIntersectionObserver.observe(options.containingElement);
145
+ renderIntersectionObserver.observe(element);
135
146
  return context;
136
147
  }
137
148
  exports.createSlot = createSlot;
@@ -1 +1 @@
1
- {"version":3,"file":"sdkLite.cjs","sources":["../../src/sdkLite.ts"],"sourcesContent":["import {\n type DefaultLogLevels,\n type Logger,\n type RenderOptions,\n addTrackingPixel,\n createLogger,\n generateName,\n renderIframe,\n renderInline,\n round,\n} from '@adhese/sdk-shared';\nimport { name as packageName, version } from '../package.json';\n\ntype RenderMode = 'iframe' | 'inline';\n\nexport type AdheseLiteSlotOptions = {\n containingElement: HTMLElement;\n format: string;\n account: string;\n location: string;\n host?: string;\n debug?: boolean;\n renderMode?: RenderMode;\n parameters?: Record<string, string | ReadonlyArray<string>>;\n slot?: string;\n consent?: boolean | string;\n clickTrackerUrl?: string | URL;\n onDispose?(slot: AdheseLiteSlot): void;\n onRender?(slot: AdheseLiteSlot): void;\n onRequest?(slot: AdheseLiteSlot): void;\n onEmpty?(slot: AdheseLiteSlot): void;\n};\n\nexport type AdheseLiteSlot = {\n options: AdheseLiteSlotOptions;\n name: string;\n data?: AdheseLiteAd;\n logger: Logger<DefaultLogLevels>;\n parameters: Record<string, string | ReadonlyArray<string>>;\n render(): Promise<HTMLElement>;\n request(): Promise<AdheseLiteAd | null>;\n dispose(): void;\n};\n\nexport type AdheseLiteAd = {\n tag: string;\n width?: string;\n height?: string;\n impressionCounter?: string;\n viewableImpressionCounter?: string;\n};\n\nconst renderFunctions: Record<RenderMode, (ad: RenderOptions, element: HTMLElement) => void> = {\n iframe: renderIframe,\n inline: renderInline,\n};\n\nexport function createSlot(options: AdheseLiteSlotOptions): AdheseLiteSlot {\n const logger = createLogger({\n scope: `${packageName}@${version}`,\n minLogLevelThreshold: options.debug ? 'debug' : 'info',\n });\n\n const name = generateName(options.location, options.format, options.slot);\n\n const trackingPixels = new Set<HTMLImageElement>();\n\n const renderIntersectionObserver = new IntersectionObserver(onRenderIntersection, {\n rootMargin: '200px',\n threshold: 0,\n });\n\n const viewabilityTrackingIntersectionObserver = new IntersectionObserver(onViewabilityIntersection, {\n rootMargin: '0px',\n threshold: Array.from({ length: 11 }, (_, i) => i * 0.1),\n });\n\n const consentParameters: {\n tl: 'all' | 'none';\n } | {\n xt: string;\n } = typeof options.consent === 'string'\n ? {\n xt: options.consent,\n }\n : {\n tl: options.consent ? 'all' : 'none',\n };\n\n const parameters = {\n ...options.parameters,\n ...consentParameters,\n };\n\n let isImpressionTracked = false;\n\n const context: AdheseLiteSlot = {\n options,\n name,\n logger,\n parameters,\n async request(): Promise<AdheseLiteAd | null> {\n logger.debug(`Requesting ad for slot ${name}`, this);\n\n const response = await fetch(`${options.host ?? `https://ads-${options.account}.adhese.com`}/json`, {\n method: 'POST',\n body: JSON.stringify({\n slots: [{\n slotname: name,\n }],\n parameters,\n }),\n headers: {\n // eslint-disable-next-line ts/naming-convention\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok)\n throw new Error(`Failed to request ad for slot ${name}. Status: ${response.status}`);\n\n const [data] = await response.json() as ReadonlyArray<AdheseLiteAd>;\n\n this.data = data;\n\n logger.debug(`Received ad data for slot ${name}`, this);\n options.onRequest?.(this);\n\n return data;\n },\n async render(): Promise<HTMLElement> {\n const data = this.data ?? (await this.request());\n options.containingElement.removeEventListener('click', onClick);\n\n if (!data) {\n options.onEmpty?.(this);\n logger.debug(`Slot ${name} is empty`, this);\n\n return options.containingElement;\n }\n\n if (typeof data.tag !== 'string')\n throw new Error('Received invalid ad data, tag is not a string');\n\n renderFunctions[options.renderMode ?? 'iframe'](data, options.containingElement);\n\n options.onRender?.(this);\n logger.debug(`Rendered slot ${name}`, this);\n\n if (data.impressionCounter && !isImpressionTracked) {\n trackingPixels.add(addTrackingPixel(data.impressionCounter));\n isImpressionTracked = true;\n }\n\n if (data.viewableImpressionCounter) {\n viewabilityTrackingIntersectionObserver.observe(options.containingElement);\n }\n\n options.containingElement.addEventListener('click', onClick);\n\n return options.containingElement;\n },\n dispose(): void {\n options.onDispose?.(this);\n\n options.containingElement.innerHTML = '';\n\n for (const pixel of trackingPixels) {\n pixel.remove();\n }\n trackingPixels.clear();\n\n renderIntersectionObserver?.disconnect();\n viewabilityTrackingIntersectionObserver.disconnect();\n\n options.containingElement.removeEventListener('click', onClick);\n\n logger.debug(`Disposing slot ${name}`, this);\n\n logger.resetLogs();\n },\n };\n\n let isClickTracked = false;\n function onClick(): void {\n if (options.clickTrackerUrl && !isClickTracked) {\n trackingPixels.add(addTrackingPixel(options.clickTrackerUrl));\n isClickTracked = true;\n }\n }\n\n let timeoutId: number | null = null;\n function onViewabilityIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n const ratio = round(entry.intersectionRatio, 1);\n const threshold = 0.2;\n\n if (ratio >= threshold && !timeoutId) {\n // @ts-expect-error The is misfiring to the Node type\n timeoutId = setTimeout(() => {\n timeoutId = null;\n viewabilityTrackingIntersectionObserver.disconnect();\n\n if (context.data?.viewableImpressionCounter) {\n trackingPixels.add(addTrackingPixel(context.data?.viewableImpressionCounter));\n }\n }, 1000);\n }\n else if (ratio < threshold && timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n\n function onRenderIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n if (entry.isIntersecting) {\n context.render().catch(logger.error);\n renderIntersectionObserver?.disconnect();\n }\n }\n renderIntersectionObserver.observe(options.containingElement);\n return context;\n}\n"],"names":["renderIframe","renderInline","createLogger","packageName","name","generateName","addTrackingPixel","round"],"mappings":"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"}
1
+ {"version":3,"file":"sdkLite.cjs","sources":["../../src/sdkLite.ts"],"sourcesContent":["import {\n type DefaultLogLevels,\n type Logger,\n type RenderOptions,\n addTrackingPixel,\n createLogger,\n generateName,\n renderIframe,\n renderInline,\n round,\n uniqueId,\n} from '@adhese/sdk-shared';\nimport { name as packageName, version } from '../package.json';\n\ntype RenderMode = 'iframe' | 'inline';\n\nexport type AdheseLiteSlotOptions = {\n containingElement?: HTMLElement;\n format: string;\n account: string;\n location: string;\n host?: string;\n debug?: boolean;\n renderMode?: RenderMode;\n parameters?: Record<string, string | ReadonlyArray<string>>;\n slot?: string;\n consent?: boolean | string;\n clickTrackerUrl?: string | URL;\n onDispose?(slot: AdheseLiteSlot): void;\n onRender?(slot: AdheseLiteSlot): void;\n onRequest?(slot: AdheseLiteSlot): void;\n onEmpty?(slot: AdheseLiteSlot): void;\n};\n\nexport type AdheseLiteSlot = {\n options: AdheseLiteSlotOptions;\n name: string;\n data?: AdheseLiteAd;\n logger: Logger<DefaultLogLevels>;\n parameters: Record<string, string | ReadonlyArray<string>>;\n element: HTMLElement;\n render(): Promise<HTMLElement>;\n request(): Promise<AdheseLiteAd | null>;\n dispose(): void;\n};\n\nexport type AdheseLiteAd = {\n tag: string;\n width?: string;\n height?: string;\n impressionCounter?: string;\n viewableImpressionCounter?: string;\n};\n\nconst renderFunctions: Record<RenderMode, (ad: RenderOptions, element: HTMLElement) => void> = {\n iframe: renderIframe,\n inline: renderInline,\n};\n\nexport function createSlot(options: AdheseLiteSlotOptions): AdheseLiteSlot {\n const logger = createLogger({\n scope: `${packageName}@${version}`,\n minLogLevelThreshold: options.debug ? 'debug' : 'info',\n });\n\n const name = generateName(options.location, options.format, options.slot);\n\n const trackingPixels = new Set<HTMLImageElement>();\n\n const renderIntersectionObserver = new IntersectionObserver(onRenderIntersection, {\n rootMargin: '200px',\n threshold: 0,\n });\n\n const viewabilityTrackingIntersectionObserver = new IntersectionObserver(onViewabilityIntersection, {\n rootMargin: '0px',\n threshold: Array.from({ length: 11 }, (_, i) => i * 0.1),\n });\n\n const consentParameters: {\n tl: 'all' | 'none';\n } | {\n xt: string;\n } = typeof options.consent === 'string'\n ? {\n xt: options.consent,\n }\n : {\n tl: options.consent ? 'all' : 'none',\n };\n\n const parameters = {\n ...options.parameters,\n ...consentParameters,\n };\n\n let isImpressionTracked = false;\n\n const element = options.containingElement ?? document.createElement('div');\n\n if (!options.containingElement) {\n element.id = `${name}:${uniqueId(3)}`;\n element.classList.add('adunit');\n element.dataset.format = options.format;\n element.dataset.slot = options.format;\n element.dataset.location = options.location;\n\n document.currentScript?.insertAdjacentElement('beforebegin', element);\n }\n\n const context: AdheseLiteSlot = {\n options,\n name,\n logger,\n parameters,\n element,\n async request(): Promise<AdheseLiteAd | null> {\n logger.debug(`Requesting ad for slot ${name}`, this);\n\n const response = await fetch(`${options.host ?? `https://ads-${options.account}.adhese.com`}/json`, {\n method: 'POST',\n body: JSON.stringify({\n slots: [{\n slotname: name,\n }],\n parameters,\n }),\n headers: {\n // eslint-disable-next-line ts/naming-convention\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok)\n throw new Error(`Failed to request ad for slot ${name}. Status: ${response.status}`);\n\n const [data] = await response.json() as ReadonlyArray<AdheseLiteAd>;\n\n this.data = data;\n\n logger.debug(`Received ad data for slot ${name}`, this);\n options.onRequest?.(this);\n\n return data;\n },\n async render(): Promise<HTMLElement> {\n const data = this.data ?? (await this.request());\n element.removeEventListener('click', onClick);\n\n if (!data) {\n options.onEmpty?.(this);\n logger.debug(`Slot ${name} is empty`, this);\n\n return element;\n }\n\n if (typeof data.tag !== 'string')\n throw new Error('Received invalid ad data, tag is not a string');\n\n renderFunctions[options.renderMode ?? 'iframe'](data, element);\n\n options.onRender?.(this);\n logger.debug(`Rendered slot ${name}`, this);\n\n if (data.impressionCounter && !isImpressionTracked) {\n trackingPixels.add(addTrackingPixel(data.impressionCounter));\n isImpressionTracked = true;\n }\n\n if (data.viewableImpressionCounter) {\n viewabilityTrackingIntersectionObserver.observe(element);\n }\n\n element.addEventListener('click', onClick);\n\n return element;\n },\n dispose(): void {\n options.onDispose?.(this);\n\n element.innerHTML = '';\n\n for (const pixel of trackingPixels) {\n pixel.remove();\n }\n trackingPixels.clear();\n\n renderIntersectionObserver?.disconnect();\n viewabilityTrackingIntersectionObserver.disconnect();\n\n element.removeEventListener('click', onClick);\n\n logger.debug(`Disposing slot ${name}`, this);\n\n logger.resetLogs();\n },\n };\n\n let isClickTracked = false;\n function onClick(): void {\n if (options.clickTrackerUrl && !isClickTracked) {\n trackingPixels.add(addTrackingPixel(options.clickTrackerUrl));\n isClickTracked = true;\n }\n }\n\n let timeoutId: number | null = null;\n function onViewabilityIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n const ratio = round(entry.intersectionRatio, 1);\n const threshold = 0.2;\n\n if (ratio >= threshold && !timeoutId) {\n // @ts-expect-error The is misfiring to the Node type\n timeoutId = setTimeout(() => {\n timeoutId = null;\n viewabilityTrackingIntersectionObserver.disconnect();\n\n if (context.data?.viewableImpressionCounter) {\n trackingPixels.add(addTrackingPixel(context.data?.viewableImpressionCounter));\n }\n }, 1000);\n }\n else if (ratio < threshold && timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n\n function onRenderIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n if (entry.isIntersecting) {\n context.render().catch(logger.error);\n renderIntersectionObserver?.disconnect();\n }\n }\n renderIntersectionObserver.observe(element);\n return context;\n}\n"],"names":["renderIframe","renderInline","createLogger","packageName","name","generateName","uniqueId","_a","addTrackingPixel","round"],"mappings":"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"}
package/dist/sdkLite.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Logger, DefaultLogLevels } from '@adhese/sdk-shared';
2
2
 
3
3
  type RenderMode = 'iframe' | 'inline';
4
4
  type AdheseLiteSlotOptions = {
5
- containingElement: HTMLElement;
5
+ containingElement?: HTMLElement;
6
6
  format: string;
7
7
  account: string;
8
8
  location: string;
@@ -24,6 +24,7 @@ type AdheseLiteSlot = {
24
24
  data?: AdheseLiteAd;
25
25
  logger: Logger<DefaultLogLevels>;
26
26
  parameters: Record<string, string | ReadonlyArray<string>>;
27
+ element: HTMLElement;
27
28
  render(): Promise<HTMLElement>;
28
29
  request(): Promise<AdheseLiteAd | null>;
29
30
  dispose(): void;
package/dist/sdkLite.js CHANGED
@@ -1,11 +1,12 @@
1
- import { createLogger, generateName, round, addTrackingPixel, renderIframe, renderInline } from "@adhese/sdk-shared";
1
+ import { createLogger, generateName, uniqueId, round, addTrackingPixel, renderIframe, renderInline } from "@adhese/sdk-shared";
2
2
  const name = "@adhese/sdk-lite";
3
- const version = "1.0.0";
3
+ const version = "1.1.0";
4
4
  const renderFunctions = {
5
5
  iframe: renderIframe,
6
6
  inline: renderInline
7
7
  };
8
8
  function createSlot(options) {
9
+ var _a;
9
10
  const logger = createLogger({
10
11
  scope: `${name}@${version}`,
11
12
  minLogLevelThreshold: options.debug ? "debug" : "info"
@@ -30,13 +31,23 @@ function createSlot(options) {
30
31
  ...consentParameters
31
32
  };
32
33
  let isImpressionTracked = false;
34
+ const element = options.containingElement ?? document.createElement("div");
35
+ if (!options.containingElement) {
36
+ element.id = `${name$1}:${uniqueId(3)}`;
37
+ element.classList.add("adunit");
38
+ element.dataset.format = options.format;
39
+ element.dataset.slot = options.format;
40
+ element.dataset.location = options.location;
41
+ (_a = document.currentScript) == null ? void 0 : _a.insertAdjacentElement("beforebegin", element);
42
+ }
33
43
  const context = {
34
44
  options,
35
45
  name: name$1,
36
46
  logger,
37
47
  parameters,
48
+ element,
38
49
  async request() {
39
- var _a;
50
+ var _a2;
40
51
  logger.debug(`Requesting ad for slot ${name$1}`, this);
41
52
  const response = await fetch(`${options.host ?? `https://ads-${options.account}.adhese.com`}/json`, {
42
53
  method: "POST",
@@ -56,21 +67,21 @@ function createSlot(options) {
56
67
  const [data] = await response.json();
57
68
  this.data = data;
58
69
  logger.debug(`Received ad data for slot ${name$1}`, this);
59
- (_a = options.onRequest) == null ? void 0 : _a.call(options, this);
70
+ (_a2 = options.onRequest) == null ? void 0 : _a2.call(options, this);
60
71
  return data;
61
72
  },
62
73
  async render() {
63
- var _a, _b;
74
+ var _a2, _b;
64
75
  const data = this.data ?? await this.request();
65
- options.containingElement.removeEventListener("click", onClick);
76
+ element.removeEventListener("click", onClick);
66
77
  if (!data) {
67
- (_a = options.onEmpty) == null ? void 0 : _a.call(options, this);
78
+ (_a2 = options.onEmpty) == null ? void 0 : _a2.call(options, this);
68
79
  logger.debug(`Slot ${name$1} is empty`, this);
69
- return options.containingElement;
80
+ return element;
70
81
  }
71
82
  if (typeof data.tag !== "string")
72
83
  throw new Error("Received invalid ad data, tag is not a string");
73
- renderFunctions[options.renderMode ?? "iframe"](data, options.containingElement);
84
+ renderFunctions[options.renderMode ?? "iframe"](data, element);
74
85
  (_b = options.onRender) == null ? void 0 : _b.call(options, this);
75
86
  logger.debug(`Rendered slot ${name$1}`, this);
76
87
  if (data.impressionCounter && !isImpressionTracked) {
@@ -78,22 +89,22 @@ function createSlot(options) {
78
89
  isImpressionTracked = true;
79
90
  }
80
91
  if (data.viewableImpressionCounter) {
81
- viewabilityTrackingIntersectionObserver.observe(options.containingElement);
92
+ viewabilityTrackingIntersectionObserver.observe(element);
82
93
  }
83
- options.containingElement.addEventListener("click", onClick);
84
- return options.containingElement;
94
+ element.addEventListener("click", onClick);
95
+ return element;
85
96
  },
86
97
  dispose() {
87
- var _a;
88
- (_a = options.onDispose) == null ? void 0 : _a.call(options, this);
89
- options.containingElement.innerHTML = "";
98
+ var _a2;
99
+ (_a2 = options.onDispose) == null ? void 0 : _a2.call(options, this);
100
+ element.innerHTML = "";
90
101
  for (const pixel of trackingPixels) {
91
102
  pixel.remove();
92
103
  }
93
104
  trackingPixels.clear();
94
105
  renderIntersectionObserver == null ? void 0 : renderIntersectionObserver.disconnect();
95
106
  viewabilityTrackingIntersectionObserver.disconnect();
96
- options.containingElement.removeEventListener("click", onClick);
107
+ element.removeEventListener("click", onClick);
97
108
  logger.debug(`Disposing slot ${name$1}`, this);
98
109
  logger.resetLogs();
99
110
  }
@@ -111,10 +122,10 @@ function createSlot(options) {
111
122
  const threshold = 0.2;
112
123
  if (ratio >= threshold && !timeoutId) {
113
124
  timeoutId = setTimeout(() => {
114
- var _a, _b;
125
+ var _a2, _b;
115
126
  timeoutId = null;
116
127
  viewabilityTrackingIntersectionObserver.disconnect();
117
- if ((_a = context.data) == null ? void 0 : _a.viewableImpressionCounter) {
128
+ if ((_a2 = context.data) == null ? void 0 : _a2.viewableImpressionCounter) {
118
129
  trackingPixels.add(addTrackingPixel((_b = context.data) == null ? void 0 : _b.viewableImpressionCounter));
119
130
  }
120
131
  }, 1e3);
@@ -129,7 +140,7 @@ function createSlot(options) {
129
140
  renderIntersectionObserver == null ? void 0 : renderIntersectionObserver.disconnect();
130
141
  }
131
142
  }
132
- renderIntersectionObserver.observe(options.containingElement);
143
+ renderIntersectionObserver.observe(element);
133
144
  return context;
134
145
  }
135
146
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"sdkLite.js","sources":["../src/sdkLite.ts"],"sourcesContent":["import {\n type DefaultLogLevels,\n type Logger,\n type RenderOptions,\n addTrackingPixel,\n createLogger,\n generateName,\n renderIframe,\n renderInline,\n round,\n} from '@adhese/sdk-shared';\nimport { name as packageName, version } from '../package.json';\n\ntype RenderMode = 'iframe' | 'inline';\n\nexport type AdheseLiteSlotOptions = {\n containingElement: HTMLElement;\n format: string;\n account: string;\n location: string;\n host?: string;\n debug?: boolean;\n renderMode?: RenderMode;\n parameters?: Record<string, string | ReadonlyArray<string>>;\n slot?: string;\n consent?: boolean | string;\n clickTrackerUrl?: string | URL;\n onDispose?(slot: AdheseLiteSlot): void;\n onRender?(slot: AdheseLiteSlot): void;\n onRequest?(slot: AdheseLiteSlot): void;\n onEmpty?(slot: AdheseLiteSlot): void;\n};\n\nexport type AdheseLiteSlot = {\n options: AdheseLiteSlotOptions;\n name: string;\n data?: AdheseLiteAd;\n logger: Logger<DefaultLogLevels>;\n parameters: Record<string, string | ReadonlyArray<string>>;\n render(): Promise<HTMLElement>;\n request(): Promise<AdheseLiteAd | null>;\n dispose(): void;\n};\n\nexport type AdheseLiteAd = {\n tag: string;\n width?: string;\n height?: string;\n impressionCounter?: string;\n viewableImpressionCounter?: string;\n};\n\nconst renderFunctions: Record<RenderMode, (ad: RenderOptions, element: HTMLElement) => void> = {\n iframe: renderIframe,\n inline: renderInline,\n};\n\nexport function createSlot(options: AdheseLiteSlotOptions): AdheseLiteSlot {\n const logger = createLogger({\n scope: `${packageName}@${version}`,\n minLogLevelThreshold: options.debug ? 'debug' : 'info',\n });\n\n const name = generateName(options.location, options.format, options.slot);\n\n const trackingPixels = new Set<HTMLImageElement>();\n\n const renderIntersectionObserver = new IntersectionObserver(onRenderIntersection, {\n rootMargin: '200px',\n threshold: 0,\n });\n\n const viewabilityTrackingIntersectionObserver = new IntersectionObserver(onViewabilityIntersection, {\n rootMargin: '0px',\n threshold: Array.from({ length: 11 }, (_, i) => i * 0.1),\n });\n\n const consentParameters: {\n tl: 'all' | 'none';\n } | {\n xt: string;\n } = typeof options.consent === 'string'\n ? {\n xt: options.consent,\n }\n : {\n tl: options.consent ? 'all' : 'none',\n };\n\n const parameters = {\n ...options.parameters,\n ...consentParameters,\n };\n\n let isImpressionTracked = false;\n\n const context: AdheseLiteSlot = {\n options,\n name,\n logger,\n parameters,\n async request(): Promise<AdheseLiteAd | null> {\n logger.debug(`Requesting ad for slot ${name}`, this);\n\n const response = await fetch(`${options.host ?? `https://ads-${options.account}.adhese.com`}/json`, {\n method: 'POST',\n body: JSON.stringify({\n slots: [{\n slotname: name,\n }],\n parameters,\n }),\n headers: {\n // eslint-disable-next-line ts/naming-convention\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok)\n throw new Error(`Failed to request ad for slot ${name}. Status: ${response.status}`);\n\n const [data] = await response.json() as ReadonlyArray<AdheseLiteAd>;\n\n this.data = data;\n\n logger.debug(`Received ad data for slot ${name}`, this);\n options.onRequest?.(this);\n\n return data;\n },\n async render(): Promise<HTMLElement> {\n const data = this.data ?? (await this.request());\n options.containingElement.removeEventListener('click', onClick);\n\n if (!data) {\n options.onEmpty?.(this);\n logger.debug(`Slot ${name} is empty`, this);\n\n return options.containingElement;\n }\n\n if (typeof data.tag !== 'string')\n throw new Error('Received invalid ad data, tag is not a string');\n\n renderFunctions[options.renderMode ?? 'iframe'](data, options.containingElement);\n\n options.onRender?.(this);\n logger.debug(`Rendered slot ${name}`, this);\n\n if (data.impressionCounter && !isImpressionTracked) {\n trackingPixels.add(addTrackingPixel(data.impressionCounter));\n isImpressionTracked = true;\n }\n\n if (data.viewableImpressionCounter) {\n viewabilityTrackingIntersectionObserver.observe(options.containingElement);\n }\n\n options.containingElement.addEventListener('click', onClick);\n\n return options.containingElement;\n },\n dispose(): void {\n options.onDispose?.(this);\n\n options.containingElement.innerHTML = '';\n\n for (const pixel of trackingPixels) {\n pixel.remove();\n }\n trackingPixels.clear();\n\n renderIntersectionObserver?.disconnect();\n viewabilityTrackingIntersectionObserver.disconnect();\n\n options.containingElement.removeEventListener('click', onClick);\n\n logger.debug(`Disposing slot ${name}`, this);\n\n logger.resetLogs();\n },\n };\n\n let isClickTracked = false;\n function onClick(): void {\n if (options.clickTrackerUrl && !isClickTracked) {\n trackingPixels.add(addTrackingPixel(options.clickTrackerUrl));\n isClickTracked = true;\n }\n }\n\n let timeoutId: number | null = null;\n function onViewabilityIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n const ratio = round(entry.intersectionRatio, 1);\n const threshold = 0.2;\n\n if (ratio >= threshold && !timeoutId) {\n // @ts-expect-error The is misfiring to the Node type\n timeoutId = setTimeout(() => {\n timeoutId = null;\n viewabilityTrackingIntersectionObserver.disconnect();\n\n if (context.data?.viewableImpressionCounter) {\n trackingPixels.add(addTrackingPixel(context.data?.viewableImpressionCounter));\n }\n }, 1000);\n }\n else if (ratio < threshold && timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n\n function onRenderIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n if (entry.isIntersecting) {\n context.render().catch(logger.error);\n renderIntersectionObserver?.disconnect();\n }\n }\n renderIntersectionObserver.observe(options.containingElement);\n return context;\n}\n"],"names":["packageName","name"],"mappings":";;;AAoDA,MAAM,kBAAyF;AAAA,EAC7F,QAAQ;AAAA,EACR,QAAQ;AACV;AAEO,SAAS,WAAW,SAAgD;AACzE,QAAM,SAAS,aAAa;AAAA,IAC1B,OAAO,GAAGA,IAAW,IAAI,OAAO;AAAA,IAChC,sBAAsB,QAAQ,QAAQ,UAAU;AAAA,EAAA,CACjD;AAED,QAAMC,SAAO,aAAa,QAAQ,UAAU,QAAQ,QAAQ,QAAQ,IAAI;AAElE,QAAA,qCAAqB;AAErB,QAAA,6BAA6B,IAAI,qBAAqB,sBAAsB;AAAA,IAChF,YAAY;AAAA,IACZ,WAAW;AAAA,EAAA,CACZ;AAEK,QAAA,0CAA0C,IAAI,qBAAqB,2BAA2B;AAAA,IAClG,YAAY;AAAA,IACZ,WAAW,MAAM,KAAK,EAAE,QAAQ,MAAM,CAAC,GAAG,MAAM,IAAI,GAAG;AAAA,EAAA,CACxD;AAED,QAAM,oBAIF,OAAO,QAAQ,YAAY,WAC3B;AAAA,IACE,IAAI,QAAQ;AAAA,EAAA,IAEd;AAAA,IACE,IAAI,QAAQ,UAAU,QAAQ;AAAA,EAAA;AAGpC,QAAM,aAAa;AAAA,IACjB,GAAG,QAAQ;AAAA,IACX,GAAG;AAAA,EAAA;AAGL,MAAI,sBAAsB;AAE1B,QAAM,UAA0B;AAAA,IAC9B;AAAA,IAAA,MACAA;AAAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM,UAAwC;;AAC5C,aAAO,MAAM,0BAA0BA,MAAI,IAAI,IAAI;AAE7C,YAAA,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,eAAe,QAAQ,OAAO,aAAa,SAAS;AAAA,QAClG,QAAQ;AAAA,QACR,MAAM,KAAK,UAAU;AAAA,UACnB,OAAO,CAAC;AAAA,YACN,UAAUA;AAAAA,UAAA,CACX;AAAA,UACD;AAAA,QAAA,CACD;AAAA,QACD,SAAS;AAAA;AAAA,UAEP,gBAAgB;AAAA,QAClB;AAAA,MAAA,CACD;AAED,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,MAAM,iCAAiCA,MAAI,aAAa,SAAS,MAAM,EAAE;AAErF,YAAM,CAAC,IAAI,IAAI,MAAM,SAAS,KAAK;AAEnC,WAAK,OAAO;AAEZ,aAAO,MAAM,6BAA6BA,MAAI,IAAI,IAAI;AACtD,oBAAQ,cAAR,iCAAoB;AAEb,aAAA;AAAA,IACT;AAAA,IACA,MAAM,SAA+B;;AACnC,YAAM,OAAO,KAAK,QAAS,MAAM,KAAK,QAAQ;AACtC,cAAA,kBAAkB,oBAAoB,SAAS,OAAO;AAE9D,UAAI,CAAC,MAAM;AACT,sBAAQ,YAAR,iCAAkB;AAClB,eAAO,MAAM,QAAQA,MAAI,aAAa,IAAI;AAE1C,eAAO,QAAQ;AAAA,MACjB;AAEI,UAAA,OAAO,KAAK,QAAQ;AAChB,cAAA,IAAI,MAAM,+CAA+C;AAEjE,sBAAgB,QAAQ,cAAc,QAAQ,EAAE,MAAM,QAAQ,iBAAiB;AAE/E,oBAAQ,aAAR,iCAAmB;AACnB,aAAO,MAAM,iBAAiBA,MAAI,IAAI,IAAI;AAEtC,UAAA,KAAK,qBAAqB,CAAC,qBAAqB;AAClD,uBAAe,IAAI,iBAAiB,KAAK,iBAAiB,CAAC;AACrC,8BAAA;AAAA,MACxB;AAEA,UAAI,KAAK,2BAA2B;AACM,gDAAA,QAAQ,QAAQ,iBAAiB;AAAA,MAC3E;AAEQ,cAAA,kBAAkB,iBAAiB,SAAS,OAAO;AAE3D,aAAO,QAAQ;AAAA,IACjB;AAAA,IACA,UAAgB;;AACd,oBAAQ,cAAR,iCAAoB;AAEpB,cAAQ,kBAAkB,YAAY;AAEtC,iBAAW,SAAS,gBAAgB;AAClC,cAAM,OAAO;AAAA,MACf;AACA,qBAAe,MAAM;AAErB,+EAA4B;AAC5B,8CAAwC,WAAW;AAE3C,cAAA,kBAAkB,oBAAoB,SAAS,OAAO;AAE9D,aAAO,MAAM,kBAAkBA,MAAI,IAAI,IAAI;AAE3C,aAAO,UAAU;AAAA,IACnB;AAAA,EAAA;AAGF,MAAI,iBAAiB;AACrB,WAAS,UAAgB;AACnB,QAAA,QAAQ,mBAAmB,CAAC,gBAAgB;AAC9C,qBAAe,IAAI,iBAAiB,QAAQ,eAAe,CAAC;AAC3C,uBAAA;AAAA,IACnB;AAAA,EACF;AAEA,MAAI,YAA2B;AACtB,WAAA,0BAA0B,CAAC,KAAK,GAAmD;AAC1F,UAAM,QAAQ,MAAM,MAAM,mBAAmB,CAAC;AAC9C,UAAM,YAAY;AAEd,QAAA,SAAS,aAAa,CAAC,WAAW;AAEpC,kBAAY,WAAW,MAAM;;AACf,oBAAA;AACZ,gDAAwC,WAAW;AAE/C,aAAA,aAAQ,SAAR,mBAAc,2BAA2B;AAC3C,yBAAe,IAAI,kBAAiB,aAAQ,SAAR,mBAAc,yBAAyB,CAAC;AAAA,QAC9E;AAAA,SACC,GAAI;AAAA,IAAA,WAEA,QAAQ,aAAa,WAAW;AACvC,mBAAa,SAAS;AACV,kBAAA;AAAA,IACd;AAAA,EACF;AAES,WAAA,qBAAqB,CAAC,KAAK,GAAmD;AACrF,QAAI,MAAM,gBAAgB;AACxB,cAAQ,OAAO,EAAE,MAAM,OAAO,KAAK;AACnC,+EAA4B;AAAA,IAC9B;AAAA,EACF;AAC2B,6BAAA,QAAQ,QAAQ,iBAAiB;AACrD,SAAA;AACT;"}
1
+ {"version":3,"file":"sdkLite.js","sources":["../src/sdkLite.ts"],"sourcesContent":["import {\n type DefaultLogLevels,\n type Logger,\n type RenderOptions,\n addTrackingPixel,\n createLogger,\n generateName,\n renderIframe,\n renderInline,\n round,\n uniqueId,\n} from '@adhese/sdk-shared';\nimport { name as packageName, version } from '../package.json';\n\ntype RenderMode = 'iframe' | 'inline';\n\nexport type AdheseLiteSlotOptions = {\n containingElement?: HTMLElement;\n format: string;\n account: string;\n location: string;\n host?: string;\n debug?: boolean;\n renderMode?: RenderMode;\n parameters?: Record<string, string | ReadonlyArray<string>>;\n slot?: string;\n consent?: boolean | string;\n clickTrackerUrl?: string | URL;\n onDispose?(slot: AdheseLiteSlot): void;\n onRender?(slot: AdheseLiteSlot): void;\n onRequest?(slot: AdheseLiteSlot): void;\n onEmpty?(slot: AdheseLiteSlot): void;\n};\n\nexport type AdheseLiteSlot = {\n options: AdheseLiteSlotOptions;\n name: string;\n data?: AdheseLiteAd;\n logger: Logger<DefaultLogLevels>;\n parameters: Record<string, string | ReadonlyArray<string>>;\n element: HTMLElement;\n render(): Promise<HTMLElement>;\n request(): Promise<AdheseLiteAd | null>;\n dispose(): void;\n};\n\nexport type AdheseLiteAd = {\n tag: string;\n width?: string;\n height?: string;\n impressionCounter?: string;\n viewableImpressionCounter?: string;\n};\n\nconst renderFunctions: Record<RenderMode, (ad: RenderOptions, element: HTMLElement) => void> = {\n iframe: renderIframe,\n inline: renderInline,\n};\n\nexport function createSlot(options: AdheseLiteSlotOptions): AdheseLiteSlot {\n const logger = createLogger({\n scope: `${packageName}@${version}`,\n minLogLevelThreshold: options.debug ? 'debug' : 'info',\n });\n\n const name = generateName(options.location, options.format, options.slot);\n\n const trackingPixels = new Set<HTMLImageElement>();\n\n const renderIntersectionObserver = new IntersectionObserver(onRenderIntersection, {\n rootMargin: '200px',\n threshold: 0,\n });\n\n const viewabilityTrackingIntersectionObserver = new IntersectionObserver(onViewabilityIntersection, {\n rootMargin: '0px',\n threshold: Array.from({ length: 11 }, (_, i) => i * 0.1),\n });\n\n const consentParameters: {\n tl: 'all' | 'none';\n } | {\n xt: string;\n } = typeof options.consent === 'string'\n ? {\n xt: options.consent,\n }\n : {\n tl: options.consent ? 'all' : 'none',\n };\n\n const parameters = {\n ...options.parameters,\n ...consentParameters,\n };\n\n let isImpressionTracked = false;\n\n const element = options.containingElement ?? document.createElement('div');\n\n if (!options.containingElement) {\n element.id = `${name}:${uniqueId(3)}`;\n element.classList.add('adunit');\n element.dataset.format = options.format;\n element.dataset.slot = options.format;\n element.dataset.location = options.location;\n\n document.currentScript?.insertAdjacentElement('beforebegin', element);\n }\n\n const context: AdheseLiteSlot = {\n options,\n name,\n logger,\n parameters,\n element,\n async request(): Promise<AdheseLiteAd | null> {\n logger.debug(`Requesting ad for slot ${name}`, this);\n\n const response = await fetch(`${options.host ?? `https://ads-${options.account}.adhese.com`}/json`, {\n method: 'POST',\n body: JSON.stringify({\n slots: [{\n slotname: name,\n }],\n parameters,\n }),\n headers: {\n // eslint-disable-next-line ts/naming-convention\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok)\n throw new Error(`Failed to request ad for slot ${name}. Status: ${response.status}`);\n\n const [data] = await response.json() as ReadonlyArray<AdheseLiteAd>;\n\n this.data = data;\n\n logger.debug(`Received ad data for slot ${name}`, this);\n options.onRequest?.(this);\n\n return data;\n },\n async render(): Promise<HTMLElement> {\n const data = this.data ?? (await this.request());\n element.removeEventListener('click', onClick);\n\n if (!data) {\n options.onEmpty?.(this);\n logger.debug(`Slot ${name} is empty`, this);\n\n return element;\n }\n\n if (typeof data.tag !== 'string')\n throw new Error('Received invalid ad data, tag is not a string');\n\n renderFunctions[options.renderMode ?? 'iframe'](data, element);\n\n options.onRender?.(this);\n logger.debug(`Rendered slot ${name}`, this);\n\n if (data.impressionCounter && !isImpressionTracked) {\n trackingPixels.add(addTrackingPixel(data.impressionCounter));\n isImpressionTracked = true;\n }\n\n if (data.viewableImpressionCounter) {\n viewabilityTrackingIntersectionObserver.observe(element);\n }\n\n element.addEventListener('click', onClick);\n\n return element;\n },\n dispose(): void {\n options.onDispose?.(this);\n\n element.innerHTML = '';\n\n for (const pixel of trackingPixels) {\n pixel.remove();\n }\n trackingPixels.clear();\n\n renderIntersectionObserver?.disconnect();\n viewabilityTrackingIntersectionObserver.disconnect();\n\n element.removeEventListener('click', onClick);\n\n logger.debug(`Disposing slot ${name}`, this);\n\n logger.resetLogs();\n },\n };\n\n let isClickTracked = false;\n function onClick(): void {\n if (options.clickTrackerUrl && !isClickTracked) {\n trackingPixels.add(addTrackingPixel(options.clickTrackerUrl));\n isClickTracked = true;\n }\n }\n\n let timeoutId: number | null = null;\n function onViewabilityIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n const ratio = round(entry.intersectionRatio, 1);\n const threshold = 0.2;\n\n if (ratio >= threshold && !timeoutId) {\n // @ts-expect-error The is misfiring to the Node type\n timeoutId = setTimeout(() => {\n timeoutId = null;\n viewabilityTrackingIntersectionObserver.disconnect();\n\n if (context.data?.viewableImpressionCounter) {\n trackingPixels.add(addTrackingPixel(context.data?.viewableImpressionCounter));\n }\n }, 1000);\n }\n else if (ratio < threshold && timeoutId) {\n clearTimeout(timeoutId);\n timeoutId = null;\n }\n }\n\n function onRenderIntersection([entry]: ReadonlyArray<IntersectionObserverEntry>): void {\n if (entry.isIntersecting) {\n context.render().catch(logger.error);\n renderIntersectionObserver?.disconnect();\n }\n }\n renderIntersectionObserver.observe(element);\n return context;\n}\n"],"names":["packageName","name","_a"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adhese/sdk-lite",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "description": "Adhese SDK",
6
6
  "repository": {
7
7
  "type": "git",