@action-x/ad-sdk 0.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/README.md +678 -0
- package/dist/index.cjs +139 -0
- package/dist/index.d.ts +1552 -0
- package/dist/index.js +1478 -0
- package/dist/index.umd.js +139 -0
- package/dist/style.css +1 -0
- package/package.json +30 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1478 @@
|
|
|
1
|
+
class I {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.events = {};
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Register an event listener
|
|
7
|
+
*/
|
|
8
|
+
on(e, t) {
|
|
9
|
+
this.events[e] || (this.events[e] = []), this.events[e].push(t);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Remove an event listener
|
|
13
|
+
*/
|
|
14
|
+
off(e, t) {
|
|
15
|
+
if (!this.events[e]) return;
|
|
16
|
+
const i = this.events[e].indexOf(t);
|
|
17
|
+
i > -1 && this.events[e].splice(i, 1);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Emit an event
|
|
21
|
+
*/
|
|
22
|
+
emit(e, ...t) {
|
|
23
|
+
this.events[e] && this.events[e].forEach((i) => {
|
|
24
|
+
try {
|
|
25
|
+
i(...t);
|
|
26
|
+
} catch (n) {
|
|
27
|
+
console.error(`[EventEmitter] Error in event handler for "${e}":`, n);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Remove all listeners for an event
|
|
33
|
+
*/
|
|
34
|
+
removeAllListeners(e) {
|
|
35
|
+
e ? delete this.events[e] : this.events = {};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get listener count for an event
|
|
39
|
+
*/
|
|
40
|
+
listenerCount(e) {
|
|
41
|
+
var t;
|
|
42
|
+
return ((t = this.events[e]) == null ? void 0 : t.length) || 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
class f {
|
|
46
|
+
/**
|
|
47
|
+
* Render Action Card ad as HTML string
|
|
48
|
+
*
|
|
49
|
+
* @param ad - Adapted ad content
|
|
50
|
+
* @param options - Rendering options
|
|
51
|
+
* @returns HTML string
|
|
52
|
+
*/
|
|
53
|
+
static renderActionCard(e, t = {}) {
|
|
54
|
+
var k;
|
|
55
|
+
const { variant: i = "horizontal", includeWrapper: n = !0 } = t, s = e.adapted, r = e.tracking, a = n ? `<div class="ax-ad-card ax-ad-card-${i}" data-ad-id="${e.original.id}">` : "", c = (k = s.image) != null && k.url ? `<img
|
|
56
|
+
src="${s.image.url}"
|
|
57
|
+
alt="${s.title}"
|
|
58
|
+
class="ax-ad-image"
|
|
59
|
+
loading="lazy"
|
|
60
|
+
/>` : "", l = s.price ? `<span class="ax-ad-price">${s.price.display || s.price.value}</span>` : "", d = s.rating ? `<div class="ax-ad-rating" aria-label="Rating: ${s.rating}">
|
|
61
|
+
${"★".repeat(Math.floor(s.rating))}${"☆".repeat(5 - Math.floor(s.rating))}
|
|
62
|
+
</div>` : "", u = s.brand ? `<span class="ax-ad-brand">${s.brand}</span>` : "", $ = `
|
|
63
|
+
${c}
|
|
64
|
+
<div class="ax-ad-content">
|
|
65
|
+
${u}
|
|
66
|
+
<h3 class="ax-ad-title">${s.title}</h3>
|
|
67
|
+
${s.body ? `<p class="ax-ad-body">${s.body}</p>` : ""}
|
|
68
|
+
${d}
|
|
69
|
+
<div class="ax-ad-footer">
|
|
70
|
+
${l}
|
|
71
|
+
<a
|
|
72
|
+
href="${r.clickUrl}"
|
|
73
|
+
class="ax-ad-cta"
|
|
74
|
+
target="_blank"
|
|
75
|
+
rel="sponsored noopener noreferrer"
|
|
76
|
+
data-ad-id="${e.original.id}"
|
|
77
|
+
data-impression-url="${r.impressionUrl}"
|
|
78
|
+
>
|
|
79
|
+
${s.ctaText || "Learn More"}
|
|
80
|
+
</a>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
`, M = n ? "</div>" : "";
|
|
84
|
+
return a + $ + M;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Render Suffix ad as HTML string
|
|
88
|
+
*
|
|
89
|
+
* @param ad - Adapted ad content
|
|
90
|
+
* @returns HTML string
|
|
91
|
+
*/
|
|
92
|
+
static renderSuffixAd(e) {
|
|
93
|
+
const t = e.adapted, i = e.tracking;
|
|
94
|
+
return `
|
|
95
|
+
<div class="ax-ad-suffix" data-ad-id="${e.original.id}">
|
|
96
|
+
<div class="ax-ad-suffix-content">
|
|
97
|
+
${t.title ? `<h4 class="ax-ad-suffix-title">${t.title}</h4>` : ""}
|
|
98
|
+
${t.body ? `<p class="ax-ad-suffix-body">${t.body}</p>` : ""}
|
|
99
|
+
<a
|
|
100
|
+
href="${i.clickUrl}"
|
|
101
|
+
class="ax-ad-suffix-link"
|
|
102
|
+
target="_blank"
|
|
103
|
+
rel="sponsored noopener noreferrer"
|
|
104
|
+
data-ad-id="${e.original.id}"
|
|
105
|
+
data-impression-url="${i.impressionUrl}"
|
|
106
|
+
>
|
|
107
|
+
${t.ctaText || "Learn More"}
|
|
108
|
+
</a>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Render Sponsored Source ad as HTML string
|
|
115
|
+
*
|
|
116
|
+
* @param ad - Adapted ad content
|
|
117
|
+
* @returns HTML string
|
|
118
|
+
*/
|
|
119
|
+
static renderSponsoredSource(e) {
|
|
120
|
+
var n;
|
|
121
|
+
const t = e.adapted, i = e.tracking;
|
|
122
|
+
return `
|
|
123
|
+
<div class="ax-ad-source" data-ad-id="${e.original.id}">
|
|
124
|
+
<a
|
|
125
|
+
href="${i.clickUrl}"
|
|
126
|
+
class="ax-ad-source-link"
|
|
127
|
+
target="_blank"
|
|
128
|
+
rel="sponsored noopener noreferrer"
|
|
129
|
+
data-ad-id="${e.original.id}"
|
|
130
|
+
data-impression-url="${i.impressionUrl}"
|
|
131
|
+
>
|
|
132
|
+
${(n = t.image) != null && n.url ? `<img src="${t.image.url}" alt="" class="ax-ad-source-icon" />` : ""}
|
|
133
|
+
<div class="ax-ad-source-info">
|
|
134
|
+
<span class="ax-ad-source-name">${t.title}</span>
|
|
135
|
+
${t.body ? `<span class="ax-ad-source-desc">${t.body}</span>` : ""}
|
|
136
|
+
</div>
|
|
137
|
+
<span class="ax-ad-source-badge">Sponsored</span>
|
|
138
|
+
</a>
|
|
139
|
+
</div>
|
|
140
|
+
`;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Render Lead Gen ad as HTML string
|
|
144
|
+
*
|
|
145
|
+
* @param ad - Adapted ad content
|
|
146
|
+
* @returns HTML string
|
|
147
|
+
*/
|
|
148
|
+
static renderLeadGenAd(e) {
|
|
149
|
+
const t = e.adapted, i = e.tracking;
|
|
150
|
+
return `
|
|
151
|
+
<div class="ax-ad-leadgen" data-ad-id="${e.original.id}">
|
|
152
|
+
<div class="ax-ad-leadgen-content">
|
|
153
|
+
${t.title ? `<h3 class="ax-ad-leadgen-title">${t.title}</h3>` : ""}
|
|
154
|
+
${t.body ? `<p class="ax-ad-leadgen-body">${t.body}</p>` : ""}
|
|
155
|
+
<a
|
|
156
|
+
href="${i.clickUrl}"
|
|
157
|
+
class="ax-ad-leadgen-cta"
|
|
158
|
+
target="_blank"
|
|
159
|
+
rel="sponsored noopener noreferrer"
|
|
160
|
+
data-ad-id="${e.original.id}"
|
|
161
|
+
data-impression-url="${i.impressionUrl}"
|
|
162
|
+
>
|
|
163
|
+
${t.ctaText || "Get Started"}
|
|
164
|
+
</a>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Render multiple ads
|
|
171
|
+
*
|
|
172
|
+
* @param ads - Array of adapted ads
|
|
173
|
+
* @param renderFn - Render function to use
|
|
174
|
+
* @returns HTML string with all ads
|
|
175
|
+
*/
|
|
176
|
+
static renderAds(e, t = f.renderActionCard.bind(f)) {
|
|
177
|
+
return e.map(t).join(`
|
|
178
|
+
`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const C = "/api/v1/ads", V = "ad_session_id";
|
|
182
|
+
function E() {
|
|
183
|
+
if (typeof window < "u") {
|
|
184
|
+
const o = window.__AD_CONFIG__;
|
|
185
|
+
if (o != null && o.apiKey)
|
|
186
|
+
return o.apiKey;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
class A {
|
|
190
|
+
constructor(e = {}) {
|
|
191
|
+
this.memoryCache = /* @__PURE__ */ new Map(), this.sessionKey = e.sessionKey || V, this.storage = e.storage || "sessionStorage", (typeof window > "u" || typeof window.sessionStorage > "u") && (this.storage = "memory");
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 获取 Session ID
|
|
195
|
+
*/
|
|
196
|
+
getSessionId() {
|
|
197
|
+
if (this.storage === "sessionStorage") {
|
|
198
|
+
let e = sessionStorage.getItem(this.sessionKey);
|
|
199
|
+
return e || (e = this.generateSessionId(), sessionStorage.setItem(this.sessionKey, e)), e;
|
|
200
|
+
} else {
|
|
201
|
+
let e = this.memoryCache.get(this.sessionKey);
|
|
202
|
+
return e || (e = this.generateSessionId(), this.memoryCache.set(this.sessionKey, e)), e;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 重新生成 Session ID
|
|
207
|
+
*/
|
|
208
|
+
regenerateSessionId() {
|
|
209
|
+
const e = this.generateSessionId();
|
|
210
|
+
return this.storage === "sessionStorage" ? sessionStorage.setItem(this.sessionKey, e) : this.memoryCache.set(this.sessionKey, e), e;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* 生成新的 Session ID
|
|
214
|
+
*/
|
|
215
|
+
generateSessionId() {
|
|
216
|
+
return `session_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* 清除 Session ID
|
|
220
|
+
*/
|
|
221
|
+
clearSessionId() {
|
|
222
|
+
this.storage === "sessionStorage" ? sessionStorage.removeItem(this.sessionKey) : this.memoryCache.delete(this.sessionKey);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const P = new A(), m = () => P.getSessionId();
|
|
226
|
+
class S {
|
|
227
|
+
constructor(e = {}) {
|
|
228
|
+
this.baseUrl = e.baseUrl || C, this.timeout = e.timeout || 1e4, this.retryAttempts = e.retryAttempts ?? 2, this.retryDelay = e.retryDelay || 1e3, this.debug = e.debug || !1;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* 发送广告展示事件
|
|
232
|
+
*/
|
|
233
|
+
async trackImpression(e) {
|
|
234
|
+
return this.sendRequest(
|
|
235
|
+
`${this.baseUrl}/impression`,
|
|
236
|
+
e,
|
|
237
|
+
"impression"
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* 发送广告点击事件
|
|
242
|
+
*/
|
|
243
|
+
async trackClick(e) {
|
|
244
|
+
return this.sendRequest(
|
|
245
|
+
`${this.baseUrl}/click`,
|
|
246
|
+
e,
|
|
247
|
+
"click"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* 通用请求发送方法(带重试)
|
|
252
|
+
* 自动添加 X-API-Key header
|
|
253
|
+
*/
|
|
254
|
+
async sendRequest(e, t, i) {
|
|
255
|
+
let n = null;
|
|
256
|
+
for (let s = 0; s <= this.retryAttempts; s++)
|
|
257
|
+
try {
|
|
258
|
+
this.debug && console.log(`[Analytics] Sending ${i} event (attempt ${s + 1}):`, t);
|
|
259
|
+
const r = new AbortController(), a = setTimeout(() => r.abort(), this.timeout), c = {
|
|
260
|
+
"Content-Type": "application/json"
|
|
261
|
+
}, l = E();
|
|
262
|
+
l && (c["X-API-Key"] = l, this.debug && console.log(`[Analytics] Adding X-API-Key header for ${i} event`));
|
|
263
|
+
const d = await fetch(e, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: c,
|
|
266
|
+
body: JSON.stringify(t),
|
|
267
|
+
keepalive: !0,
|
|
268
|
+
signal: r.signal
|
|
269
|
+
});
|
|
270
|
+
if (clearTimeout(a), !d.ok)
|
|
271
|
+
throw new Error(`HTTP ${d.status}: ${d.statusText}`);
|
|
272
|
+
const u = await d.json();
|
|
273
|
+
return this.debug && console.log(`[Analytics] ${i} event tracked successfully:`, u), u;
|
|
274
|
+
} catch (r) {
|
|
275
|
+
n = r, this.debug && console.warn(`[Analytics] ${i} tracking failed (attempt ${s + 1}):`, r), s < this.retryAttempts && await this.delay(this.retryDelay * (s + 1));
|
|
276
|
+
}
|
|
277
|
+
return console.error(`[Analytics] ${i} tracking failed after ${this.retryAttempts + 1} attempts:`, n), null;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* 延迟辅助方法
|
|
281
|
+
*/
|
|
282
|
+
delay(e) {
|
|
283
|
+
return new Promise((t) => setTimeout(t, e));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const T = new S(), y = (o) => T.trackImpression(o), b = (o) => T.trackClick(o);
|
|
287
|
+
function B(o, e) {
|
|
288
|
+
return `vt_${o}_${e}`;
|
|
289
|
+
}
|
|
290
|
+
async function L(o) {
|
|
291
|
+
return Promise.all(o.map((e) => y(e)));
|
|
292
|
+
}
|
|
293
|
+
async function q(o) {
|
|
294
|
+
return Promise.all(o.map((e) => b(e)));
|
|
295
|
+
}
|
|
296
|
+
function K(o) {
|
|
297
|
+
const e = new S(o);
|
|
298
|
+
return {
|
|
299
|
+
trackImpression: (t) => e.trackImpression(t),
|
|
300
|
+
trackClick: (t) => e.trackClick(t)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function R(o = {}) {
|
|
304
|
+
const e = new A(o);
|
|
305
|
+
return {
|
|
306
|
+
getSessionId: () => e.getSessionId(),
|
|
307
|
+
regenerateSessionId: () => e.regenerateSessionId(),
|
|
308
|
+
clearSessionId: () => e.clearSessionId()
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
class g {
|
|
312
|
+
/**
|
|
313
|
+
* Render Action Card ad into container
|
|
314
|
+
*/
|
|
315
|
+
static renderActionCard(e, t, i = {}) {
|
|
316
|
+
t.innerHTML = f.renderActionCard(e, i);
|
|
317
|
+
const n = t.querySelector(".ax-ad-cta");
|
|
318
|
+
return n && n.addEventListener("click", (s) => {
|
|
319
|
+
s.preventDefault(), this.handleClick(e, i);
|
|
320
|
+
}), this.trackImpression(e, t, i), t;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Render Suffix ad into container
|
|
324
|
+
*/
|
|
325
|
+
static renderSuffixAd(e, t, i = {}) {
|
|
326
|
+
const n = i.variant || "block";
|
|
327
|
+
t.innerHTML = this.generateSuffixAdHTML(e, n);
|
|
328
|
+
const s = t.querySelector(".ax-ad-suffix-link");
|
|
329
|
+
return s && s.addEventListener("click", (r) => {
|
|
330
|
+
r.preventDefault(), this.handleClick(e, i);
|
|
331
|
+
}), this.trackImpression(e, t, i), t;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Render Sponsored Source ad into container
|
|
335
|
+
*/
|
|
336
|
+
static renderSponsoredSource(e, t, i = {}) {
|
|
337
|
+
const n = i.variant || "card";
|
|
338
|
+
t.innerHTML = this.generateSponsoredSourceHTML(e, n);
|
|
339
|
+
const s = t.querySelector(".ax-ad-source-link");
|
|
340
|
+
return s && s.addEventListener("click", (r) => {
|
|
341
|
+
r.preventDefault(), this.handleClick(e, i);
|
|
342
|
+
}), this.trackImpression(e, t, i), t;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Render Lead Gen ad into container
|
|
346
|
+
*/
|
|
347
|
+
static renderLeadGenAd(e, t, i = {}) {
|
|
348
|
+
t.innerHTML = f.renderLeadGenAd(e);
|
|
349
|
+
const n = t.querySelector(".ax-ad-leadgen-cta");
|
|
350
|
+
return n && n.addEventListener("click", (s) => {
|
|
351
|
+
s.preventDefault(), this.handleClick(e, i);
|
|
352
|
+
}), this.trackImpression(e, t, i), t;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Render multiple ads into container
|
|
356
|
+
*/
|
|
357
|
+
static renderAds(e, t, i = this.renderActionCard.bind(this), n) {
|
|
358
|
+
t.innerHTML = "";
|
|
359
|
+
const s = document.createElement("div");
|
|
360
|
+
return s.className = "ax-ads-container", e.forEach((r) => {
|
|
361
|
+
const a = document.createElement("div");
|
|
362
|
+
a.className = "ax-ad-wrapper", i.call(this, r, a, n), s.appendChild(a);
|
|
363
|
+
}), t.appendChild(s), t;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Handle ad click
|
|
367
|
+
* First tracks the click (async, non-blocking), then opens the URL.
|
|
368
|
+
*/
|
|
369
|
+
static handleClick(e, t) {
|
|
370
|
+
const { analytics: i, onClick: n } = t;
|
|
371
|
+
n && n(e), i && b({
|
|
372
|
+
requestId: i.requestId,
|
|
373
|
+
adId: e.original.id,
|
|
374
|
+
destinationUrl: e.tracking.clickUrl,
|
|
375
|
+
slotId: i.slotId,
|
|
376
|
+
sessionId: m(),
|
|
377
|
+
adTitle: e.adapted.title,
|
|
378
|
+
format: e.original.type
|
|
379
|
+
}).catch((s) => {
|
|
380
|
+
console.error("[DOMRenderer] Analytics click tracking failed:", s);
|
|
381
|
+
}), window.open(e.tracking.clickUrl, "_blank", "noopener,noreferrer");
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Track ad impression using IntersectionObserver (50% visible + 1000ms delay)
|
|
385
|
+
* Falls back to immediate tracking if IntersectionObserver is unavailable.
|
|
386
|
+
*/
|
|
387
|
+
static trackImpression(e, t, i) {
|
|
388
|
+
const { analytics: n, onImpression: s } = i, r = () => {
|
|
389
|
+
n ? y({
|
|
390
|
+
requestId: n.requestId,
|
|
391
|
+
adId: e.original.id,
|
|
392
|
+
slotId: n.slotId,
|
|
393
|
+
position: n.position,
|
|
394
|
+
totalAds: n.totalAds,
|
|
395
|
+
sessionId: m(),
|
|
396
|
+
adTitle: e.adapted.title,
|
|
397
|
+
format: e.original.type,
|
|
398
|
+
source: "internal",
|
|
399
|
+
viewToken: e.tracking.viewToken
|
|
400
|
+
}).then(() => {
|
|
401
|
+
s && s(e);
|
|
402
|
+
}).catch((a) => {
|
|
403
|
+
console.error("[DOMRenderer] Analytics impression tracking failed:", a);
|
|
404
|
+
}) : s && s(e);
|
|
405
|
+
};
|
|
406
|
+
if (typeof IntersectionObserver < "u") {
|
|
407
|
+
const a = new IntersectionObserver(
|
|
408
|
+
(l) => {
|
|
409
|
+
l.forEach((d) => {
|
|
410
|
+
d.isIntersecting && (setTimeout(() => {
|
|
411
|
+
r();
|
|
412
|
+
}, 1e3), a.disconnect());
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
{ threshold: 0.5 }
|
|
416
|
+
), c = t.querySelector(`[data-ad-id="${e.original.id}"]`) || t;
|
|
417
|
+
a.observe(c);
|
|
418
|
+
} else
|
|
419
|
+
r();
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Generate HTML for Suffix Ad variants
|
|
423
|
+
*/
|
|
424
|
+
static generateSuffixAdHTML(e, t) {
|
|
425
|
+
const i = e.adapted.title || "", n = e.adapted.body || "";
|
|
426
|
+
return t === "inline" ? `
|
|
427
|
+
<span class="ax-ad-suffix-link ax-ad-variant-inline" data-ad-id="${e.original.id}">
|
|
428
|
+
${i}
|
|
429
|
+
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="display:inline;vertical-align:middle;margin-left:4px">
|
|
430
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
431
|
+
</svg>
|
|
432
|
+
</span>
|
|
433
|
+
` : t === "minimal" ? `
|
|
434
|
+
<span class="ax-ad-suffix-link ax-ad-variant-minimal" data-ad-id="${e.original.id}">
|
|
435
|
+
${i}
|
|
436
|
+
</span>
|
|
437
|
+
` : `
|
|
438
|
+
<div class="ax-ad-suffix ax-ad-variant-block" data-ad-id="${e.original.id}">
|
|
439
|
+
<div class="ax-ad-suffix-content">
|
|
440
|
+
<p class="ax-ad-suffix-title">${i}</p>
|
|
441
|
+
<div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
|
|
442
|
+
<span class="ax-ad-sponsored-badge">Sponsored</span>
|
|
443
|
+
<svg width="16" height="16" fill="none" stroke="#d1d5db" viewBox="0 0 24 24">
|
|
444
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3" />
|
|
445
|
+
</svg>
|
|
446
|
+
</div>
|
|
447
|
+
</div>
|
|
448
|
+
${n ? `<p class="ax-ad-suffix-body">${n}</p>` : ""}
|
|
449
|
+
</div>
|
|
450
|
+
`;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Generate HTML for Sponsored Source Ad variants
|
|
454
|
+
*/
|
|
455
|
+
static generateSponsoredSourceHTML(e, t) {
|
|
456
|
+
var r;
|
|
457
|
+
const i = e.adapted.title || "", n = e.adapted.body || "", s = ((r = e.adapted.image) == null ? void 0 : r.url) || "";
|
|
458
|
+
return t === "list-item" ? `
|
|
459
|
+
<div class="ax-ad-source ax-ad-variant-list-item" data-ad-id="${e.original.id}">
|
|
460
|
+
${s ? `<img src="${s}" alt="${i}" loading="lazy" />` : ""}
|
|
461
|
+
<div class="ax-ad-source-info">
|
|
462
|
+
<span class="ax-ad-source-name">${i}</span>
|
|
463
|
+
<span class="ax-ad-sponsored-badge" style="font-size:9px">Ad</span>
|
|
464
|
+
</div>
|
|
465
|
+
<svg width="12" height="12" fill="none" stroke="#d1d5db" viewBox="0 0 24 24" style="flex-shrink:0">
|
|
466
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
467
|
+
</svg>
|
|
468
|
+
</div>
|
|
469
|
+
` : t === "minimal" ? `
|
|
470
|
+
<div data-ad-id="${e.original.id}" style="display:inline-flex">
|
|
471
|
+
<a class="ax-ad-source-link ax-ad-variant-minimal">
|
|
472
|
+
${s ? `<img src="${s}" alt="${i}" loading="lazy" style="width:16px;height:16px;border-radius:2px;object-fit:cover" />` : ""}
|
|
473
|
+
<span>${i}</span>
|
|
474
|
+
<span class="ax-ad-sponsored-badge" style="font-size:9px">Ad</span>
|
|
475
|
+
</a>
|
|
476
|
+
</div>
|
|
477
|
+
` : `
|
|
478
|
+
<div class="ax-ad-source ax-ad-variant-card" data-ad-id="${e.original.id}">
|
|
479
|
+
${s ? `<img src="${s}" alt="${i}" loading="lazy" style="width:48px;height:48px;border-radius:6px;object-fit:cover;flex-shrink:0" />` : ""}
|
|
480
|
+
<div class="ax-ad-source-info">
|
|
481
|
+
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
|
|
482
|
+
<span class="ax-ad-source-name">${i}</span>
|
|
483
|
+
<span class="ax-ad-sponsored-badge">Sponsored</span>
|
|
484
|
+
</div>
|
|
485
|
+
${n ? `<p class="ax-ad-source-desc">${n}</p>` : ""}
|
|
486
|
+
</div>
|
|
487
|
+
<svg width="16" height="16" fill="none" stroke="#d1d5db" viewBox="0 0 24 24" style="flex-shrink:0">
|
|
488
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
489
|
+
</svg>
|
|
490
|
+
</div>
|
|
491
|
+
`;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
class v {
|
|
495
|
+
constructor() {
|
|
496
|
+
this.cachedStaticInfo = null, this.cacheTimestamp = 0, this.CACHE_TTL = 36e5;
|
|
497
|
+
}
|
|
498
|
+
// 1 hour for static info
|
|
499
|
+
// App information (auto-inferred from runtime environment)
|
|
500
|
+
// 零配置:自动从window.location、document.title等推断
|
|
501
|
+
inferAppInfo() {
|
|
502
|
+
if (typeof window > "u")
|
|
503
|
+
return {
|
|
504
|
+
bundle: "unknown",
|
|
505
|
+
ver: "1.0.0",
|
|
506
|
+
name: "Unknown App",
|
|
507
|
+
publisher: { id: "unknown" }
|
|
508
|
+
};
|
|
509
|
+
const e = this.inferBundle(), t = this.inferAppName(), i = this.inferAppVersion(), n = this.inferPublisherId();
|
|
510
|
+
return {
|
|
511
|
+
bundle: e,
|
|
512
|
+
ver: i,
|
|
513
|
+
name: t,
|
|
514
|
+
publisher: {
|
|
515
|
+
id: n,
|
|
516
|
+
domain: window.location.hostname
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* 自动推断bundle/domain
|
|
522
|
+
*/
|
|
523
|
+
inferBundle() {
|
|
524
|
+
var t;
|
|
525
|
+
if (typeof window > "u") return "unknown";
|
|
526
|
+
const e = (t = window.location) == null ? void 0 : t.hostname;
|
|
527
|
+
return e ? e.replace(/^www\./, "").replace(/:\d+$/, "") : "unknown";
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* 自动推断应用名称
|
|
531
|
+
*/
|
|
532
|
+
inferAppName() {
|
|
533
|
+
var n, s;
|
|
534
|
+
if (typeof document > "u") return "Unknown App";
|
|
535
|
+
const e = document.title;
|
|
536
|
+
if (e && e !== "Loading..." && e.length < 100)
|
|
537
|
+
return e;
|
|
538
|
+
const t = (n = document.querySelector('meta[property="og:title"]')) == null ? void 0 : n.getAttribute("content");
|
|
539
|
+
if (t) return t;
|
|
540
|
+
const i = (s = document.querySelector('meta[name="application-name"]')) == null ? void 0 : s.getAttribute("content");
|
|
541
|
+
return i || "Unknown App";
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* 自动推断应用版本
|
|
545
|
+
*/
|
|
546
|
+
inferAppVersion() {
|
|
547
|
+
if (typeof document > "u") return "1.0.0";
|
|
548
|
+
const e = [
|
|
549
|
+
'meta[name="version"]',
|
|
550
|
+
'meta[name="app-version"]',
|
|
551
|
+
'meta[name="application-version"]',
|
|
552
|
+
'link[rel="manifest"]'
|
|
553
|
+
// 从manifest.json读取
|
|
554
|
+
];
|
|
555
|
+
for (const t of e) {
|
|
556
|
+
const i = document.querySelector(t);
|
|
557
|
+
if (i) {
|
|
558
|
+
if (t.includes("manifest"))
|
|
559
|
+
return "1.0.0";
|
|
560
|
+
const n = i.getAttribute("content");
|
|
561
|
+
if (n) return n;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
return "1.0.0";
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* 自动推断publisher ID
|
|
568
|
+
*/
|
|
569
|
+
inferPublisherId() {
|
|
570
|
+
var i, n;
|
|
571
|
+
if (typeof document > "u") return "unknown";
|
|
572
|
+
const e = (i = document.querySelector('meta[name="publisher-id"]')) == null ? void 0 : i.getAttribute("content");
|
|
573
|
+
if (e) return e;
|
|
574
|
+
const t = (n = window.location) == null ? void 0 : n.hostname;
|
|
575
|
+
return t ? t.replace(/\./g, "_") : "unknown";
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Get singleton instance
|
|
579
|
+
*/
|
|
580
|
+
static getInstance() {
|
|
581
|
+
return this.instance || (this.instance = new v()), this.instance;
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Collect all available client information
|
|
585
|
+
* @param options Optional overrides from app
|
|
586
|
+
* @returns ClientInfo object compliant with OpenRTB 2.5/2.6
|
|
587
|
+
*/
|
|
588
|
+
collect(e) {
|
|
589
|
+
const t = Date.now();
|
|
590
|
+
(!this.cachedStaticInfo || t - this.cacheTimestamp > this.CACHE_TTL) && (this.cachedStaticInfo = this.collectStaticInfo(), this.cacheTimestamp = t);
|
|
591
|
+
const i = this.collectDynamicInfo();
|
|
592
|
+
return {
|
|
593
|
+
device: {
|
|
594
|
+
ua: this.cachedStaticInfo.ua,
|
|
595
|
+
os: this.cachedStaticInfo.os,
|
|
596
|
+
osv: this.cachedStaticInfo.osv,
|
|
597
|
+
devicetype: this.cachedStaticInfo.devicetype,
|
|
598
|
+
model: this.cachedStaticInfo.model,
|
|
599
|
+
make: this.cachedStaticInfo.make,
|
|
600
|
+
language: this.cachedStaticInfo.language,
|
|
601
|
+
connectiontype: i.connectiontype ?? 0,
|
|
602
|
+
bandwidth: i.bandwidth,
|
|
603
|
+
...(e == null ? void 0 : e.device) || {}
|
|
604
|
+
// App overrides
|
|
605
|
+
},
|
|
606
|
+
app: {
|
|
607
|
+
...this.inferAppInfo(),
|
|
608
|
+
// 零配置:自动推断
|
|
609
|
+
...(e == null ? void 0 : e.app) || {}
|
|
610
|
+
// App overrides
|
|
611
|
+
},
|
|
612
|
+
user: {
|
|
613
|
+
...this.collectUserInfo(),
|
|
614
|
+
...(e == null ? void 0 : e.user) || {}
|
|
615
|
+
// App overrides
|
|
616
|
+
},
|
|
617
|
+
geo: {
|
|
618
|
+
...this.collectGeoInfo(),
|
|
619
|
+
...(e == null ? void 0 : e.geo) || {}
|
|
620
|
+
// App overrides
|
|
621
|
+
},
|
|
622
|
+
timestamp: t
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Collect static device information (cached)
|
|
627
|
+
* These values don't change during app lifecycle
|
|
628
|
+
*/
|
|
629
|
+
collectStaticInfo() {
|
|
630
|
+
if (typeof navigator > "u")
|
|
631
|
+
return this.getFallbackInfo();
|
|
632
|
+
const e = navigator.userAgent, t = this.detectOS(e), i = this.detectOSVersion(e);
|
|
633
|
+
return {
|
|
634
|
+
ua: e,
|
|
635
|
+
os: t,
|
|
636
|
+
osv: i,
|
|
637
|
+
devicetype: this.detectDeviceType(e),
|
|
638
|
+
model: this.detectModel(e),
|
|
639
|
+
make: this.detectMake(e),
|
|
640
|
+
language: navigator.language || "en-US"
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Collect dynamic device information (not cached)
|
|
645
|
+
* These values may change during app lifecycle
|
|
646
|
+
*/
|
|
647
|
+
collectDynamicInfo() {
|
|
648
|
+
if (typeof navigator > "u")
|
|
649
|
+
return { connectiontype: 0 };
|
|
650
|
+
const e = this.getConnectionInfo();
|
|
651
|
+
return {
|
|
652
|
+
connectiontype: (e == null ? void 0 : e.type) || 0,
|
|
653
|
+
bandwidth: e == null ? void 0 : e.downlink
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Collect user information
|
|
658
|
+
*/
|
|
659
|
+
collectUserInfo() {
|
|
660
|
+
let e;
|
|
661
|
+
try {
|
|
662
|
+
e = localStorage.getItem("chatbox_user_id") || "", e || (e = this.generateUUID(), localStorage.setItem("chatbox_user_id", e));
|
|
663
|
+
} catch {
|
|
664
|
+
e = this.generateUUID();
|
|
665
|
+
}
|
|
666
|
+
return {
|
|
667
|
+
id: e,
|
|
668
|
+
language: (navigator == null ? void 0 : navigator.language) || "en-US"
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Collect geographic information (inferred)
|
|
673
|
+
*/
|
|
674
|
+
collectGeoInfo() {
|
|
675
|
+
if (typeof navigator > "u" || typeof Intl > "u")
|
|
676
|
+
return {};
|
|
677
|
+
const e = Intl.DateTimeFormat().resolvedOptions().timeZone, t = navigator.language;
|
|
678
|
+
return {
|
|
679
|
+
country: this.inferCountry(t, e),
|
|
680
|
+
timezone: e
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
// ============================================================
|
|
684
|
+
// Detection Methods
|
|
685
|
+
// ============================================================
|
|
686
|
+
/**
|
|
687
|
+
* Detect Operating System
|
|
688
|
+
*/
|
|
689
|
+
detectOS(e) {
|
|
690
|
+
return e ? /iPad|iPhone|iPod/.test(e) ? "iOS" : /Android/.test(e) ? "Android" : /Windows/.test(e) ? "Windows" : /Macintosh|Mac OS/.test(e) ? "macOS" : /Linux/.test(e) ? "Linux" : /CrOS/.test(e) ? "Chrome OS" : "Unknown" : "Unknown";
|
|
691
|
+
}
|
|
692
|
+
/**
|
|
693
|
+
* Detect OS Version
|
|
694
|
+
*/
|
|
695
|
+
detectOSVersion(e) {
|
|
696
|
+
if (!e) return "Unknown";
|
|
697
|
+
const t = e.match(/OS (\d+)_(\d+)_?(\d+)?/);
|
|
698
|
+
if (t)
|
|
699
|
+
return `${t[1]}.${t[2]}${t[3] ? "." + t[3] : ""}`;
|
|
700
|
+
const i = e.match(/Android (\d+)\.(\d+)/);
|
|
701
|
+
if (i)
|
|
702
|
+
return `${i[1]}.${i[2]}`;
|
|
703
|
+
const n = e.match(/Windows NT (\d+\.\d+)/);
|
|
704
|
+
if (n)
|
|
705
|
+
return n[1];
|
|
706
|
+
const s = e.match(/Mac OS X (\d+[._]\d+)/);
|
|
707
|
+
return s ? s[1].replace("_", ".") : "Unknown";
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Detect Device Type (OpenRTB standard)
|
|
711
|
+
* 0 = Unknown/Other
|
|
712
|
+
* 1 = Mobile/Phone
|
|
713
|
+
* 2 = Tablet
|
|
714
|
+
* 3 = Personal Computer
|
|
715
|
+
* 4 = Connected TV
|
|
716
|
+
* 5 = Set Top Box
|
|
717
|
+
*/
|
|
718
|
+
detectDeviceType(e) {
|
|
719
|
+
return e ? /iPad|Tablet/i.test(e) || /Android/.test(e) && !/Mobile/.test(e) ? 2 : /iPhone|iPod|Mobile|Android|webOS|BlackBerry|Opera Mini|IEMobile/i.test(e) ? 1 : /Windows|Macintosh|Linux|x86_64/i.test(e) ? 3 : /SmartTV|TV|WebTV|GoogleTV|AppleTV/i.test(e) ? 4 : 0 : 0;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Detect Device Model
|
|
723
|
+
*/
|
|
724
|
+
detectModel(e) {
|
|
725
|
+
if (!e) return;
|
|
726
|
+
const t = e.match(/iPhone(?:;\s*[^\)]*)?\s*(\d+,\d)?/);
|
|
727
|
+
if (t) return `iPhone${t[1] || ""}`;
|
|
728
|
+
if (/iPad/.test(e)) return "iPad";
|
|
729
|
+
const i = e.match(/Samsung-.*(SM-\w+)/);
|
|
730
|
+
if (i) return i[1];
|
|
731
|
+
if (/Pixel/.test(e)) return "Google Pixel";
|
|
732
|
+
const n = e.match(/(BARC-|HUAWEI-)?([A-Z]{2}\-\w{4})/);
|
|
733
|
+
if (n) return n[2];
|
|
734
|
+
const s = e.match(/(MI|Redmi|POCO)\s([\w\s]+)/);
|
|
735
|
+
if (s) return s[1] + " " + s[2];
|
|
736
|
+
if (/OnePlus/.test(e)) {
|
|
737
|
+
const r = e.match(/OnePlus\s([A-Z\d]+)/);
|
|
738
|
+
return r ? "OnePlus " + r[1] : "OnePlus";
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Detect Device Make
|
|
743
|
+
*/
|
|
744
|
+
detectMake(e) {
|
|
745
|
+
if (e) {
|
|
746
|
+
if (/iPad|iPhone|iPod/.test(e)) return "Apple";
|
|
747
|
+
if (/Samsung/.test(e)) return "Samsung";
|
|
748
|
+
if (/Pixel/.test(e)) return "Google";
|
|
749
|
+
if (/Huawei|HONOR/.test(e)) return "Huawei";
|
|
750
|
+
if (/Xiaomi|Redmi|POCO/.test(e)) return "Xiaomi";
|
|
751
|
+
if (/OnePlus/.test(e)) return "OnePlus";
|
|
752
|
+
if (/Sony/.test(e)) return "Sony";
|
|
753
|
+
if (/LG/.test(e)) return "LG";
|
|
754
|
+
if (/Motorola|Moto/.test(e)) return "Motorola";
|
|
755
|
+
if (/Nokia/.test(e)) return "Nokia";
|
|
756
|
+
if (/HTC/.test(e)) return "HTC";
|
|
757
|
+
if (/OPPO/.test(e)) return "OPPO";
|
|
758
|
+
if (/vivo/.test(e)) return "vivo";
|
|
759
|
+
if (/Realme/.test(e)) return "Realme";
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Get Network Connection Info
|
|
764
|
+
*/
|
|
765
|
+
getConnectionInfo() {
|
|
766
|
+
if (typeof navigator > "u") return null;
|
|
767
|
+
const e = navigator, t = e.connection || e.mozConnection || e.webkitConnection;
|
|
768
|
+
return t ? {
|
|
769
|
+
type: this.mapConnectionType(t.effectiveType),
|
|
770
|
+
downlink: t.downlink,
|
|
771
|
+
rtt: t.rtt
|
|
772
|
+
} : null;
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Map Network Information API type to OpenRTB
|
|
776
|
+
* 0 = Unknown
|
|
777
|
+
* 1 = Ethernet
|
|
778
|
+
* 2 = WiFi
|
|
779
|
+
* 3 = 2G
|
|
780
|
+
* 4 = 3G
|
|
781
|
+
* 5 = 4G
|
|
782
|
+
* 6 = 5G
|
|
783
|
+
*/
|
|
784
|
+
mapConnectionType(e) {
|
|
785
|
+
if (!e) return 0;
|
|
786
|
+
switch (e.toLowerCase()) {
|
|
787
|
+
case "slow-2g":
|
|
788
|
+
case "2g":
|
|
789
|
+
return 3;
|
|
790
|
+
case "3g":
|
|
791
|
+
return 4;
|
|
792
|
+
case "4g":
|
|
793
|
+
return 5;
|
|
794
|
+
default:
|
|
795
|
+
return 0;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Infer country from language and timezone
|
|
800
|
+
*/
|
|
801
|
+
inferCountry(e, t) {
|
|
802
|
+
if (!e && !t) return;
|
|
803
|
+
const i = {
|
|
804
|
+
"Asia/Shanghai": "CN",
|
|
805
|
+
"Asia/Hong_Kong": "HK",
|
|
806
|
+
"Asia/Taipei": "TW",
|
|
807
|
+
"Asia/Tokyo": "JP",
|
|
808
|
+
"Asia/Seoul": "KR",
|
|
809
|
+
"Asia/Singapore": "SG",
|
|
810
|
+
"Asia/Dubai": "AE",
|
|
811
|
+
"Asia/Kolkata": "IN",
|
|
812
|
+
"Asia/Jakarta": "ID",
|
|
813
|
+
"Asia/Manila": "PH",
|
|
814
|
+
"Asia/Bangkok": "TH",
|
|
815
|
+
"Asia/Kuala_Lumpur": "MY",
|
|
816
|
+
"America/New_York": "US",
|
|
817
|
+
"America/Chicago": "US",
|
|
818
|
+
"America/Denver": "US",
|
|
819
|
+
"America/Los_Angeles": "US",
|
|
820
|
+
"America/Toronto": "CA",
|
|
821
|
+
"America/Vancouver": "CA",
|
|
822
|
+
"America/Mexico_City": "MX",
|
|
823
|
+
"America/Sao_Paulo": "BR",
|
|
824
|
+
"America/Buenos_Aires": "AR",
|
|
825
|
+
"Europe/London": "GB",
|
|
826
|
+
"Europe/Paris": "FR",
|
|
827
|
+
"Europe/Berlin": "DE",
|
|
828
|
+
"Europe/Madrid": "ES",
|
|
829
|
+
"Europe/Rome": "IT",
|
|
830
|
+
"Europe/Amsterdam": "NL",
|
|
831
|
+
"Europe/Brussels": "BE",
|
|
832
|
+
"Europe/Zurich": "CH",
|
|
833
|
+
"Europe/Vienna": "AT",
|
|
834
|
+
"Europe/Stockholm": "SE",
|
|
835
|
+
"Europe/Oslo": "NO",
|
|
836
|
+
"Europe/Copenhagen": "DK",
|
|
837
|
+
"Europe/Helsinki": "FI",
|
|
838
|
+
"Europe/Dublin": "IE",
|
|
839
|
+
"Europe/Lisbon": "PT",
|
|
840
|
+
"Europe/Athens": "GR",
|
|
841
|
+
"Europe/Prague": "CZ",
|
|
842
|
+
"Europe/Warsaw": "PL",
|
|
843
|
+
"Europe/Budapest": "HU",
|
|
844
|
+
"Europe/Bucharest": "RO",
|
|
845
|
+
"Australia/Sydney": "AU",
|
|
846
|
+
"Pacific/Auckland": "NZ"
|
|
847
|
+
};
|
|
848
|
+
if (t && i[t])
|
|
849
|
+
return i[t];
|
|
850
|
+
if (e) {
|
|
851
|
+
const n = e.split("-")[1];
|
|
852
|
+
if (n)
|
|
853
|
+
return n.toUpperCase();
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Generate UUID v4
|
|
858
|
+
*/
|
|
859
|
+
generateUUID() {
|
|
860
|
+
return typeof crypto < "u" && crypto.randomUUID ? crypto.randomUUID() : "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (e) => {
|
|
861
|
+
const t = Math.random() * 16 | 0;
|
|
862
|
+
return (e === "x" ? t : t & 3 | 8).toString(16);
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Get fallback info for SSR or restricted environments
|
|
867
|
+
*/
|
|
868
|
+
getFallbackInfo() {
|
|
869
|
+
return {
|
|
870
|
+
ua: "Unknown",
|
|
871
|
+
os: "Unknown",
|
|
872
|
+
osv: "Unknown",
|
|
873
|
+
devicetype: 0,
|
|
874
|
+
language: "en-US"
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Clear cache (call when device state changes)
|
|
879
|
+
*/
|
|
880
|
+
clearCache() {
|
|
881
|
+
this.cachedStaticInfo = null, this.cacheTimestamp = 0;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
const w = () => v.getInstance();
|
|
885
|
+
function h(o) {
|
|
886
|
+
return w().collect(o);
|
|
887
|
+
}
|
|
888
|
+
function N() {
|
|
889
|
+
return h().device;
|
|
890
|
+
}
|
|
891
|
+
function U() {
|
|
892
|
+
return h().user;
|
|
893
|
+
}
|
|
894
|
+
function H() {
|
|
895
|
+
return h().app;
|
|
896
|
+
}
|
|
897
|
+
function G() {
|
|
898
|
+
return h().geo;
|
|
899
|
+
}
|
|
900
|
+
function j() {
|
|
901
|
+
return U().id;
|
|
902
|
+
}
|
|
903
|
+
function W() {
|
|
904
|
+
w().clearCache();
|
|
905
|
+
}
|
|
906
|
+
function D(o) {
|
|
907
|
+
const e = h(o);
|
|
908
|
+
return JSON.stringify(e, null, 2);
|
|
909
|
+
}
|
|
910
|
+
function Q() {
|
|
911
|
+
var t;
|
|
912
|
+
const o = h();
|
|
913
|
+
return [
|
|
914
|
+
o.device.os,
|
|
915
|
+
o.device.osv,
|
|
916
|
+
o.app.name,
|
|
917
|
+
o.user.id.slice(0, 8) + "...",
|
|
918
|
+
((t = o.geo) == null ? void 0 : t.country) || "Unknown"
|
|
919
|
+
].join(" / ");
|
|
920
|
+
}
|
|
921
|
+
function z(o) {
|
|
922
|
+
var e, t;
|
|
923
|
+
return {
|
|
924
|
+
id: o.original.id,
|
|
925
|
+
type: o.original.type,
|
|
926
|
+
score: o.original.score,
|
|
927
|
+
source: "internal",
|
|
928
|
+
// Default source
|
|
929
|
+
content: {
|
|
930
|
+
title: o.adapted.title,
|
|
931
|
+
body: o.adapted.body,
|
|
932
|
+
image: (e = o.adapted.image) == null ? void 0 : e.url,
|
|
933
|
+
cta_text: o.adapted.ctaText,
|
|
934
|
+
price: (t = o.adapted.price) == null ? void 0 : t.display,
|
|
935
|
+
rating: o.adapted.rating,
|
|
936
|
+
brand: o.adapted.brand
|
|
937
|
+
},
|
|
938
|
+
tracking: {
|
|
939
|
+
click_url: o.tracking.clickUrl,
|
|
940
|
+
impression_url: o.tracking.impressionUrl
|
|
941
|
+
},
|
|
942
|
+
metadata: {
|
|
943
|
+
viewToken: o.tracking.viewToken,
|
|
944
|
+
styling: o.adapted.styling
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
async function _(o, e = {}) {
|
|
949
|
+
const t = e.apiBaseUrl || "/api/v1", n = w().collect(), s = {
|
|
950
|
+
...o,
|
|
951
|
+
clientInfo: n
|
|
952
|
+
// Auto-injected
|
|
953
|
+
}, r = {
|
|
954
|
+
"Content-Type": "application/json"
|
|
955
|
+
};
|
|
956
|
+
e.apiKey && (r["X-API-Key"] = e.apiKey);
|
|
957
|
+
const a = await fetch(`${t}/ads/request`, {
|
|
958
|
+
method: "POST",
|
|
959
|
+
headers: r,
|
|
960
|
+
body: JSON.stringify(s)
|
|
961
|
+
});
|
|
962
|
+
if (!a.ok)
|
|
963
|
+
throw new Error(`Ad request failed: ${a.status} ${a.statusText}`);
|
|
964
|
+
const c = await a.json();
|
|
965
|
+
if (!c.success)
|
|
966
|
+
throw new Error(c.error || "Ad request failed");
|
|
967
|
+
return c.data;
|
|
968
|
+
}
|
|
969
|
+
class F {
|
|
970
|
+
constructor(e) {
|
|
971
|
+
this.slots = {}, this.isLoading = !1, this.currentRequestId = null, this.adsAnalyticsMap = /* @__PURE__ */ new Map(), this.config = e, this.enabled = e.enabled !== !1, this.eventBus = new I(), typeof window < "u" && (window.__AD_CONFIG__ = {
|
|
972
|
+
...window.__AD_CONFIG__ || {},
|
|
973
|
+
apiKey: e.apiKey,
|
|
974
|
+
apiBaseUrl: e.apiBaseUrl
|
|
975
|
+
}), this.config.debug && console.log("[AdManager] Initialized with config:", {
|
|
976
|
+
apiBaseUrl: e.apiBaseUrl,
|
|
977
|
+
hasApiKey: !!e.apiKey,
|
|
978
|
+
slotCount: e.slots.length,
|
|
979
|
+
enabled: this.enabled
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Request ads for all configured slots
|
|
984
|
+
* ClientInfo is automatically collected and injected
|
|
985
|
+
*
|
|
986
|
+
* @param context - Conversation and user context
|
|
987
|
+
* @returns Promise resolving to ad response batch
|
|
988
|
+
*/
|
|
989
|
+
async requestAds(e) {
|
|
990
|
+
if (!this.enabled)
|
|
991
|
+
throw this.config.debug && console.warn("[AdManager] Ads are disabled, skipping request"), new Error("Ads are disabled");
|
|
992
|
+
if (this.isLoading)
|
|
993
|
+
throw this.config.debug && console.warn("[AdManager] Request already in progress"), new Error("Request already in progress");
|
|
994
|
+
this.isLoading = !0, this.eventBus.emit("adsLoading"), this.config.debug && console.log("[AdManager] Starting ad request...");
|
|
995
|
+
try {
|
|
996
|
+
const t = await _(
|
|
997
|
+
{
|
|
998
|
+
conversationContext: e.conversationContext,
|
|
999
|
+
userContext: e.userContext,
|
|
1000
|
+
slots: this.config.slots
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
apiBaseUrl: this.config.apiBaseUrl,
|
|
1004
|
+
apiKey: this.config.apiKey
|
|
1005
|
+
}
|
|
1006
|
+
);
|
|
1007
|
+
this.currentRequestId = t.requestId, this.adsAnalyticsMap.clear(), t.slots.forEach((n) => {
|
|
1008
|
+
n.ads.forEach((s, r) => {
|
|
1009
|
+
const a = s.original.id;
|
|
1010
|
+
this.adsAnalyticsMap.set(a, {
|
|
1011
|
+
requestId: t.requestId,
|
|
1012
|
+
slotId: n.slotId,
|
|
1013
|
+
position: r,
|
|
1014
|
+
totalAds: n.ads.length
|
|
1015
|
+
});
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
1018
|
+
const i = {};
|
|
1019
|
+
return t.slots.forEach((n) => {
|
|
1020
|
+
i[n.slotId] = n;
|
|
1021
|
+
}), this.slots = i, this.eventBus.emit("adsUpdated", this.slots), this.config.debug && console.log("[AdManager] Ads received:", {
|
|
1022
|
+
slotCount: Object.keys(this.slots).length,
|
|
1023
|
+
slots: Object.keys(this.slots)
|
|
1024
|
+
}), t;
|
|
1025
|
+
} catch (t) {
|
|
1026
|
+
const i = t;
|
|
1027
|
+
throw this.eventBus.emit("adsError", i), i;
|
|
1028
|
+
} finally {
|
|
1029
|
+
this.isLoading = !1;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
/**
|
|
1033
|
+
* Render a single ad from a slot into a container element
|
|
1034
|
+
*
|
|
1035
|
+
* @param slotId - Slot ID to render
|
|
1036
|
+
* @param container - DOM element to render into
|
|
1037
|
+
* @param options - Rendering options
|
|
1038
|
+
* @returns Rendered HTMLElement or null if slot/ad not found
|
|
1039
|
+
*/
|
|
1040
|
+
render(e, t, i = {}) {
|
|
1041
|
+
const n = this.slots[e];
|
|
1042
|
+
if (!n || !n.ads || n.ads.length === 0)
|
|
1043
|
+
return this.config.debug && console.warn("[AdManager] No ads in slot:", e), null;
|
|
1044
|
+
const s = i.adIndex ?? 0, r = n.ads[s];
|
|
1045
|
+
if (!r)
|
|
1046
|
+
return this.config.debug && console.warn(`[AdManager] Ad at index ${s} not found in slot:`, e), null;
|
|
1047
|
+
const a = this.adsAnalyticsMap.get(r.original.id), c = this.config.slots.find((u) => u.slotId === e), l = (c == null ? void 0 : c.format) || "action_card";
|
|
1048
|
+
t.innerHTML = "";
|
|
1049
|
+
const d = {
|
|
1050
|
+
analytics: a,
|
|
1051
|
+
variant: i.variant,
|
|
1052
|
+
onClick: i.onClick,
|
|
1053
|
+
onImpression: i.onImpression
|
|
1054
|
+
};
|
|
1055
|
+
return l === "suffix" ? g.renderSuffixAd(r, t, d) : l === "source" ? g.renderSponsoredSource(r, t, d) : l === "lead_gen" ? g.renderLeadGenAd(r, t, d) : g.renderActionCard(r, t, d);
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Get ad slots data
|
|
1059
|
+
*/
|
|
1060
|
+
getSlots(e) {
|
|
1061
|
+
return e ? this.slots[e] : this.slots;
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Check if a slot has ads
|
|
1065
|
+
*/
|
|
1066
|
+
hasAds(e) {
|
|
1067
|
+
var i;
|
|
1068
|
+
const t = this.slots[e];
|
|
1069
|
+
return (t == null ? void 0 : t.status) === "filled" && ((i = t.ads) == null ? void 0 : i.length) > 0;
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Get ads for a specific slot
|
|
1073
|
+
*/
|
|
1074
|
+
getAds(e) {
|
|
1075
|
+
var t;
|
|
1076
|
+
return ((t = this.slots[e]) == null ? void 0 : t.ads) || [];
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Check if currently loading
|
|
1080
|
+
*/
|
|
1081
|
+
getLoadingStatus() {
|
|
1082
|
+
return this.isLoading;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Enable or disable ads
|
|
1086
|
+
*/
|
|
1087
|
+
setEnabled(e) {
|
|
1088
|
+
this.enabled = e, this.config.debug && console.log("[AdManager] Ads", e ? "enabled" : "disabled");
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Update configuration
|
|
1092
|
+
*/
|
|
1093
|
+
updateConfig(e) {
|
|
1094
|
+
this.config = { ...this.config, ...e }, this.config.debug && console.log("[AdManager] Config updated:", e);
|
|
1095
|
+
}
|
|
1096
|
+
/**
|
|
1097
|
+
* Clear all slot data
|
|
1098
|
+
*/
|
|
1099
|
+
clearSlots() {
|
|
1100
|
+
this.slots = {}, this.currentRequestId = null, this.adsAnalyticsMap.clear(), this.config.debug && console.log("[AdManager] Slots and analytics cleared");
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Register event listener
|
|
1104
|
+
*/
|
|
1105
|
+
on(e, t) {
|
|
1106
|
+
this.eventBus.on(e, t);
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Remove event listener
|
|
1110
|
+
*/
|
|
1111
|
+
off(e, t) {
|
|
1112
|
+
this.eventBus.off(e, t);
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Remove all event listeners
|
|
1116
|
+
*/
|
|
1117
|
+
removeAllListeners(e) {
|
|
1118
|
+
this.eventBus.removeAllListeners(e);
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Get current requestId
|
|
1122
|
+
*/
|
|
1123
|
+
getCurrentRequestId() {
|
|
1124
|
+
return this.currentRequestId;
|
|
1125
|
+
}
|
|
1126
|
+
/**
|
|
1127
|
+
* Get analytics info for a single ad
|
|
1128
|
+
*/
|
|
1129
|
+
getAdAnalytics(e) {
|
|
1130
|
+
return this.adsAnalyticsMap.get(e) || null;
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get analytics info for multiple ads
|
|
1134
|
+
*/
|
|
1135
|
+
getAdsAnalytics(e) {
|
|
1136
|
+
const t = /* @__PURE__ */ new Map();
|
|
1137
|
+
return e.forEach((i) => {
|
|
1138
|
+
const n = this.adsAnalyticsMap.get(i);
|
|
1139
|
+
n && t.set(i, n);
|
|
1140
|
+
}), t;
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Track ad click using Analytics API
|
|
1144
|
+
*/
|
|
1145
|
+
async trackAdClick(e, t, i) {
|
|
1146
|
+
const n = this.adsAnalyticsMap.get(e);
|
|
1147
|
+
if (!n || !this.currentRequestId)
|
|
1148
|
+
return this.config.debug && console.warn("[AdManager] No analytics info for ad:", e), null;
|
|
1149
|
+
const s = m(), r = await b({
|
|
1150
|
+
requestId: this.currentRequestId,
|
|
1151
|
+
adId: e,
|
|
1152
|
+
destinationUrl: t,
|
|
1153
|
+
sessionId: s,
|
|
1154
|
+
slotId: n.slotId,
|
|
1155
|
+
adTitle: i == null ? void 0 : i.title,
|
|
1156
|
+
format: i == null ? void 0 : i.format,
|
|
1157
|
+
source: i == null ? void 0 : i.source
|
|
1158
|
+
});
|
|
1159
|
+
return r && (this.eventBus.emit("adClicked", e, n.slotId), this.config.debug && console.log("[AdManager] Click tracked via Analytics API:", r.eventId)), r;
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Track ad impression using Analytics API
|
|
1163
|
+
*/
|
|
1164
|
+
async trackAdImpressionAPI(e, t) {
|
|
1165
|
+
const i = this.adsAnalyticsMap.get(e);
|
|
1166
|
+
if (!i || !this.currentRequestId)
|
|
1167
|
+
return this.config.debug && console.warn("[AdManager] No analytics info for ad:", e), null;
|
|
1168
|
+
const n = m(), s = await y({
|
|
1169
|
+
requestId: this.currentRequestId,
|
|
1170
|
+
adId: e,
|
|
1171
|
+
slotId: i.slotId,
|
|
1172
|
+
position: i.position,
|
|
1173
|
+
totalAds: i.totalAds,
|
|
1174
|
+
sessionId: n,
|
|
1175
|
+
adTitle: t == null ? void 0 : t.title,
|
|
1176
|
+
format: t == null ? void 0 : t.format,
|
|
1177
|
+
source: t == null ? void 0 : t.source,
|
|
1178
|
+
viewToken: t == null ? void 0 : t.viewToken
|
|
1179
|
+
});
|
|
1180
|
+
return s && (this.eventBus.emit("adImpression", e, i.slotId), this.config.debug && console.log("[AdManager] Impression tracked via Analytics API:", s.eventId)), s;
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Destroy the manager and clean up
|
|
1184
|
+
*/
|
|
1185
|
+
destroy() {
|
|
1186
|
+
this.removeAllListeners(), this.clearSlots(), this.config.debug && console.log("[AdManager] Destroyed");
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function x() {
|
|
1190
|
+
if (typeof window < "u") {
|
|
1191
|
+
const o = window.__AD_CONFIG__;
|
|
1192
|
+
if (o != null && o.apiKey)
|
|
1193
|
+
return o.apiKey;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
class O {
|
|
1197
|
+
constructor(e = {}, t = "/api/v1") {
|
|
1198
|
+
var i, n;
|
|
1199
|
+
this.observers = /* @__PURE__ */ new Map(), this.metrics = /* @__PURE__ */ new Map(), this.timers = /* @__PURE__ */ new Map(), this.batchTimers = /* @__PURE__ */ new Map(), this.eventQueue = /* @__PURE__ */ new Map(), this.isTracking = /* @__PURE__ */ new Map(), this.config = {
|
|
1200
|
+
minVisiblePercentage: e.minVisiblePercentage ?? 50,
|
|
1201
|
+
minViewableDuration: e.minViewableDuration ?? 1e3,
|
|
1202
|
+
maxTrackingDuration: e.maxTrackingDuration ?? 6e4,
|
|
1203
|
+
batchConfig: {
|
|
1204
|
+
maxBatchSize: ((i = e.batchConfig) == null ? void 0 : i.maxBatchSize) ?? 5,
|
|
1205
|
+
maxBatchWaitMs: ((n = e.batchConfig) == null ? void 0 : n.maxBatchWaitMs) ?? 1e4
|
|
1206
|
+
},
|
|
1207
|
+
debug: e.debug ?? !1
|
|
1208
|
+
}, this.baseUrl = t;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Start tracking viewability for an ad element
|
|
1212
|
+
*/
|
|
1213
|
+
startTracking(e, t, i, n, s) {
|
|
1214
|
+
if (this.isTracking.get(e)) {
|
|
1215
|
+
this.log(`[ViewabilityTracker] Already tracking ${e}, skipping`);
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
this.log(`[ViewabilityTracker] Starting tracking for ${e}`);
|
|
1219
|
+
const r = {
|
|
1220
|
+
visiblePercentage: 0,
|
|
1221
|
+
maxVisiblePercentage: 0,
|
|
1222
|
+
totalVisibleTimeMs: 0,
|
|
1223
|
+
currentVisibleTimeMs: 0,
|
|
1224
|
+
isViewable: !1,
|
|
1225
|
+
viewableAt: null,
|
|
1226
|
+
enteredViewportAt: null,
|
|
1227
|
+
enterCount: 0
|
|
1228
|
+
};
|
|
1229
|
+
this.metrics.set(e, r), this.isTracking.set(e, !0), this.eventQueue.set(e, []);
|
|
1230
|
+
const a = new IntersectionObserver(
|
|
1231
|
+
(l) => this.handleIntersection(e, l[0], i, n, s),
|
|
1232
|
+
{
|
|
1233
|
+
threshold: this.createThresholds()
|
|
1234
|
+
}
|
|
1235
|
+
);
|
|
1236
|
+
a.observe(t), this.observers.set(e, a), this.startMonitoring(e, i, n, s);
|
|
1237
|
+
const c = setTimeout(() => {
|
|
1238
|
+
this.endTracking(e, i, n, s);
|
|
1239
|
+
}, this.config.maxTrackingDuration);
|
|
1240
|
+
this.timers.set(e, c);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Stop tracking an ad element
|
|
1244
|
+
*/
|
|
1245
|
+
stopTracking(e) {
|
|
1246
|
+
this.log(`[ViewabilityTracker] Manual stop tracking for ${e}`), this.cleanup(e);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Get current metrics for an ad
|
|
1250
|
+
*/
|
|
1251
|
+
getMetrics(e) {
|
|
1252
|
+
return this.metrics.get(e);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Handle IntersectionObserver callback
|
|
1256
|
+
* Event-driven: Only process when state actually changes
|
|
1257
|
+
*/
|
|
1258
|
+
handleIntersection(e, t, i, n, s) {
|
|
1259
|
+
const r = this.metrics.get(e);
|
|
1260
|
+
if (!r || !this.isTracking.get(e)) return;
|
|
1261
|
+
const a = r.isViewable, c = Math.round(t.intersectionRatio * 100);
|
|
1262
|
+
r.visiblePercentage = c, r.maxVisiblePercentage = Math.max(r.maxVisiblePercentage, c);
|
|
1263
|
+
const l = Date.now(), d = c >= this.config.minVisiblePercentage;
|
|
1264
|
+
if (d && !r.enteredViewportAt && (r.enteredViewportAt = l, r.enterCount++, this.log(`[ViewabilityTracker] ${e} entered viewport at ${l}`), this.queueEvent(e, {
|
|
1265
|
+
adId: e,
|
|
1266
|
+
sessionId: i,
|
|
1267
|
+
requestId: n,
|
|
1268
|
+
viewToken: s,
|
|
1269
|
+
eventType: "enter_viewport",
|
|
1270
|
+
visiblePercentage: c,
|
|
1271
|
+
maxVisiblePercentage: r.maxVisiblePercentage,
|
|
1272
|
+
totalVisibleTimeMs: r.totalVisibleTimeMs,
|
|
1273
|
+
isViewable: !1,
|
|
1274
|
+
timestamp: l
|
|
1275
|
+
})), !d && r.enteredViewportAt) {
|
|
1276
|
+
const u = l - r.enteredViewportAt;
|
|
1277
|
+
r.totalVisibleTimeMs += u, r.enteredViewportAt = null, r.currentVisibleTimeMs = 0, this.log(`[ViewabilityTracker] ${e} exited viewport, total visible: ${r.totalVisibleTimeMs}ms`), this.queueEvent(e, {
|
|
1278
|
+
adId: e,
|
|
1279
|
+
sessionId: i,
|
|
1280
|
+
requestId: n,
|
|
1281
|
+
viewToken: s,
|
|
1282
|
+
eventType: "exit_viewport",
|
|
1283
|
+
visiblePercentage: c,
|
|
1284
|
+
maxVisiblePercentage: r.maxVisiblePercentage,
|
|
1285
|
+
totalVisibleTimeMs: r.totalVisibleTimeMs,
|
|
1286
|
+
isViewable: r.isViewable,
|
|
1287
|
+
timestamp: l
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
r.isViewable !== a && r.isViewable && this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`), this.metrics.set(e, r);
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Start monitoring loop for tracking duration
|
|
1294
|
+
* Runs every 100ms to track continuous visible time
|
|
1295
|
+
*/
|
|
1296
|
+
startMonitoring(e, t, i, n) {
|
|
1297
|
+
const r = setInterval(() => {
|
|
1298
|
+
const a = this.metrics.get(e);
|
|
1299
|
+
if (!a || !this.isTracking.get(e)) {
|
|
1300
|
+
clearInterval(r);
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1303
|
+
const c = Date.now(), l = a.isViewable;
|
|
1304
|
+
if (a.enteredViewportAt) {
|
|
1305
|
+
const d = c - a.enteredViewportAt;
|
|
1306
|
+
a.currentVisibleTimeMs = d, d >= this.config.minViewableDuration && a.visiblePercentage >= this.config.minVisiblePercentage && (a.isViewable = !0, l || (a.viewableAt = a.enteredViewportAt, this.log(`[ViewabilityTracker] ${e} BECAME VIEWABLE at ${a.viewableAt}`), this.queueEvent(e, {
|
|
1307
|
+
adId: e,
|
|
1308
|
+
sessionId: t,
|
|
1309
|
+
requestId: i,
|
|
1310
|
+
viewToken: n,
|
|
1311
|
+
eventType: "become_viewable",
|
|
1312
|
+
visiblePercentage: a.visiblePercentage,
|
|
1313
|
+
maxVisiblePercentage: a.maxVisiblePercentage,
|
|
1314
|
+
totalVisibleTimeMs: a.totalVisibleTimeMs,
|
|
1315
|
+
isViewable: !0,
|
|
1316
|
+
timestamp: c
|
|
1317
|
+
})));
|
|
1318
|
+
}
|
|
1319
|
+
a.enteredViewportAt && (a.totalVisibleTimeMs += 100), this.metrics.set(e, a);
|
|
1320
|
+
}, 100);
|
|
1321
|
+
this.timers.set(`${e}_monitoring`, r);
|
|
1322
|
+
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Queue an event to be sent (batched)
|
|
1325
|
+
*/
|
|
1326
|
+
queueEvent(e, t) {
|
|
1327
|
+
var s, r;
|
|
1328
|
+
const i = this.eventQueue.get(e);
|
|
1329
|
+
if (!i) {
|
|
1330
|
+
this.log(`[ViewabilityTracker] No queue found for ${e}, skipping event`);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
i.push(t), this.log(`[ViewabilityTracker] Queued ${t.eventType} for ${e} (queue size: ${i.length})`);
|
|
1334
|
+
const n = ((s = this.config.batchConfig) == null ? void 0 : s.maxBatchSize) ?? 5;
|
|
1335
|
+
if (i.length >= n)
|
|
1336
|
+
this.flushQueue(e);
|
|
1337
|
+
else {
|
|
1338
|
+
const a = this.batchTimers.get(e);
|
|
1339
|
+
a && clearTimeout(a);
|
|
1340
|
+
const c = ((r = this.config.batchConfig) == null ? void 0 : r.maxBatchWaitMs) ?? 1e4, l = setTimeout(() => {
|
|
1341
|
+
this.flushQueue(e);
|
|
1342
|
+
}, c);
|
|
1343
|
+
this.batchTimers.set(e, l);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
/**
|
|
1347
|
+
* Flush the event queue and send to server
|
|
1348
|
+
*/
|
|
1349
|
+
async flushQueue(e) {
|
|
1350
|
+
const t = this.eventQueue.get(e);
|
|
1351
|
+
if (!t || t.length === 0) return;
|
|
1352
|
+
const i = [...t];
|
|
1353
|
+
t.length = 0, this.log(`[ViewabilityTracker] Flushing ${i.length} events for ${e}`);
|
|
1354
|
+
try {
|
|
1355
|
+
const n = {
|
|
1356
|
+
"Content-Type": "application/json"
|
|
1357
|
+
}, s = x();
|
|
1358
|
+
s && (n["x-api-key"] = s), await fetch(`${this.baseUrl}/ads/viewability/batch`, {
|
|
1359
|
+
method: "POST",
|
|
1360
|
+
headers: n,
|
|
1361
|
+
body: JSON.stringify({ events: i })
|
|
1362
|
+
}), this.log(`[ViewabilityTracker] Successfully sent ${i.length} events for ${e}`);
|
|
1363
|
+
} catch (n) {
|
|
1364
|
+
this.log(`[ViewabilityTracker] Failed to send events for ${e}:`, n), t.unshift(...i);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* End tracking and send final metrics
|
|
1369
|
+
*/
|
|
1370
|
+
endTracking(e, t, i, n) {
|
|
1371
|
+
const s = this.metrics.get(e);
|
|
1372
|
+
if (!s) return;
|
|
1373
|
+
this.log(`[ViewabilityTracker] Ending tracking for ${e}`), this.flushQueue(e);
|
|
1374
|
+
const r = Date.now();
|
|
1375
|
+
this.queueEvent(e, {
|
|
1376
|
+
adId: e,
|
|
1377
|
+
sessionId: t,
|
|
1378
|
+
requestId: i,
|
|
1379
|
+
viewToken: n,
|
|
1380
|
+
eventType: "end_tracking",
|
|
1381
|
+
visiblePercentage: s.visiblePercentage,
|
|
1382
|
+
maxVisiblePercentage: s.maxVisiblePercentage,
|
|
1383
|
+
totalVisibleTimeMs: s.totalVisibleTimeMs,
|
|
1384
|
+
isViewable: s.isViewable,
|
|
1385
|
+
timestamp: r
|
|
1386
|
+
}), this.flushQueue(e), this.cleanup(e);
|
|
1387
|
+
}
|
|
1388
|
+
/**
|
|
1389
|
+
* Cleanup resources for an ad
|
|
1390
|
+
*/
|
|
1391
|
+
cleanup(e) {
|
|
1392
|
+
const t = this.observers.get(e);
|
|
1393
|
+
t && (t.disconnect(), this.observers.delete(e));
|
|
1394
|
+
const i = this.timers.get(e);
|
|
1395
|
+
i && (clearTimeout(i), this.timers.delete(e));
|
|
1396
|
+
const n = this.timers.get(`${e}_monitoring`);
|
|
1397
|
+
n && (clearInterval(n), this.timers.delete(`${e}_monitoring`));
|
|
1398
|
+
const s = this.batchTimers.get(e);
|
|
1399
|
+
s && (clearTimeout(s), this.batchTimers.delete(e)), this.flushQueue(e), this.isTracking.delete(e), this.metrics.delete(e), this.eventQueue.delete(e);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Create thresholds for IntersectionObserver
|
|
1403
|
+
*/
|
|
1404
|
+
createThresholds() {
|
|
1405
|
+
return [0, 0.25, 0.5, 0.75, 0.9, 1];
|
|
1406
|
+
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Stop all tracking
|
|
1409
|
+
*/
|
|
1410
|
+
stopAll() {
|
|
1411
|
+
const e = Array.from(this.isTracking.keys());
|
|
1412
|
+
for (const t of e)
|
|
1413
|
+
this.cleanup(t);
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Debug logging
|
|
1417
|
+
*/
|
|
1418
|
+
log(...e) {
|
|
1419
|
+
this.config.debug && console.log(...e);
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Send any pending events before page unload
|
|
1423
|
+
*/
|
|
1424
|
+
setupBeforeUnload() {
|
|
1425
|
+
typeof window < "u" && window.addEventListener("beforeunload", () => {
|
|
1426
|
+
for (const e of this.eventQueue.keys()) {
|
|
1427
|
+
const t = this.eventQueue.get(e);
|
|
1428
|
+
if (t && t.length > 0) {
|
|
1429
|
+
const i = {
|
|
1430
|
+
"Content-Type": "application/json"
|
|
1431
|
+
}, n = x();
|
|
1432
|
+
n && (i["x-api-key"] = n), fetch(`${this.baseUrl}/ads/viewability/batch`, {
|
|
1433
|
+
method: "POST",
|
|
1434
|
+
headers: i,
|
|
1435
|
+
keepalive: !0,
|
|
1436
|
+
body: JSON.stringify({ events: t })
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
let p = null;
|
|
1444
|
+
function J(o, e) {
|
|
1445
|
+
return p || (p = new O(o, e), p.setupBeforeUnload()), p;
|
|
1446
|
+
}
|
|
1447
|
+
const X = "0.1.0";
|
|
1448
|
+
export {
|
|
1449
|
+
F as AdManager,
|
|
1450
|
+
S as AnalyticsSender,
|
|
1451
|
+
v as ClientInfoCollector,
|
|
1452
|
+
g as DOMRenderer,
|
|
1453
|
+
f as HTMLRenderer,
|
|
1454
|
+
X as SDK_VERSION,
|
|
1455
|
+
A as SessionManager,
|
|
1456
|
+
O as ViewabilityTracker,
|
|
1457
|
+
z as adaptAdToKoahAd,
|
|
1458
|
+
W as clearClientInfoCache,
|
|
1459
|
+
K as createAnalytics,
|
|
1460
|
+
R as createSession,
|
|
1461
|
+
_ as fetchAds,
|
|
1462
|
+
B as generateViewToken,
|
|
1463
|
+
H as getAppInfo,
|
|
1464
|
+
h as getClientInfo,
|
|
1465
|
+
w as getClientInfoCollector,
|
|
1466
|
+
D as getClientInfoJSON,
|
|
1467
|
+
Q as getClientInfoSummary,
|
|
1468
|
+
N as getDeviceInfo,
|
|
1469
|
+
G as getGeoInfo,
|
|
1470
|
+
m as getSessionId,
|
|
1471
|
+
j as getUserId,
|
|
1472
|
+
U as getUserInfo,
|
|
1473
|
+
J as getViewabilityTracker,
|
|
1474
|
+
b as trackAdClick,
|
|
1475
|
+
y as trackAdImpression,
|
|
1476
|
+
q as trackClicksBatch,
|
|
1477
|
+
L as trackImpressionsBatch
|
|
1478
|
+
};
|