@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/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
+ };