@action-x/ad-sdk 0.1.2 → 0.1.4

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 CHANGED
@@ -17,7 +17,7 @@ pnpm add @action-x/ad-sdk
17
17
  **Import styles (required):**
18
18
 
19
19
  ```js
20
- import '@action-x/ad-sdk/dist/style.css';
20
+ import '@action-x/ad-sdk/style.css';
21
21
  ```
22
22
 
23
23
  ---
@@ -37,7 +37,7 @@ Below are AdCard wrapper examples for different frameworks. The component takes
37
37
  <script setup>
38
38
  import { ref, onMounted, onUnmounted } from 'vue';
39
39
  import { AdManager } from '@action-x/ad-sdk';
40
- import '@action-x/ad-sdk/dist/style.css';
40
+ import '@action-x/ad-sdk/style.css';
41
41
 
42
42
  const props = defineProps(['query', 'response']);
43
43
  const adEl = ref(null);
@@ -64,7 +64,7 @@ onUnmounted(() => manager?.destroy());
64
64
  // components/AdCard.tsx
65
65
  import { useEffect, useRef } from 'react';
66
66
  import { AdManager } from '@action-x/ad-sdk';
67
- import '@action-x/ad-sdk/dist/style.css';
67
+ import '@action-x/ad-sdk/style.css';
68
68
 
69
69
  interface Props {
70
70
  query: string;
@@ -99,7 +99,7 @@ export function AdCard({ query, response }: Props) {
99
99
 
100
100
  ```js
101
101
  import { AdManager } from '@action-x/ad-sdk';
102
- import '@action-x/ad-sdk/dist/style.css';
102
+ import '@action-x/ad-sdk/style.css';
103
103
 
104
104
  const manager = new AdManager({
105
105
  apiBaseUrl: 'https://your-api.example.com/api/v1',
@@ -118,7 +118,7 @@ manager.render(document.getElementById('ad-card'));
118
118
  ### CDN (No Build Tool)
119
119
 
120
120
  ```html
121
- <link rel="stylesheet" href="https://unpkg.com/@action-x/ad-sdk/dist/style.css" />
121
+ <link rel="stylesheet" href="https://unpkg.com/@action-x/ad-sdk/style.css" />
122
122
  <script src="https://unpkg.com/@action-x/ad-sdk/dist/index.umd.js"></script>
123
123
 
124
124
  <div id="ad-card"></div>
package/dist/index.cjs CHANGED
@@ -1,19 +1,19 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class L{constructor(){this.events={}}on(e,t){this.events[e]||(this.events[e]=[]),this.events[e].push(t)}off(e,t){if(!this.events[e])return;const i=this.events[e].indexOf(t);i>-1&&this.events[e].splice(i,1)}emit(e,...t){this.events[e]&&this.events[e].forEach(i=>{try{i(...t)}catch(n){console.error(`[EventEmitter] Error in event handler for "${e}":`,n)}})}removeAllListeners(e){e?delete this.events[e]:this.events={}}listenerCount(e){var t;return((t=this.events[e])==null?void 0:t.length)||0}}class p{static renderActionCard(e,t={}){var h;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=(h=s.image)!=null&&h.url?`<img
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class L{constructor(){this.events={}}on(e,t){this.events[e]||(this.events[e]=[]),this.events[e].push(t)}off(e,t){if(!this.events[e])return;const i=this.events[e].indexOf(t);i>-1&&this.events[e].splice(i,1)}emit(e,...t){this.events[e]&&this.events[e].forEach(i=>{try{i(...t)}catch(n){console.error(`[EventEmitter] Error in event handler for "${e}":`,n)}})}removeAllListeners(e){e?delete this.events[e]:this.events={}}listenerCount(e){var t;return((t=this.events[e])==null?void 0:t.length)||0}}class b{static renderActionCard(e,t={}){var h;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=(h=s.image)!=null&&h.url?`<img
2
2
  src="${s.image.url}"
3
3
  alt="${s.title}"
4
4
  class="ax-ad-image"
5
5
  loading="lazy"
6
- />`:"",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}">
6
+ />`:"",d=s.price?`<span class="ax-ad-price">${s.price.display||s.price.value}</span>`:"",l=s.rating?`<div class="ax-ad-rating" aria-label="Rating: ${s.rating}">
7
7
  ${"★".repeat(Math.floor(s.rating))}${"☆".repeat(5-Math.floor(s.rating))}
8
- </div>`:"",u=s.brand?`<span class="ax-ad-brand">${s.brand}</span>`:"",m=`
8
+ </div>`:"",u=s.brand?`<span class="ax-ad-brand">${s.brand}</span>`:"",f=`
9
9
  ${c}
10
10
  <div class="ax-ad-content">
11
11
  ${u}
12
12
  <h3 class="ax-ad-title">${s.title}</h3>
13
13
  ${s.body?`<p class="ax-ad-body">${s.body}</p>`:""}
14
- ${d}
14
+ ${l}
15
15
  <div class="ax-ad-footer">
16
- ${l}
16
+ ${d}
17
17
  <a
18
18
  href="${r.clickUrl}"
19
19
  class="ax-ad-cta"
@@ -26,7 +26,7 @@
26
26
  </a>
27
27
  </div>
28
28
  </div>
29
- `,y=n?"</div>":"";return a+m+y}static renderSuffixAd(e){const t=e.adapted,i=e.tracking;return`
29
+ `,p=n?"</div>":"";return a+f+p}static renderSuffixAd(e){const t=e.adapted,i=e.tracking;return`
30
30
  <div class="ax-ad-suffix" data-ad-id="${e.original.id}">
31
31
  <div class="ax-ad-suffix-content">
32
32
  ${t.title?`<h4 class="ax-ad-suffix-title">${t.title}</h4>`:""}
@@ -78,8 +78,8 @@
78
78
  </a>
79
79
  </div>
80
80
  </div>
81
- `}static renderAds(e,t=p.renderActionCard.bind(p)){return e.map(t).join(`
82
- `)}}const B="/api/v1/ads",q="ad_session_id";function K(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}class ${constructor(e={}){this.memoryCache=new Map,this.sessionKey=e.sessionKey||q,this.storage=e.storage||"sessionStorage",(typeof window>"u"||typeof window.sessionStorage>"u")&&(this.storage="memory")}getSessionId(){if(this.storage==="sessionStorage"){let e=sessionStorage.getItem(this.sessionKey);return e||(e=this.generateSessionId(),sessionStorage.setItem(this.sessionKey,e)),e}else{let e=this.memoryCache.get(this.sessionKey);return e||(e=this.generateSessionId(),this.memoryCache.set(this.sessionKey,e)),e}}regenerateSessionId(){const e=this.generateSessionId();return this.storage==="sessionStorage"?sessionStorage.setItem(this.sessionKey,e):this.memoryCache.set(this.sessionKey,e),e}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substring(2,11)}`}clearSessionId(){this.storage==="sessionStorage"?sessionStorage.removeItem(this.sessionKey):this.memoryCache.delete(this.sessionKey)}}const R=new $,f=()=>R.getSessionId();class M{constructor(e={}){this.baseUrl=e.baseUrl||B,this.timeout=e.timeout||1e4,this.retryAttempts=e.retryAttempts??2,this.retryDelay=e.retryDelay||1e3,this.debug=e.debug||!1}async trackImpression(e){return this.sendRequest(`${this.baseUrl}/impression`,e,"impression")}async trackClick(e){return this.sendRequest(`${this.baseUrl}/click`,e,"click")}async sendRequest(e,t,i){let n=null;for(let s=0;s<=this.retryAttempts;s++)try{this.debug&&console.log(`[Analytics] Sending ${i} event (attempt ${s+1}):`,t);const r=new AbortController,a=setTimeout(()=>r.abort(),this.timeout),c={"Content-Type":"application/json"},l=K();l&&(c["X-API-Key"]=l,this.debug&&console.log(`[Analytics] Adding X-API-Key header for ${i} event`));const d=await fetch(e,{method:"POST",headers:c,body:JSON.stringify(t),keepalive:!0,signal:r.signal});if(clearTimeout(a),!d.ok)throw new Error(`HTTP ${d.status}: ${d.statusText}`);const u=await d.json();return this.debug&&console.log(`[Analytics] ${i} event tracked successfully:`,u),u}catch(r){n=r,this.debug&&console.warn(`[Analytics] ${i} tracking failed (attempt ${s+1}):`,r),s<this.retryAttempts&&await this.delay(this.retryDelay*(s+1))}return console.error(`[Analytics] ${i} tracking failed after ${this.retryAttempts+1} attempts:`,n),null}delay(e){return new Promise(t=>setTimeout(t,e))}}const E=new M,k=o=>E.trackImpression(o),A=o=>E.trackClick(o);function N(o,e){return`vt_${o}_${e}`}async function H(o){return Promise.all(o.map(e=>k(e)))}async function D(o){return Promise.all(o.map(e=>A(e)))}function G(o){const e=new M(o);return{trackImpression:t=>e.trackImpression(t),trackClick:t=>e.trackClick(t)}}function j(o={}){const e=new $(o);return{getSessionId:()=>e.getSessionId(),regenerateSessionId:()=>e.regenerateSessionId(),clearSessionId:()=>e.clearSessionId()}}class v{static renderActionCard(e,t,i={}){t.innerHTML=p.renderActionCard(e,i);const n=t.querySelector(".ax-ad-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSuffixAd(e,t,i={}){const n=i.variant||"block";t.innerHTML=this.generateSuffixAdHTML(e,n);const s=t.querySelector(".ax-ad-suffix-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSponsoredSource(e,t,i={}){const n=i.variant||"card";t.innerHTML=this.generateSponsoredSourceHTML(e,n);const s=t.querySelector(".ax-ad-source-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderLeadGenAd(e,t,i={}){t.innerHTML=p.renderLeadGenAd(e);const n=t.querySelector(".ax-ad-leadgen-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderAds(e,t,i=this.renderActionCard.bind(this),n){t.innerHTML="";const s=document.createElement("div");return s.className="ax-ads-container",e.forEach(r=>{const a=document.createElement("div");a.className="ax-ad-wrapper",i.call(this,r,a,n),s.appendChild(a)}),t.appendChild(s),t}static handleClick(e,t){const{analytics:i,onClick:n}=t;n&&n(e),i&&A({requestId:i.requestId,adId:e.original.id,destinationUrl:e.tracking.clickUrl,slotId:i.slotId,sessionId:f(),adTitle:e.adapted.title,format:e.original.type}).catch(s=>{console.error("[DOMRenderer] Analytics click tracking failed:",s)}),window.open(e.tracking.clickUrl,"_blank","noopener,noreferrer")}static trackImpression(e,t,i){const{analytics:n,onImpression:s}=i,r=()=>{n?k({requestId:n.requestId,adId:e.original.id,slotId:n.slotId,position:n.position,totalAds:n.totalAds,sessionId:f(),adTitle:e.adapted.title,format:e.original.type,source:"internal",viewToken:e.tracking.viewToken}).then(()=>{s&&s(e)}).catch(a=>{console.error("[DOMRenderer] Analytics impression tracking failed:",a)}):s&&s(e)};if(typeof IntersectionObserver<"u"){const a=new IntersectionObserver(l=>{l.forEach(d=>{d.isIntersecting&&(setTimeout(()=>{r()},1e3),a.disconnect())})},{threshold:.5}),c=t.querySelector(`[data-ad-id="${e.original.id}"]`)||t;a.observe(c)}else r()}static generateSuffixAdHTML(e,t){const i=e.adapted.title||"",n=e.adapted.body||"";return t==="inline"?`
81
+ `}static renderAds(e,t=b.renderActionCard.bind(b)){return e.map(t).join(`
82
+ `)}}const K="ad_session_id";function R(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}function E(o){if(o)return o;if(typeof window<"u"){const e=window.__AD_CONFIG__;if(e!=null&&e.apiBaseUrl)return e.apiBaseUrl}return"/api/v1"}class C{constructor(e={}){this.memoryCache=new Map,this.sessionKey=e.sessionKey||K,this.storage=e.storage||"sessionStorage",(typeof window>"u"||typeof window.sessionStorage>"u")&&(this.storage="memory")}getSessionId(){if(this.storage==="sessionStorage"){let e=sessionStorage.getItem(this.sessionKey);return e||(e=this.generateSessionId(),sessionStorage.setItem(this.sessionKey,e)),e}else{let e=this.memoryCache.get(this.sessionKey);return e||(e=this.generateSessionId(),this.memoryCache.set(this.sessionKey,e)),e}}regenerateSessionId(){const e=this.generateSessionId();return this.storage==="sessionStorage"?sessionStorage.setItem(this.sessionKey,e):this.memoryCache.set(this.sessionKey,e),e}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substring(2,11)}`}clearSessionId(){this.storage==="sessionStorage"?sessionStorage.removeItem(this.sessionKey):this.memoryCache.delete(this.sessionKey)}}const N=new C,m=()=>N.getSessionId();class M{constructor(e={}){this.explicitBaseUrl=e.baseUrl,this.timeout=e.timeout||1e4,this.retryAttempts=e.retryAttempts??2,this.retryDelay=e.retryDelay||1e3,this.debug=e.debug||!1}async trackImpression(e){return this.sendRequest(`${E(this.explicitBaseUrl)}/ads/impression`,e,"impression")}async trackClick(e){return this.sendRequest(`${E(this.explicitBaseUrl)}/ads/click`,e,"click")}async sendRequest(e,t,i){let n=null;for(let s=0;s<=this.retryAttempts;s++)try{this.debug&&console.log(`[Analytics] Sending ${i} event (attempt ${s+1}):`,t);const r=new AbortController,a=setTimeout(()=>r.abort(),this.timeout),c={"Content-Type":"application/json"},d=R();d&&(c["X-API-Key"]=d,this.debug&&console.log(`[Analytics] Adding X-API-Key header for ${i} event`));const l=await fetch(e,{method:"POST",headers:c,body:JSON.stringify(t),keepalive:!0,signal:r.signal});if(clearTimeout(a),!l.ok)throw new Error(`HTTP ${l.status}: ${l.statusText}`);const u=await l.json();return this.debug&&console.log(`[Analytics] ${i} event tracked successfully:`,u),u}catch(r){n=r,this.debug&&console.warn(`[Analytics] ${i} tracking failed (attempt ${s+1}):`,r),s<this.retryAttempts&&await this.delay(this.retryDelay*(s+1))}return console.error(`[Analytics] ${i} tracking failed after ${this.retryAttempts+1} attempts:`,n),null}delay(e){return new Promise(t=>setTimeout(t,e))}}const P=new M,x=o=>P.trackImpression(o),A=o=>P.trackClick(o);function H(o,e){return`vt_${o}_${e}`}async function D(o){return Promise.all(o.map(e=>x(e)))}async function G(o){return Promise.all(o.map(e=>A(e)))}function F(o){const e=new M(o);return{trackImpression:t=>e.trackImpression(t),trackClick:t=>e.trackClick(t)}}function j(o={}){const e=new C(o);return{getSessionId:()=>e.getSessionId(),regenerateSessionId:()=>e.regenerateSessionId(),clearSessionId:()=>e.clearSessionId()}}const V=class V{static renderActionCard(e,t,i={}){t.innerHTML=b.renderActionCard(e,i);const n=t.querySelector(".ax-ad-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSuffixAd(e,t,i={}){const n=i.variant||"block";t.innerHTML=this.generateSuffixAdHTML(e,n);const s=t.querySelector(".ax-ad-suffix-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSponsoredSource(e,t,i={}){const n=i.variant||"card";t.innerHTML=this.generateSponsoredSourceHTML(e,n);const s=t.querySelector(".ax-ad-source-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderLeadGenAd(e,t,i={}){t.innerHTML=b.renderLeadGenAd(e);const n=t.querySelector(".ax-ad-leadgen-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderAds(e,t,i=this.renderActionCard.bind(this),n){t.innerHTML="";const s=document.createElement("div");return s.className="ax-ads-container",e.forEach(r=>{const a=document.createElement("div");a.className="ax-ad-wrapper",i.call(this,r,a,n),s.appendChild(a)}),t.appendChild(s),t}static handleClick(e,t){const{analytics:i,onClick:n}=t;n&&n(e),i&&A({requestId:i.requestId,adId:e.original.id,destinationUrl:e.tracking.clickUrl,slotId:i.slotId,sessionId:m(),adTitle:e.adapted.title,format:e.original.type}).catch(s=>{console.error("[DOMRenderer] Analytics click tracking failed:",s)}),window.open(e.tracking.clickUrl,"_blank","noopener,noreferrer")}static trackImpression(e,t,i){const{analytics:n,onImpression:s}=i,r=n?`${n.requestId}:${n.slotId}:${e.original.id}:${e.tracking.viewToken||""}`:`no-analytics:${e.original.id}:${e.tracking.viewToken||""}`;if(this.trackedImpressionKeys.has(r))return;let a=!1;const c=()=>{a||this.trackedImpressionKeys.has(r)||(a=!0,this.trackedImpressionKeys.add(r),d())},d=()=>{n?x({requestId:n.requestId,adId:e.original.id,slotId:n.slotId,position:n.position,totalAds:n.totalAds,sessionId:m(),adTitle:e.adapted.title,format:e.original.type,source:"internal",viewToken:e.tracking.viewToken}).then(()=>{s&&s(e)}).catch(l=>{console.error("[DOMRenderer] Analytics impression tracking failed:",l)}):s&&s(e)};if(typeof IntersectionObserver<"u"){let l=!1,u=null;const f=new IntersectionObserver(h=>{h.forEach(w=>{l=w.isIntersecting&&w.intersectionRatio>=.5,l?u||(u=setTimeout(()=>{u=null,l&&(c(),f.disconnect())},1e3)):u&&(clearTimeout(u),u=null)})},{threshold:.5}),p=t.querySelector(`[data-ad-id="${e.original.id}"]`)||t;f.observe(p)}else c()}static generateSuffixAdHTML(e,t){const i=e.adapted.title||"",n=e.adapted.body||"";return t==="inline"?`
83
83
  <span class="ax-ad-suffix-link ax-ad-variant-inline" data-ad-id="${e.original.id}">
84
84
  ${i}
85
85
  <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="display:inline;vertical-align:middle;margin-left:4px">
@@ -136,4 +136,4 @@
136
136
  <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" />
137
137
  </svg>
138
138
  </div>
139
- `}}class x{constructor(){this.cachedStaticInfo=null,this.cacheTimestamp=0,this.CACHE_TTL=36e5}inferAppInfo(){if(typeof window>"u")return{bundle:"unknown",ver:"1.0.0",name:"Unknown App",publisher:{id:"unknown"}};const e=this.inferBundle(),t=this.inferAppName(),i=this.inferAppVersion(),n=this.inferPublisherId();return{bundle:e,ver:i,name:t,publisher:{id:n,domain:window.location.hostname}}}inferBundle(){var t;if(typeof window>"u")return"unknown";const e=(t=window.location)==null?void 0:t.hostname;return e?e.replace(/^www\./,"").replace(/:\d+$/,""):"unknown"}inferAppName(){var n,s;if(typeof document>"u")return"Unknown App";const e=document.title;if(e&&e!=="Loading..."&&e.length<100)return e;const t=(n=document.querySelector('meta[property="og:title"]'))==null?void 0:n.getAttribute("content");if(t)return t;const i=(s=document.querySelector('meta[name="application-name"]'))==null?void 0:s.getAttribute("content");return i||"Unknown App"}inferAppVersion(){if(typeof document>"u")return"1.0.0";const e=['meta[name="version"]','meta[name="app-version"]','meta[name="application-version"]','link[rel="manifest"]'];for(const t of e){const i=document.querySelector(t);if(i){if(t.includes("manifest"))return"1.0.0";const n=i.getAttribute("content");if(n)return n}}return"1.0.0"}inferPublisherId(){var i,n;if(typeof document>"u")return"unknown";const e=(i=document.querySelector('meta[name="publisher-id"]'))==null?void 0:i.getAttribute("content");if(e)return e;const t=(n=window.location)==null?void 0:n.hostname;return t?t.replace(/\./g,"_"):"unknown"}static getInstance(){return this.instance||(this.instance=new x),this.instance}collect(e){const t=Date.now();(!this.cachedStaticInfo||t-this.cacheTimestamp>this.CACHE_TTL)&&(this.cachedStaticInfo=this.collectStaticInfo(),this.cacheTimestamp=t);const i=this.collectDynamicInfo();return{device:{ua:this.cachedStaticInfo.ua,os:this.cachedStaticInfo.os,osv:this.cachedStaticInfo.osv,devicetype:this.cachedStaticInfo.devicetype,model:this.cachedStaticInfo.model,make:this.cachedStaticInfo.make,language:this.cachedStaticInfo.language,connectiontype:i.connectiontype??0,bandwidth:i.bandwidth,...(e==null?void 0:e.device)||{}},app:{...this.inferAppInfo(),...(e==null?void 0:e.app)||{}},user:{...this.collectUserInfo(),...(e==null?void 0:e.user)||{}},geo:{...this.collectGeoInfo(),...(e==null?void 0:e.geo)||{}},timestamp:t}}collectStaticInfo(){if(typeof navigator>"u")return this.getFallbackInfo();const e=navigator.userAgent,t=this.detectOS(e),i=this.detectOSVersion(e);return{ua:e,os:t,osv:i,devicetype:this.detectDeviceType(e),model:this.detectModel(e),make:this.detectMake(e),language:navigator.language||"en-US"}}collectDynamicInfo(){if(typeof navigator>"u")return{connectiontype:0};const e=this.getConnectionInfo();return{connectiontype:(e==null?void 0:e.type)||0,bandwidth:e==null?void 0:e.downlink}}collectUserInfo(){let e;try{e=localStorage.getItem("chatbox_user_id")||"",e||(e=this.generateUUID(),localStorage.setItem("chatbox_user_id",e))}catch{e=this.generateUUID()}return{id:e,language:(navigator==null?void 0:navigator.language)||"en-US"}}collectGeoInfo(){if(typeof navigator>"u"||typeof Intl>"u")return{};const e=Intl.DateTimeFormat().resolvedOptions().timeZone,t=navigator.language;return{country:this.inferCountry(t,e),timezone:e}}detectOS(e){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"}detectOSVersion(e){if(!e)return"Unknown";const t=e.match(/OS (\d+)_(\d+)_?(\d+)?/);if(t)return`${t[1]}.${t[2]}${t[3]?"."+t[3]:""}`;const i=e.match(/Android (\d+)\.(\d+)/);if(i)return`${i[1]}.${i[2]}`;const n=e.match(/Windows NT (\d+\.\d+)/);if(n)return n[1];const s=e.match(/Mac OS X (\d+[._]\d+)/);return s?s[1].replace("_","."):"Unknown"}detectDeviceType(e){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}detectModel(e){if(!e)return;const t=e.match(/iPhone(?:;\s*[^\)]*)?\s*(\d+,\d)?/);if(t)return`iPhone${t[1]||""}`;if(/iPad/.test(e))return"iPad";const i=e.match(/Samsung-.*(SM-\w+)/);if(i)return i[1];if(/Pixel/.test(e))return"Google Pixel";const n=e.match(/(BARC-|HUAWEI-)?([A-Z]{2}\-\w{4})/);if(n)return n[2];const s=e.match(/(MI|Redmi|POCO)\s([\w\s]+)/);if(s)return s[1]+" "+s[2];if(/OnePlus/.test(e)){const r=e.match(/OnePlus\s([A-Z\d]+)/);return r?"OnePlus "+r[1]:"OnePlus"}}detectMake(e){if(e){if(/iPad|iPhone|iPod/.test(e))return"Apple";if(/Samsung/.test(e))return"Samsung";if(/Pixel/.test(e))return"Google";if(/Huawei|HONOR/.test(e))return"Huawei";if(/Xiaomi|Redmi|POCO/.test(e))return"Xiaomi";if(/OnePlus/.test(e))return"OnePlus";if(/Sony/.test(e))return"Sony";if(/LG/.test(e))return"LG";if(/Motorola|Moto/.test(e))return"Motorola";if(/Nokia/.test(e))return"Nokia";if(/HTC/.test(e))return"HTC";if(/OPPO/.test(e))return"OPPO";if(/vivo/.test(e))return"vivo";if(/Realme/.test(e))return"Realme"}}getConnectionInfo(){if(typeof navigator>"u")return null;const e=navigator,t=e.connection||e.mozConnection||e.webkitConnection;return t?{type:this.mapConnectionType(t.effectiveType),downlink:t.downlink,rtt:t.rtt}:null}mapConnectionType(e){if(!e)return 0;switch(e.toLowerCase()){case"slow-2g":case"2g":return 3;case"3g":return 4;case"4g":return 5;default:return 0}}inferCountry(e,t){if(!e&&!t)return;const i={"Asia/Shanghai":"CN","Asia/Hong_Kong":"HK","Asia/Taipei":"TW","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Singapore":"SG","Asia/Dubai":"AE","Asia/Kolkata":"IN","Asia/Jakarta":"ID","Asia/Manila":"PH","Asia/Bangkok":"TH","Asia/Kuala_Lumpur":"MY","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Mexico_City":"MX","America/Sao_Paulo":"BR","America/Buenos_Aires":"AR","Europe/London":"GB","Europe/Paris":"FR","Europe/Berlin":"DE","Europe/Madrid":"ES","Europe/Rome":"IT","Europe/Amsterdam":"NL","Europe/Brussels":"BE","Europe/Zurich":"CH","Europe/Vienna":"AT","Europe/Stockholm":"SE","Europe/Oslo":"NO","Europe/Copenhagen":"DK","Europe/Helsinki":"FI","Europe/Dublin":"IE","Europe/Lisbon":"PT","Europe/Athens":"GR","Europe/Prague":"CZ","Europe/Warsaw":"PL","Europe/Budapest":"HU","Europe/Bucharest":"RO","Australia/Sydney":"AU","Pacific/Auckland":"NZ"};if(t&&i[t])return i[t];if(e){const n=e.split("-")[1];if(n)return n.toUpperCase()}}generateUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}getFallbackInfo(){return{ua:"Unknown",os:"Unknown",osv:"Unknown",devicetype:0,language:"en-US"}}clearCache(){this.cachedStaticInfo=null,this.cacheTimestamp=0}}const S=()=>x.getInstance();function g(o){return S().collect(o)}function F(){return g().device}function _(){return g().user}function z(){return g().app}function W(){return g().geo}function Q(){return _().id}function J(){S().clearCache()}function X(o){const e=g(o);return JSON.stringify(e,null,2)}function Z(){var t;const o=g();return[o.device.os,o.device.osv,o.app.name,o.user.id.slice(0,8)+"...",((t=o.geo)==null?void 0:t.country)||"Unknown"].join(" / ")}function Y(o){var e,t;return{id:o.original.id,type:o.original.type,score:o.original.score,source:"internal",content:{title:o.adapted.title,body:o.adapted.body,image:(e=o.adapted.image)==null?void 0:e.url,cta_text:o.adapted.ctaText,price:(t=o.adapted.price)==null?void 0:t.display,rating:o.adapted.rating,brand:o.adapted.brand},tracking:{click_url:o.tracking.clickUrl,impression_url:o.tracking.impressionUrl},metadata:{viewToken:o.tracking.viewToken,styling:o.adapted.styling}}}async function P(o,e={}){const t=e.apiBaseUrl||"/api/v1",n=S().collect(),s={...o,clientInfo:n},r={"Content-Type":"application/json"};e.apiKey&&(r["X-API-Key"]=e.apiKey);const a=await fetch(`${t}/ads/request`,{method:"POST",headers:r,body:JSON.stringify(s)});if(!a.ok)throw new Error(`Ad request failed: ${a.status} ${a.statusText}`);const c=await a.json();if(!c.success)throw new Error(c.error||"Ad request failed");return c.data}const ee={width:400,height:200},T={variant:"horizontal",count:1,preferences:{}};function C(o={}){const e={variant:o.variant??T.variant,count:o.count??T.count,preferences:{...T.preferences,...o.preferences}};return[{slotId:"action_card",slotName:"Action Card",format:"action_card",variant:e.variant,size:ee,count:e.count,preferences:e.preferences,placement:{position:"below_fold",context:"post_response"}}]}const w=class w{constructor(e){this.slots={},this.isLoading=!1,this.currentRequestId=null,this.adsAnalyticsMap=new Map,this.config=e,this.enabled=e.enabled!==!1,this.eventBus=new L,this.slots_config=C(e.cardOption),typeof window<"u"&&(window.__AD_CONFIG__={...window.__AD_CONFIG__||{},apiKey:e.apiKey,apiBaseUrl:e.apiBaseUrl}),this.config.debug&&console.log("[AdManager] Initialized with config:",{apiBaseUrl:e.apiBaseUrl,hasApiKey:!!e.apiKey,enabled:this.enabled})}async requestAds(e){if(!this.enabled)throw this.config.debug&&console.warn("[AdManager] Ads are disabled, skipping request"),new Error("Ads are disabled");if(this.isLoading)throw this.config.debug&&console.warn("[AdManager] Request already in progress"),new Error("Request already in progress");this.isLoading=!0,this.eventBus.emit("adsLoading"),this.config.debug&&console.log("[AdManager] Starting ad request...");const t="conversationContext"in e?e:{conversationContext:e};try{const i=await P({conversationContext:t.conversationContext,userContext:t.userContext?{...t.userContext,sessionId:t.userContext.sessionId??f()}:{sessionId:f()},slots:this.slots_config},{apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey});this.currentRequestId=i.requestId,this.adsAnalyticsMap.clear(),i.slots.forEach(s=>{s.ads.forEach((r,a)=>{const c=r.original.id;this.adsAnalyticsMap.set(c,{requestId:i.requestId,slotId:s.slotId,position:a,totalAds:s.ads.length})})});const n={};return i.slots.forEach(s=>{n[s.slotId]=s}),this.slots=n,this.eventBus.emit("adsUpdated",this.slots),this.config.debug&&console.log("[AdManager] Ads received:",{slotCount:Object.keys(this.slots).length,slots:Object.keys(this.slots)}),i}catch(i){const n=i;throw this.eventBus.emit("adsError",n),n}finally{this.isLoading=!1}}render(e,t,i={}){const n=typeof e!="string",s=n?w.DEFAULT_SLOT_ID:e,r=n?e:t,a=(n?t:i)||{};if(!r)return this.config.debug&&console.warn("[AdManager] Render container is required"),null;const c=this.slots[s];if(!c||!c.ads||c.ads.length===0)return this.config.debug&&console.warn("[AdManager] No ads in slot:",s),null;const l=a.adIndex??0,d=c.ads[l];if(!d)return this.config.debug&&console.warn(`[AdManager] Ad at index ${l} not found in slot:`,s),null;const u=this.adsAnalyticsMap.get(d.original.id),m=this.slots_config.find(O=>O.slotId===s),y=(m==null?void 0:m.format)||"action_card";r.innerHTML="";const h={analytics:u,variant:a.variant,onClick:a.onClick,onImpression:a.onImpression};return y==="suffix"?v.renderSuffixAd(d,r,h):y==="source"?v.renderSponsoredSource(d,r,h):y==="lead_gen"?v.renderLeadGenAd(d,r,h):v.renderActionCard(d,r,h)}getSlots(e){return e?this.slots[e]:this.slots}hasAds(e){var i;const t=this.slots[e];return(t==null?void 0:t.status)==="filled"&&((i=t.ads)==null?void 0:i.length)>0}getAds(e){var t;return((t=this.slots[e])==null?void 0:t.ads)||[]}getLoadingStatus(){return this.isLoading}setEnabled(e){this.enabled=e,this.config.debug&&console.log("[AdManager] Ads",e?"enabled":"disabled")}updateConfig(e){this.config={...this.config,...e},e.cardOption!==void 0&&(this.slots_config=C(this.config.cardOption)),this.config.debug&&console.log("[AdManager] Config updated:",e)}clearSlots(){this.slots={},this.currentRequestId=null,this.adsAnalyticsMap.clear(),this.config.debug&&console.log("[AdManager] Slots and analytics cleared")}on(e,t){this.eventBus.on(e,t)}off(e,t){this.eventBus.off(e,t)}removeAllListeners(e){this.eventBus.removeAllListeners(e)}getCurrentRequestId(){return this.currentRequestId}getAdAnalytics(e){return this.adsAnalyticsMap.get(e)||null}getAdsAnalytics(e){const t=new Map;return e.forEach(i=>{const n=this.adsAnalyticsMap.get(i);n&&t.set(i,n)}),t}async trackAdClick(e,t,i){const n=this.adsAnalyticsMap.get(e);if(!n||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const s=f(),r=await A({requestId:this.currentRequestId,adId:e,destinationUrl:t,sessionId:s,slotId:n.slotId,adTitle:i==null?void 0:i.title,format:i==null?void 0:i.format,source:i==null?void 0:i.source});return r&&(this.eventBus.emit("adClicked",e,n.slotId),this.config.debug&&console.log("[AdManager] Click tracked via Analytics API:",r.eventId)),r}async trackAdImpressionAPI(e,t){const i=this.adsAnalyticsMap.get(e);if(!i||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const n=f(),s=await k({requestId:this.currentRequestId,adId:e,slotId:i.slotId,position:i.position,totalAds:i.totalAds,sessionId:n,adTitle:t==null?void 0:t.title,format:t==null?void 0:t.format,source:t==null?void 0:t.source,viewToken:t==null?void 0:t.viewToken});return s&&(this.eventBus.emit("adImpression",e,i.slotId),this.config.debug&&console.log("[AdManager] Impression tracked via Analytics API:",s.eventId)),s}destroy(){this.removeAllListeners(),this.clearSlots(),this.config.debug&&console.log("[AdManager] Destroyed")}};w.DEFAULT_SLOT_ID="action_card";let I=w;function V(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}class U{constructor(e={},t="/api/v1"){var i,n;this.observers=new Map,this.metrics=new Map,this.timers=new Map,this.batchTimers=new Map,this.eventQueue=new Map,this.isTracking=new Map,this.config={minVisiblePercentage:e.minVisiblePercentage??50,minViewableDuration:e.minViewableDuration??1e3,maxTrackingDuration:e.maxTrackingDuration??6e4,batchConfig:{maxBatchSize:((i=e.batchConfig)==null?void 0:i.maxBatchSize)??5,maxBatchWaitMs:((n=e.batchConfig)==null?void 0:n.maxBatchWaitMs)??1e4},debug:e.debug??!1},this.baseUrl=t}startTracking(e,t,i,n,s){if(this.isTracking.get(e)){this.log(`[ViewabilityTracker] Already tracking ${e}, skipping`);return}this.log(`[ViewabilityTracker] Starting tracking for ${e}`);const r={visiblePercentage:0,maxVisiblePercentage:0,totalVisibleTimeMs:0,currentVisibleTimeMs:0,isViewable:!1,viewableAt:null,enteredViewportAt:null,enterCount:0};this.metrics.set(e,r),this.isTracking.set(e,!0),this.eventQueue.set(e,[]);const a=new IntersectionObserver(l=>this.handleIntersection(e,l[0],i,n,s),{threshold:this.createThresholds()});a.observe(t),this.observers.set(e,a),this.startMonitoring(e,i,n,s);const c=setTimeout(()=>{this.endTracking(e,i,n,s)},this.config.maxTrackingDuration);this.timers.set(e,c)}stopTracking(e){this.log(`[ViewabilityTracker] Manual stop tracking for ${e}`),this.cleanup(e)}getMetrics(e){return this.metrics.get(e)}handleIntersection(e,t,i,n,s){const r=this.metrics.get(e);if(!r||!this.isTracking.get(e))return;const a=r.isViewable,c=Math.round(t.intersectionRatio*100);r.visiblePercentage=c,r.maxVisiblePercentage=Math.max(r.maxVisiblePercentage,c);const l=Date.now(),d=c>=this.config.minVisiblePercentage;if(d&&!r.enteredViewportAt&&(r.enteredViewportAt=l,r.enterCount++,this.log(`[ViewabilityTracker] ${e} entered viewport at ${l}`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"enter_viewport",visiblePercentage:c,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:!1,timestamp:l})),!d&&r.enteredViewportAt){const u=l-r.enteredViewportAt;r.totalVisibleTimeMs+=u,r.enteredViewportAt=null,r.currentVisibleTimeMs=0,this.log(`[ViewabilityTracker] ${e} exited viewport, total visible: ${r.totalVisibleTimeMs}ms`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"exit_viewport",visiblePercentage:c,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:r.isViewable,timestamp:l})}r.isViewable!==a&&r.isViewable&&this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`),this.metrics.set(e,r)}startMonitoring(e,t,i,n){const r=setInterval(()=>{const a=this.metrics.get(e);if(!a||!this.isTracking.get(e)){clearInterval(r);return}const c=Date.now(),l=a.isViewable;if(a.enteredViewportAt){const d=c-a.enteredViewportAt;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,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"become_viewable",visiblePercentage:a.visiblePercentage,maxVisiblePercentage:a.maxVisiblePercentage,totalVisibleTimeMs:a.totalVisibleTimeMs,isViewable:!0,timestamp:c})))}a.enteredViewportAt&&(a.totalVisibleTimeMs+=100),this.metrics.set(e,a)},100);this.timers.set(`${e}_monitoring`,r)}queueEvent(e,t){var s,r;const i=this.eventQueue.get(e);if(!i){this.log(`[ViewabilityTracker] No queue found for ${e}, skipping event`);return}i.push(t),this.log(`[ViewabilityTracker] Queued ${t.eventType} for ${e} (queue size: ${i.length})`);const n=((s=this.config.batchConfig)==null?void 0:s.maxBatchSize)??5;if(i.length>=n)this.flushQueue(e);else{const a=this.batchTimers.get(e);a&&clearTimeout(a);const c=((r=this.config.batchConfig)==null?void 0:r.maxBatchWaitMs)??1e4,l=setTimeout(()=>{this.flushQueue(e)},c);this.batchTimers.set(e,l)}}async flushQueue(e){const t=this.eventQueue.get(e);if(!t||t.length===0)return;const i=[...t];t.length=0,this.log(`[ViewabilityTracker] Flushing ${i.length} events for ${e}`);try{const n={"Content-Type":"application/json"},s=V();s&&(n["x-api-key"]=s),await fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:n,body:JSON.stringify({events:i})}),this.log(`[ViewabilityTracker] Successfully sent ${i.length} events for ${e}`)}catch(n){this.log(`[ViewabilityTracker] Failed to send events for ${e}:`,n),t.unshift(...i)}}endTracking(e,t,i,n){const s=this.metrics.get(e);if(!s)return;this.log(`[ViewabilityTracker] Ending tracking for ${e}`),this.flushQueue(e);const r=Date.now();this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"end_tracking",visiblePercentage:s.visiblePercentage,maxVisiblePercentage:s.maxVisiblePercentage,totalVisibleTimeMs:s.totalVisibleTimeMs,isViewable:s.isViewable,timestamp:r}),this.flushQueue(e),this.cleanup(e)}cleanup(e){const t=this.observers.get(e);t&&(t.disconnect(),this.observers.delete(e));const i=this.timers.get(e);i&&(clearTimeout(i),this.timers.delete(e));const n=this.timers.get(`${e}_monitoring`);n&&(clearInterval(n),this.timers.delete(`${e}_monitoring`));const s=this.batchTimers.get(e);s&&(clearTimeout(s),this.batchTimers.delete(e)),this.flushQueue(e),this.isTracking.delete(e),this.metrics.delete(e),this.eventQueue.delete(e)}createThresholds(){return[0,.25,.5,.75,.9,1]}stopAll(){const e=Array.from(this.isTracking.keys());for(const t of e)this.cleanup(t)}log(...e){this.config.debug&&console.log(...e)}setupBeforeUnload(){typeof window<"u"&&window.addEventListener("beforeunload",()=>{for(const e of this.eventQueue.keys()){const t=this.eventQueue.get(e);if(t&&t.length>0){const i={"Content-Type":"application/json"},n=V();n&&(i["x-api-key"]=n),fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:i,keepalive:!0,body:JSON.stringify({events:t})})}}})}}let b=null;function te(o,e){return b||(b=new U(o,e),b.setupBeforeUnload()),b}const ie="0.1.0";exports.AdManager=I;exports.AnalyticsSender=M;exports.ClientInfoCollector=x;exports.DOMRenderer=v;exports.HTMLRenderer=p;exports.SDK_VERSION=ie;exports.SessionManager=$;exports.ViewabilityTracker=U;exports.adaptAdToKoahAd=Y;exports.clearClientInfoCache=J;exports.createAnalytics=G;exports.createSession=j;exports.fetchAds=P;exports.generateViewToken=N;exports.getAppInfo=z;exports.getClientInfo=g;exports.getClientInfoCollector=S;exports.getClientInfoJSON=X;exports.getClientInfoSummary=Z;exports.getDeviceInfo=F;exports.getGeoInfo=W;exports.getSessionId=f;exports.getUserId=Q;exports.getUserInfo=_;exports.getViewabilityTracker=te;exports.trackAdClick=A;exports.trackAdImpression=k;exports.trackClicksBatch=D;exports.trackImpressionsBatch=H;
139
+ `}};V.trackedImpressionKeys=new Set;let y=V;class S{constructor(){this.cachedStaticInfo=null,this.cacheTimestamp=0,this.CACHE_TTL=36e5}inferAppInfo(){if(typeof window>"u")return{bundle:"unknown",ver:"1.0.0",name:"Unknown App",publisher:{id:"unknown"}};const e=this.inferBundle(),t=this.inferAppName(),i=this.inferAppVersion(),n=this.inferPublisherId();return{bundle:e,ver:i,name:t,publisher:{id:n,domain:window.location.hostname}}}inferBundle(){var t;if(typeof window>"u")return"unknown";const e=(t=window.location)==null?void 0:t.hostname;return e?e.replace(/^www\./,"").replace(/:\d+$/,""):"unknown"}inferAppName(){var n,s;if(typeof document>"u")return"Unknown App";const e=document.title;if(e&&e!=="Loading..."&&e.length<100)return e;const t=(n=document.querySelector('meta[property="og:title"]'))==null?void 0:n.getAttribute("content");if(t)return t;const i=(s=document.querySelector('meta[name="application-name"]'))==null?void 0:s.getAttribute("content");return i||"Unknown App"}inferAppVersion(){if(typeof document>"u")return"1.0.0";const e=['meta[name="version"]','meta[name="app-version"]','meta[name="application-version"]','link[rel="manifest"]'];for(const t of e){const i=document.querySelector(t);if(i){if(t.includes("manifest"))return"1.0.0";const n=i.getAttribute("content");if(n)return n}}return"1.0.0"}inferPublisherId(){var i,n;if(typeof document>"u")return"unknown";const e=(i=document.querySelector('meta[name="publisher-id"]'))==null?void 0:i.getAttribute("content");if(e)return e;const t=(n=window.location)==null?void 0:n.hostname;return t?t.replace(/\./g,"_"):"unknown"}static getInstance(){return this.instance||(this.instance=new S),this.instance}collect(e){const t=Date.now();(!this.cachedStaticInfo||t-this.cacheTimestamp>this.CACHE_TTL)&&(this.cachedStaticInfo=this.collectStaticInfo(),this.cacheTimestamp=t);const i=this.collectDynamicInfo();return{device:{ua:this.cachedStaticInfo.ua,os:this.cachedStaticInfo.os,osv:this.cachedStaticInfo.osv,devicetype:this.cachedStaticInfo.devicetype,model:this.cachedStaticInfo.model,make:this.cachedStaticInfo.make,language:this.cachedStaticInfo.language,connectiontype:i.connectiontype??0,bandwidth:i.bandwidth,...(e==null?void 0:e.device)||{}},app:{...this.inferAppInfo(),...(e==null?void 0:e.app)||{}},user:{...this.collectUserInfo(),...(e==null?void 0:e.user)||{}},geo:{...this.collectGeoInfo(),...(e==null?void 0:e.geo)||{}},timestamp:t}}collectStaticInfo(){if(typeof navigator>"u")return this.getFallbackInfo();const e=navigator.userAgent,t=this.detectOS(e),i=this.detectOSVersion(e);return{ua:e,os:t,osv:i,devicetype:this.detectDeviceType(e),model:this.detectModel(e),make:this.detectMake(e),language:navigator.language||"en-US"}}collectDynamicInfo(){if(typeof navigator>"u")return{connectiontype:0};const e=this.getConnectionInfo();return{connectiontype:(e==null?void 0:e.type)||0,bandwidth:e==null?void 0:e.downlink}}collectUserInfo(){let e;try{e=localStorage.getItem("chatbox_user_id")||"",e||(e=this.generateUUID(),localStorage.setItem("chatbox_user_id",e))}catch{e=this.generateUUID()}return{id:e,language:(navigator==null?void 0:navigator.language)||"en-US"}}collectGeoInfo(){if(typeof navigator>"u"||typeof Intl>"u")return{};const e=Intl.DateTimeFormat().resolvedOptions().timeZone,t=navigator.language;return{country:this.inferCountry(t,e),timezone:e}}detectOS(e){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"}detectOSVersion(e){if(!e)return"Unknown";const t=e.match(/OS (\d+)_(\d+)_?(\d+)?/);if(t)return`${t[1]}.${t[2]}${t[3]?"."+t[3]:""}`;const i=e.match(/Android (\d+)\.(\d+)/);if(i)return`${i[1]}.${i[2]}`;const n=e.match(/Windows NT (\d+\.\d+)/);if(n)return n[1];const s=e.match(/Mac OS X (\d+[._]\d+)/);return s?s[1].replace("_","."):"Unknown"}detectDeviceType(e){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}detectModel(e){if(!e)return;const t=e.match(/iPhone(?:;\s*[^\)]*)?\s*(\d+,\d)?/);if(t)return`iPhone${t[1]||""}`;if(/iPad/.test(e))return"iPad";const i=e.match(/Samsung-.*(SM-\w+)/);if(i)return i[1];if(/Pixel/.test(e))return"Google Pixel";const n=e.match(/(BARC-|HUAWEI-)?([A-Z]{2}\-\w{4})/);if(n)return n[2];const s=e.match(/(MI|Redmi|POCO)\s([\w\s]+)/);if(s)return s[1]+" "+s[2];if(/OnePlus/.test(e)){const r=e.match(/OnePlus\s([A-Z\d]+)/);return r?"OnePlus "+r[1]:"OnePlus"}}detectMake(e){if(e){if(/iPad|iPhone|iPod/.test(e))return"Apple";if(/Samsung/.test(e))return"Samsung";if(/Pixel/.test(e))return"Google";if(/Huawei|HONOR/.test(e))return"Huawei";if(/Xiaomi|Redmi|POCO/.test(e))return"Xiaomi";if(/OnePlus/.test(e))return"OnePlus";if(/Sony/.test(e))return"Sony";if(/LG/.test(e))return"LG";if(/Motorola|Moto/.test(e))return"Motorola";if(/Nokia/.test(e))return"Nokia";if(/HTC/.test(e))return"HTC";if(/OPPO/.test(e))return"OPPO";if(/vivo/.test(e))return"vivo";if(/Realme/.test(e))return"Realme"}}getConnectionInfo(){if(typeof navigator>"u")return null;const e=navigator,t=e.connection||e.mozConnection||e.webkitConnection;return t?{type:this.mapConnectionType(t.effectiveType),downlink:t.downlink,rtt:t.rtt}:null}mapConnectionType(e){if(!e)return 0;switch(e.toLowerCase()){case"slow-2g":case"2g":return 3;case"3g":return 4;case"4g":return 5;default:return 0}}inferCountry(e,t){if(!e&&!t)return;const i={"Asia/Shanghai":"CN","Asia/Hong_Kong":"HK","Asia/Taipei":"TW","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Singapore":"SG","Asia/Dubai":"AE","Asia/Kolkata":"IN","Asia/Jakarta":"ID","Asia/Manila":"PH","Asia/Bangkok":"TH","Asia/Kuala_Lumpur":"MY","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Mexico_City":"MX","America/Sao_Paulo":"BR","America/Buenos_Aires":"AR","Europe/London":"GB","Europe/Paris":"FR","Europe/Berlin":"DE","Europe/Madrid":"ES","Europe/Rome":"IT","Europe/Amsterdam":"NL","Europe/Brussels":"BE","Europe/Zurich":"CH","Europe/Vienna":"AT","Europe/Stockholm":"SE","Europe/Oslo":"NO","Europe/Copenhagen":"DK","Europe/Helsinki":"FI","Europe/Dublin":"IE","Europe/Lisbon":"PT","Europe/Athens":"GR","Europe/Prague":"CZ","Europe/Warsaw":"PL","Europe/Budapest":"HU","Europe/Bucharest":"RO","Australia/Sydney":"AU","Pacific/Auckland":"NZ"};if(t&&i[t])return i[t];if(e){const n=e.split("-")[1];if(n)return n.toUpperCase()}}generateUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}getFallbackInfo(){return{ua:"Unknown",os:"Unknown",osv:"Unknown",devicetype:0,language:"en-US"}}clearCache(){this.cachedStaticInfo=null,this.cacheTimestamp=0}}const T=()=>S.getInstance();function v(o){return T().collect(o)}function z(){return v().device}function O(){return v().user}function W(){return v().app}function Q(){return v().geo}function J(){return O().id}function X(){T().clearCache()}function Z(o){const e=v(o);return JSON.stringify(e,null,2)}function Y(){var t;const o=v();return[o.device.os,o.device.osv,o.app.name,o.user.id.slice(0,8)+"...",((t=o.geo)==null?void 0:t.country)||"Unknown"].join(" / ")}function ee(o){var e,t;return{id:o.original.id,type:o.original.type,score:o.original.score,source:"internal",content:{title:o.adapted.title,body:o.adapted.body,image:(e=o.adapted.image)==null?void 0:e.url,cta_text:o.adapted.ctaText,price:(t=o.adapted.price)==null?void 0:t.display,rating:o.adapted.rating,brand:o.adapted.brand},tracking:{click_url:o.tracking.clickUrl,impression_url:o.tracking.impressionUrl},metadata:{viewToken:o.tracking.viewToken,styling:o.adapted.styling}}}async function B(o,e={}){const t=e.apiBaseUrl||"/api/v1",n=T().collect(),s={...o,clientInfo:n},r={"Content-Type":"application/json"};e.apiKey&&(r["X-API-Key"]=e.apiKey);const a=await fetch(`${t}/ads/request`,{method:"POST",headers:r,body:JSON.stringify(s)});if(!a.ok)throw new Error(`Ad request failed: ${a.status} ${a.statusText}`);const c=await a.json();if(!c.success)throw new Error(c.error||"Ad request failed");return c.data}const te={width:400,height:200},I={variant:"horizontal",count:1,preferences:{}};function U(o={}){const e={variant:o.variant??I.variant,count:o.count??I.count,preferences:{...I.preferences,...o.preferences}};return[{slotId:"action_card",slotName:"Action Card",format:"action_card",variant:e.variant,size:te,count:e.count,preferences:e.preferences,placement:{position:"below_fold",context:"post_response"}}]}const g=class g{constructor(e){this.slots={},this.isLoading=!1,this.currentRequestId=null,this.adsAnalyticsMap=new Map,this.config=e,this.enabled=e.enabled!==!1,this.eventBus=new L,this.slots_config=U(e.cardOption),typeof window<"u"&&(window.__AD_CONFIG__={...window.__AD_CONFIG__||{},apiKey:e.apiKey,apiBaseUrl:e.apiBaseUrl}),this.config.debug&&console.log("[AdManager] Initialized with config:",{apiBaseUrl:e.apiBaseUrl,hasApiKey:!!e.apiKey,enabled:this.enabled})}async requestAds(e){const t="conversationContext"in e?e:{conversationContext:e},i=this.buildRequestKey(t),n=g.inFlightRequests.get(i);if(n)return this.config.debug&&console.log("[AdManager] Reusing in-flight request for identical context"),n;if(!this.enabled)throw this.config.debug&&console.warn("[AdManager] Ads are disabled, skipping request"),new Error("Ads are disabled");if(this.isLoading)throw this.config.debug&&console.warn("[AdManager] Request already in progress"),new Error("Request already in progress");this.isLoading=!0,this.eventBus.emit("adsLoading"),this.config.debug&&console.log("[AdManager] Starting ad request...");const s=(async()=>{try{const r=await B({conversationContext:t.conversationContext,userContext:t.userContext?{...t.userContext,sessionId:t.userContext.sessionId??m()}:{sessionId:m()},slots:this.slots_config},{apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey});this.currentRequestId=r.requestId,this.adsAnalyticsMap.clear(),r.slots.forEach(c=>{c.ads.forEach((d,l)=>{const u=d.original.id;this.adsAnalyticsMap.set(u,{requestId:r.requestId,slotId:c.slotId,position:l,totalAds:c.ads.length})})});const a={};return r.slots.forEach(c=>{a[c.slotId]=c}),this.slots=a,this.eventBus.emit("adsUpdated",this.slots),this.config.debug&&console.log("[AdManager] Ads received:",{slotCount:Object.keys(this.slots).length,slots:Object.keys(this.slots)}),r}catch(r){const a=r;throw this.eventBus.emit("adsError",a),a}finally{this.isLoading=!1,g.inFlightRequests.delete(i)}})();return g.inFlightRequests.set(i,s),s}buildRequestKey(e){return JSON.stringify({apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey,conversationContext:e.conversationContext,userContext:e.userContext||null,slots:this.slots_config})}render(e,t,i={}){const n=typeof e!="string",s=n?g.DEFAULT_SLOT_ID:e,r=n?e:t,a=(n?t:i)||{};if(!r)return this.config.debug&&console.warn("[AdManager] Render container is required"),null;const c=this.slots[s];if(!c||!c.ads||c.ads.length===0)return this.config.debug&&console.warn("[AdManager] No ads in slot:",s),null;const d=a.adIndex??0,l=c.ads[d];if(!l)return this.config.debug&&console.warn(`[AdManager] Ad at index ${d} not found in slot:`,s),null;const u=this.adsAnalyticsMap.get(l.original.id),f=this.slots_config.find(w=>w.slotId===s),p=(f==null?void 0:f.format)||"action_card";r.innerHTML="";const h={analytics:u,variant:a.variant,onClick:a.onClick,onImpression:a.onImpression};return p==="suffix"?y.renderSuffixAd(l,r,h):p==="source"?y.renderSponsoredSource(l,r,h):p==="lead_gen"?y.renderLeadGenAd(l,r,h):y.renderActionCard(l,r,h)}getSlots(e){return e?this.slots[e]:this.slots}hasAds(e){var i;const t=this.slots[e];return(t==null?void 0:t.status)==="filled"&&((i=t.ads)==null?void 0:i.length)>0}getAds(e){var t;return((t=this.slots[e])==null?void 0:t.ads)||[]}getLoadingStatus(){return this.isLoading}setEnabled(e){this.enabled=e,this.config.debug&&console.log("[AdManager] Ads",e?"enabled":"disabled")}updateConfig(e){this.config={...this.config,...e},e.cardOption!==void 0&&(this.slots_config=U(this.config.cardOption)),this.config.debug&&console.log("[AdManager] Config updated:",e)}clearSlots(){this.slots={},this.currentRequestId=null,this.adsAnalyticsMap.clear(),this.config.debug&&console.log("[AdManager] Slots and analytics cleared")}on(e,t){this.eventBus.on(e,t)}off(e,t){this.eventBus.off(e,t)}removeAllListeners(e){this.eventBus.removeAllListeners(e)}getCurrentRequestId(){return this.currentRequestId}getAdAnalytics(e){return this.adsAnalyticsMap.get(e)||null}getAdsAnalytics(e){const t=new Map;return e.forEach(i=>{const n=this.adsAnalyticsMap.get(i);n&&t.set(i,n)}),t}async trackAdClick(e,t,i){const n=this.adsAnalyticsMap.get(e);if(!n||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const s=m(),r=await A({requestId:this.currentRequestId,adId:e,destinationUrl:t,sessionId:s,slotId:n.slotId,adTitle:i==null?void 0:i.title,format:i==null?void 0:i.format,source:i==null?void 0:i.source});return r&&(this.eventBus.emit("adClicked",e,n.slotId),this.config.debug&&console.log("[AdManager] Click tracked via Analytics API:",r.eventId)),r}async trackAdImpressionAPI(e,t){const i=this.adsAnalyticsMap.get(e);if(!i||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const n=m(),s=await x({requestId:this.currentRequestId,adId:e,slotId:i.slotId,position:i.position,totalAds:i.totalAds,sessionId:n,adTitle:t==null?void 0:t.title,format:t==null?void 0:t.format,source:t==null?void 0:t.source,viewToken:t==null?void 0:t.viewToken});return s&&(this.eventBus.emit("adImpression",e,i.slotId),this.config.debug&&console.log("[AdManager] Impression tracked via Analytics API:",s.eventId)),s}destroy(){this.removeAllListeners(),this.clearSlots(),this.config.debug&&console.log("[AdManager] Destroyed")}};g.DEFAULT_SLOT_ID="action_card",g.inFlightRequests=new Map;let $=g;function _(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}class q{constructor(e={},t="/api/v1"){var i,n;this.observers=new Map,this.metrics=new Map,this.timers=new Map,this.batchTimers=new Map,this.eventQueue=new Map,this.isTracking=new Map,this.config={minVisiblePercentage:e.minVisiblePercentage??50,minViewableDuration:e.minViewableDuration??1e3,maxTrackingDuration:e.maxTrackingDuration??6e4,batchConfig:{maxBatchSize:((i=e.batchConfig)==null?void 0:i.maxBatchSize)??5,maxBatchWaitMs:((n=e.batchConfig)==null?void 0:n.maxBatchWaitMs)??1e4},debug:e.debug??!1},this.baseUrl=t}startTracking(e,t,i,n,s){if(this.isTracking.get(e)){this.log(`[ViewabilityTracker] Already tracking ${e}, skipping`);return}this.log(`[ViewabilityTracker] Starting tracking for ${e}`);const r={visiblePercentage:0,maxVisiblePercentage:0,totalVisibleTimeMs:0,currentVisibleTimeMs:0,isViewable:!1,viewableAt:null,enteredViewportAt:null,enterCount:0};this.metrics.set(e,r),this.isTracking.set(e,!0),this.eventQueue.set(e,[]);const a=new IntersectionObserver(d=>this.handleIntersection(e,d[0],i,n,s),{threshold:this.createThresholds()});a.observe(t),this.observers.set(e,a),this.startMonitoring(e,i,n,s);const c=setTimeout(()=>{this.endTracking(e,i,n,s)},this.config.maxTrackingDuration);this.timers.set(e,c)}stopTracking(e){this.log(`[ViewabilityTracker] Manual stop tracking for ${e}`),this.cleanup(e)}getMetrics(e){return this.metrics.get(e)}handleIntersection(e,t,i,n,s){const r=this.metrics.get(e);if(!r||!this.isTracking.get(e))return;const a=r.isViewable,c=Math.round(t.intersectionRatio*100);r.visiblePercentage=c,r.maxVisiblePercentage=Math.max(r.maxVisiblePercentage,c);const d=Date.now(),l=c>=this.config.minVisiblePercentage;if(l&&!r.enteredViewportAt&&(r.enteredViewportAt=d,r.enterCount++,this.log(`[ViewabilityTracker] ${e} entered viewport at ${d}`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"enter_viewport",visiblePercentage:c,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:!1,timestamp:d})),!l&&r.enteredViewportAt){const u=d-r.enteredViewportAt;r.totalVisibleTimeMs+=u,r.enteredViewportAt=null,r.currentVisibleTimeMs=0,this.log(`[ViewabilityTracker] ${e} exited viewport, total visible: ${r.totalVisibleTimeMs}ms`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"exit_viewport",visiblePercentage:c,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:r.isViewable,timestamp:d})}r.isViewable!==a&&r.isViewable&&this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`),this.metrics.set(e,r)}startMonitoring(e,t,i,n){const r=setInterval(()=>{const a=this.metrics.get(e);if(!a||!this.isTracking.get(e)){clearInterval(r);return}const c=Date.now(),d=a.isViewable;if(a.enteredViewportAt){const l=c-a.enteredViewportAt;a.currentVisibleTimeMs=l,l>=this.config.minViewableDuration&&a.visiblePercentage>=this.config.minVisiblePercentage&&(a.isViewable=!0,d||(a.viewableAt=a.enteredViewportAt,this.log(`[ViewabilityTracker] ${e} BECAME VIEWABLE at ${a.viewableAt}`),this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"become_viewable",visiblePercentage:a.visiblePercentage,maxVisiblePercentage:a.maxVisiblePercentage,totalVisibleTimeMs:a.totalVisibleTimeMs,isViewable:!0,timestamp:c})))}a.enteredViewportAt&&(a.totalVisibleTimeMs+=100),this.metrics.set(e,a)},100);this.timers.set(`${e}_monitoring`,r)}queueEvent(e,t){var s,r;const i=this.eventQueue.get(e);if(!i){this.log(`[ViewabilityTracker] No queue found for ${e}, skipping event`);return}i.push(t),this.log(`[ViewabilityTracker] Queued ${t.eventType} for ${e} (queue size: ${i.length})`);const n=((s=this.config.batchConfig)==null?void 0:s.maxBatchSize)??5;if(i.length>=n)this.flushQueue(e);else{const a=this.batchTimers.get(e);a&&clearTimeout(a);const c=((r=this.config.batchConfig)==null?void 0:r.maxBatchWaitMs)??1e4,d=setTimeout(()=>{this.flushQueue(e)},c);this.batchTimers.set(e,d)}}async flushQueue(e){const t=this.eventQueue.get(e);if(!t||t.length===0)return;const i=[...t];t.length=0,this.log(`[ViewabilityTracker] Flushing ${i.length} events for ${e}`);try{const n={"Content-Type":"application/json"},s=_();s&&(n["x-api-key"]=s),await fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:n,body:JSON.stringify({events:i})}),this.log(`[ViewabilityTracker] Successfully sent ${i.length} events for ${e}`)}catch(n){this.log(`[ViewabilityTracker] Failed to send events for ${e}:`,n),t.unshift(...i)}}endTracking(e,t,i,n){const s=this.metrics.get(e);if(!s)return;this.log(`[ViewabilityTracker] Ending tracking for ${e}`),this.flushQueue(e);const r=Date.now();this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"end_tracking",visiblePercentage:s.visiblePercentage,maxVisiblePercentage:s.maxVisiblePercentage,totalVisibleTimeMs:s.totalVisibleTimeMs,isViewable:s.isViewable,timestamp:r}),this.flushQueue(e),this.cleanup(e)}cleanup(e){const t=this.observers.get(e);t&&(t.disconnect(),this.observers.delete(e));const i=this.timers.get(e);i&&(clearTimeout(i),this.timers.delete(e));const n=this.timers.get(`${e}_monitoring`);n&&(clearInterval(n),this.timers.delete(`${e}_monitoring`));const s=this.batchTimers.get(e);s&&(clearTimeout(s),this.batchTimers.delete(e)),this.flushQueue(e),this.isTracking.delete(e),this.metrics.delete(e),this.eventQueue.delete(e)}createThresholds(){return[0,.25,.5,.75,.9,1]}stopAll(){const e=Array.from(this.isTracking.keys());for(const t of e)this.cleanup(t)}log(...e){this.config.debug&&console.log(...e)}setupBeforeUnload(){typeof window<"u"&&window.addEventListener("beforeunload",()=>{for(const e of this.eventQueue.keys()){const t=this.eventQueue.get(e);if(t&&t.length>0){const i={"Content-Type":"application/json"},n=_();n&&(i["x-api-key"]=n),fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:i,keepalive:!0,body:JSON.stringify({events:t})})}}})}}let k=null;function ie(o,e){return k||(k=new q(o,e),k.setupBeforeUnload()),k}const ne="0.1.0";exports.AdManager=$;exports.AnalyticsSender=M;exports.ClientInfoCollector=S;exports.DOMRenderer=y;exports.HTMLRenderer=b;exports.SDK_VERSION=ne;exports.SessionManager=C;exports.ViewabilityTracker=q;exports.adaptAdToKoahAd=ee;exports.clearClientInfoCache=X;exports.createAnalytics=F;exports.createSession=j;exports.fetchAds=B;exports.generateViewToken=H;exports.getAppInfo=W;exports.getClientInfo=v;exports.getClientInfoCollector=T;exports.getClientInfoJSON=Z;exports.getClientInfoSummary=Y;exports.getDeviceInfo=z;exports.getGeoInfo=Q;exports.getSessionId=m;exports.getUserId=J;exports.getUserInfo=O;exports.getViewabilityTracker=ie;exports.trackAdClick=A;exports.trackAdImpression=x;exports.trackClicksBatch=G;exports.trackImpressionsBatch=D;
package/dist/index.d.ts CHANGED
@@ -96,6 +96,7 @@ export declare class AdManager {
96
96
  private isLoading;
97
97
  private enabled;
98
98
  private static readonly DEFAULT_SLOT_ID;
99
+ private static inFlightRequests;
99
100
  private currentRequestId;
100
101
  private adsAnalyticsMap;
101
102
  constructor(config: AdManagerConfig);
@@ -111,6 +112,7 @@ export declare class AdManager {
111
112
  conversationContext: ConversationContext;
112
113
  userContext?: UserContext;
113
114
  }): Promise<AdResponseBatch>;
115
+ private buildRequestKey;
114
116
  /**
115
117
  * Render a single ad from a slot into a container element
116
118
  *
@@ -368,7 +370,7 @@ export declare interface AnalyticsSendConfig {
368
370
  * Analytics 发送器类
369
371
  */
370
372
  export declare class AnalyticsSender {
371
- private baseUrl;
373
+ private explicitBaseUrl?;
372
374
  private timeout;
373
375
  private retryAttempts;
374
376
  private retryDelay;
@@ -694,6 +696,7 @@ export declare interface DeviceInfo {
694
696
  * DOM Renderer - Create DOM elements with event binding
695
697
  */
696
698
  export declare class DOMRenderer {
699
+ private static trackedImpressionKeys;
697
700
  /**
698
701
  * Render Action Card ad into container
699
702
  */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- class P {
1
+ class O {
2
2
  constructor() {
3
3
  this.events = {};
4
4
  }
@@ -42,7 +42,7 @@ class P {
42
42
  return ((t = this.events[e]) == null ? void 0 : t.length) || 0;
43
43
  }
44
44
  }
45
- class y {
45
+ class b {
46
46
  /**
47
47
  * Render Action Card ad as HTML string
48
48
  *
@@ -57,17 +57,17 @@ class y {
57
57
  alt="${s.title}"
58
58
  class="ax-ad-image"
59
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}">
60
+ />` : "", d = s.price ? `<span class="ax-ad-price">${s.price.display || s.price.value}</span>` : "", l = s.rating ? `<div class="ax-ad-rating" aria-label="Rating: ${s.rating}">
61
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>` : "", p = `
62
+ </div>` : "", u = s.brand ? `<span class="ax-ad-brand">${s.brand}</span>` : "", f = `
63
63
  ${c}
64
64
  <div class="ax-ad-content">
65
65
  ${u}
66
66
  <h3 class="ax-ad-title">${s.title}</h3>
67
67
  ${s.body ? `<p class="ax-ad-body">${s.body}</p>` : ""}
68
- ${d}
68
+ ${l}
69
69
  <div class="ax-ad-footer">
70
- ${l}
70
+ ${d}
71
71
  <a
72
72
  href="${r.clickUrl}"
73
73
  class="ax-ad-cta"
@@ -80,8 +80,8 @@ class y {
80
80
  </a>
81
81
  </div>
82
82
  </div>
83
- `, m = n ? "</div>" : "";
84
- return a + p + m;
83
+ `, p = n ? "</div>" : "";
84
+ return a + f + p;
85
85
  }
86
86
  /**
87
87
  * Render Suffix ad as HTML string
@@ -173,22 +173,31 @@ class y {
173
173
  * @param renderFn - Render function to use
174
174
  * @returns HTML string with all ads
175
175
  */
176
- static renderAds(e, t = y.renderActionCard.bind(y)) {
176
+ static renderAds(e, t = b.renderActionCard.bind(b)) {
177
177
  return e.map(t).join(`
178
178
  `);
179
179
  }
180
180
  }
181
- const U = "/api/v1/ads", O = "ad_session_id";
182
- function L() {
181
+ const q = "ad_session_id";
182
+ function B() {
183
183
  if (typeof window < "u") {
184
184
  const o = window.__AD_CONFIG__;
185
185
  if (o != null && o.apiKey)
186
186
  return o.apiKey;
187
187
  }
188
188
  }
189
- class C {
189
+ function M(o) {
190
+ if (o) return o;
191
+ if (typeof window < "u") {
192
+ const e = window.__AD_CONFIG__;
193
+ if (e != null && e.apiBaseUrl)
194
+ return e.apiBaseUrl;
195
+ }
196
+ return "/api/v1";
197
+ }
198
+ class _ {
190
199
  constructor(e = {}) {
191
- this.memoryCache = /* @__PURE__ */ new Map(), this.sessionKey = e.sessionKey || O, this.storage = e.storage || "sessionStorage", (typeof window > "u" || typeof window.sessionStorage > "u") && (this.storage = "memory");
200
+ this.memoryCache = /* @__PURE__ */ new Map(), this.sessionKey = e.sessionKey || q, this.storage = e.storage || "sessionStorage", (typeof window > "u" || typeof window.sessionStorage > "u") && (this.storage = "memory");
192
201
  }
193
202
  /**
194
203
  * 获取 Session ID
@@ -222,17 +231,17 @@ class C {
222
231
  this.storage === "sessionStorage" ? sessionStorage.removeItem(this.sessionKey) : this.memoryCache.delete(this.sessionKey);
223
232
  }
224
233
  }
225
- const B = new C(), f = () => B.getSessionId();
226
- class V {
234
+ const L = new _(), m = () => L.getSessionId();
235
+ class P {
227
236
  constructor(e = {}) {
228
- this.baseUrl = e.baseUrl || U, this.timeout = e.timeout || 1e4, this.retryAttempts = e.retryAttempts ?? 2, this.retryDelay = e.retryDelay || 1e3, this.debug = e.debug || !1;
237
+ this.explicitBaseUrl = e.baseUrl, this.timeout = e.timeout || 1e4, this.retryAttempts = e.retryAttempts ?? 2, this.retryDelay = e.retryDelay || 1e3, this.debug = e.debug || !1;
229
238
  }
230
239
  /**
231
240
  * 发送广告展示事件
232
241
  */
233
242
  async trackImpression(e) {
234
243
  return this.sendRequest(
235
- `${this.baseUrl}/impression`,
244
+ `${M(this.explicitBaseUrl)}/ads/impression`,
236
245
  e,
237
246
  "impression"
238
247
  );
@@ -242,7 +251,7 @@ class V {
242
251
  */
243
252
  async trackClick(e) {
244
253
  return this.sendRequest(
245
- `${this.baseUrl}/click`,
254
+ `${M(this.explicitBaseUrl)}/ads/click`,
246
255
  e,
247
256
  "click"
248
257
  );
@@ -258,18 +267,18 @@ class V {
258
267
  this.debug && console.log(`[Analytics] Sending ${i} event (attempt ${s + 1}):`, t);
259
268
  const r = new AbortController(), a = setTimeout(() => r.abort(), this.timeout), c = {
260
269
  "Content-Type": "application/json"
261
- }, l = L();
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, {
270
+ }, d = B();
271
+ d && (c["X-API-Key"] = d, this.debug && console.log(`[Analytics] Adding X-API-Key header for ${i} event`));
272
+ const l = await fetch(e, {
264
273
  method: "POST",
265
274
  headers: c,
266
275
  body: JSON.stringify(t),
267
276
  keepalive: !0,
268
277
  signal: r.signal
269
278
  });
270
- if (clearTimeout(a), !d.ok)
271
- throw new Error(`HTTP ${d.status}: ${d.statusText}`);
272
- const u = await d.json();
279
+ if (clearTimeout(a), !l.ok)
280
+ throw new Error(`HTTP ${l.status}: ${l.statusText}`);
281
+ const u = await l.json();
273
282
  return this.debug && console.log(`[Analytics] ${i} event tracked successfully:`, u), u;
274
283
  } catch (r) {
275
284
  n = r, this.debug && console.warn(`[Analytics] ${i} tracking failed (attempt ${s + 1}):`, r), s < this.retryAttempts && await this.delay(this.retryDelay * (s + 1));
@@ -283,37 +292,37 @@ class V {
283
292
  return new Promise((t) => setTimeout(t, e));
284
293
  }
285
294
  }
286
- const E = new V(), x = (o) => E.trackImpression(o), A = (o) => E.trackClick(o);
287
- function H(o, e) {
295
+ const U = new P(), A = (o) => U.trackImpression(o), S = (o) => U.trackClick(o);
296
+ function F(o, e) {
288
297
  return `vt_${o}_${e}`;
289
298
  }
290
- async function D(o) {
291
- return Promise.all(o.map((e) => x(e)));
292
- }
293
299
  async function G(o) {
294
300
  return Promise.all(o.map((e) => A(e)));
295
301
  }
296
- function F(o) {
297
- const e = new V(o);
302
+ async function D(o) {
303
+ return Promise.all(o.map((e) => S(e)));
304
+ }
305
+ function j(o) {
306
+ const e = new P(o);
298
307
  return {
299
308
  trackImpression: (t) => e.trackImpression(t),
300
309
  trackClick: (t) => e.trackClick(t)
301
310
  };
302
311
  }
303
- function j(o = {}) {
304
- const e = new C(o);
312
+ function z(o = {}) {
313
+ const e = new _(o);
305
314
  return {
306
315
  getSessionId: () => e.getSessionId(),
307
316
  regenerateSessionId: () => e.regenerateSessionId(),
308
317
  clearSessionId: () => e.clearSessionId()
309
318
  };
310
319
  }
311
- class v {
320
+ const I = class I {
312
321
  /**
313
322
  * Render Action Card ad into container
314
323
  */
315
324
  static renderActionCard(e, t, i = {}) {
316
- t.innerHTML = y.renderActionCard(e, i);
325
+ t.innerHTML = b.renderActionCard(e, i);
317
326
  const n = t.querySelector(".ax-ad-cta");
318
327
  return n && n.addEventListener("click", (s) => {
319
328
  s.preventDefault(), this.handleClick(e, i);
@@ -345,7 +354,7 @@ class v {
345
354
  * Render Lead Gen ad into container
346
355
  */
347
356
  static renderLeadGenAd(e, t, i = {}) {
348
- t.innerHTML = y.renderLeadGenAd(e);
357
+ t.innerHTML = b.renderLeadGenAd(e);
349
358
  const n = t.querySelector(".ax-ad-leadgen-cta");
350
359
  return n && n.addEventListener("click", (s) => {
351
360
  s.preventDefault(), this.handleClick(e, i);
@@ -368,12 +377,12 @@ class v {
368
377
  */
369
378
  static handleClick(e, t) {
370
379
  const { analytics: i, onClick: n } = t;
371
- n && n(e), i && A({
380
+ n && n(e), i && S({
372
381
  requestId: i.requestId,
373
382
  adId: e.original.id,
374
383
  destinationUrl: e.tracking.clickUrl,
375
384
  slotId: i.slotId,
376
- sessionId: f(),
385
+ sessionId: m(),
377
386
  adTitle: e.adapted.title,
378
387
  format: e.original.type
379
388
  }).catch((s) => {
@@ -385,38 +394,45 @@ class v {
385
394
  * Falls back to immediate tracking if IntersectionObserver is unavailable.
386
395
  */
387
396
  static trackImpression(e, t, i) {
388
- const { analytics: n, onImpression: s } = i, r = () => {
389
- n ? x({
397
+ const { analytics: n, onImpression: s } = i, r = n ? `${n.requestId}:${n.slotId}:${e.original.id}:${e.tracking.viewToken || ""}` : `no-analytics:${e.original.id}:${e.tracking.viewToken || ""}`;
398
+ if (this.trackedImpressionKeys.has(r))
399
+ return;
400
+ let a = !1;
401
+ const c = () => {
402
+ a || this.trackedImpressionKeys.has(r) || (a = !0, this.trackedImpressionKeys.add(r), d());
403
+ }, d = () => {
404
+ n ? A({
390
405
  requestId: n.requestId,
391
406
  adId: e.original.id,
392
407
  slotId: n.slotId,
393
408
  position: n.position,
394
409
  totalAds: n.totalAds,
395
- sessionId: f(),
410
+ sessionId: m(),
396
411
  adTitle: e.adapted.title,
397
412
  format: e.original.type,
398
413
  source: "internal",
399
414
  viewToken: e.tracking.viewToken
400
415
  }).then(() => {
401
416
  s && s(e);
402
- }).catch((a) => {
403
- console.error("[DOMRenderer] Analytics impression tracking failed:", a);
417
+ }).catch((l) => {
418
+ console.error("[DOMRenderer] Analytics impression tracking failed:", l);
404
419
  }) : s && s(e);
405
420
  };
406
421
  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());
422
+ let l = !1, u = null;
423
+ const f = new IntersectionObserver(
424
+ (h) => {
425
+ h.forEach((w) => {
426
+ l = w.isIntersecting && w.intersectionRatio >= 0.5, l ? u || (u = setTimeout(() => {
427
+ u = null, l && (c(), f.disconnect());
428
+ }, 1e3)) : u && (clearTimeout(u), u = null);
413
429
  });
414
430
  },
415
431
  { threshold: 0.5 }
416
- ), c = t.querySelector(`[data-ad-id="${e.original.id}"]`) || t;
417
- a.observe(c);
432
+ ), p = t.querySelector(`[data-ad-id="${e.original.id}"]`) || t;
433
+ f.observe(p);
418
434
  } else
419
- r();
435
+ c();
420
436
  }
421
437
  /**
422
438
  * Generate HTML for Suffix Ad variants
@@ -490,8 +506,10 @@ class v {
490
506
  </div>
491
507
  `;
492
508
  }
493
- }
494
- class S {
509
+ };
510
+ I.trackedImpressionKeys = /* @__PURE__ */ new Set();
511
+ let y = I;
512
+ class T {
495
513
  constructor() {
496
514
  this.cachedStaticInfo = null, this.cacheTimestamp = 0, this.CACHE_TTL = 36e5;
497
515
  }
@@ -578,7 +596,7 @@ class S {
578
596
  * Get singleton instance
579
597
  */
580
598
  static getInstance() {
581
- return this.instance || (this.instance = new S()), this.instance;
599
+ return this.instance || (this.instance = new T()), this.instance;
582
600
  }
583
601
  /**
584
602
  * Collect all available client information
@@ -881,35 +899,35 @@ class S {
881
899
  this.cachedStaticInfo = null, this.cacheTimestamp = 0;
882
900
  }
883
901
  }
884
- const T = () => S.getInstance();
885
- function g(o) {
886
- return T().collect(o);
887
- }
888
- function z() {
889
- return g().device;
890
- }
891
- function q() {
892
- return g().user;
902
+ const $ = () => T.getInstance();
903
+ function v(o) {
904
+ return $().collect(o);
893
905
  }
894
906
  function W() {
895
- return g().app;
907
+ return v().device;
908
+ }
909
+ function K() {
910
+ return v().user;
896
911
  }
897
912
  function Q() {
898
- return g().geo;
913
+ return v().app;
899
914
  }
900
915
  function J() {
901
- return q().id;
916
+ return v().geo;
902
917
  }
903
918
  function X() {
904
- T().clearCache();
919
+ return K().id;
905
920
  }
906
- function Z(o) {
907
- const e = g(o);
921
+ function Z() {
922
+ $().clearCache();
923
+ }
924
+ function Y(o) {
925
+ const e = v(o);
908
926
  return JSON.stringify(e, null, 2);
909
927
  }
910
- function Y() {
928
+ function ee() {
911
929
  var t;
912
- const o = g();
930
+ const o = v();
913
931
  return [
914
932
  o.device.os,
915
933
  o.device.osv,
@@ -918,7 +936,7 @@ function Y() {
918
936
  ((t = o.geo) == null ? void 0 : t.country) || "Unknown"
919
937
  ].join(" / ");
920
938
  }
921
- function ee(o) {
939
+ function te(o) {
922
940
  var e, t;
923
941
  return {
924
942
  id: o.original.id,
@@ -945,8 +963,8 @@ function ee(o) {
945
963
  }
946
964
  };
947
965
  }
948
- async function K(o, e = {}) {
949
- const t = e.apiBaseUrl || "/api/v1", n = T().collect(), s = {
966
+ async function R(o, e = {}) {
967
+ const t = e.apiBaseUrl || "/api/v1", n = $().collect(), s = {
950
968
  ...o,
951
969
  clientInfo: n
952
970
  // Auto-injected
@@ -966,16 +984,16 @@ async function K(o, e = {}) {
966
984
  throw new Error(c.error || "Ad request failed");
967
985
  return c.data;
968
986
  }
969
- const R = { width: 400, height: 200 }, k = {
987
+ const N = { width: 400, height: 200 }, x = {
970
988
  variant: "horizontal",
971
989
  count: 1,
972
990
  preferences: {}
973
991
  };
974
- function $(o = {}) {
992
+ function C(o = {}) {
975
993
  const e = {
976
- variant: o.variant ?? k.variant,
977
- count: o.count ?? k.count,
978
- preferences: { ...k.preferences, ...o.preferences }
994
+ variant: o.variant ?? x.variant,
995
+ count: o.count ?? x.count,
996
+ preferences: { ...x.preferences, ...o.preferences }
979
997
  };
980
998
  return [
981
999
  {
@@ -983,16 +1001,16 @@ function $(o = {}) {
983
1001
  slotName: "Action Card",
984
1002
  format: "action_card",
985
1003
  variant: e.variant,
986
- size: R,
1004
+ size: N,
987
1005
  count: e.count,
988
1006
  preferences: e.preferences,
989
1007
  placement: { position: "below_fold", context: "post_response" }
990
1008
  }
991
1009
  ];
992
1010
  }
993
- const w = class w {
1011
+ const g = class g {
994
1012
  constructor(e) {
995
- this.slots = {}, this.isLoading = !1, this.currentRequestId = null, this.adsAnalyticsMap = /* @__PURE__ */ new Map(), this.config = e, this.enabled = e.enabled !== !1, this.eventBus = new P(), this.slots_config = $(e.cardOption), typeof window < "u" && (window.__AD_CONFIG__ = {
1013
+ this.slots = {}, this.isLoading = !1, this.currentRequestId = null, this.adsAnalyticsMap = /* @__PURE__ */ new Map(), this.config = e, this.enabled = e.enabled !== !1, this.eventBus = new O(), this.slots_config = C(e.cardOption), typeof window < "u" && (window.__AD_CONFIG__ = {
996
1014
  ...window.__AD_CONFIG__ || {},
997
1015
  apiKey: e.apiKey,
998
1016
  apiBaseUrl: e.apiBaseUrl
@@ -1003,60 +1021,74 @@ const w = class w {
1003
1021
  });
1004
1022
  }
1005
1023
  async requestAds(e) {
1024
+ const t = "conversationContext" in e ? e : { conversationContext: e }, i = this.buildRequestKey(t), n = g.inFlightRequests.get(i);
1025
+ if (n)
1026
+ return this.config.debug && console.log("[AdManager] Reusing in-flight request for identical context"), n;
1006
1027
  if (!this.enabled)
1007
1028
  throw this.config.debug && console.warn("[AdManager] Ads are disabled, skipping request"), new Error("Ads are disabled");
1008
1029
  if (this.isLoading)
1009
1030
  throw this.config.debug && console.warn("[AdManager] Request already in progress"), new Error("Request already in progress");
1010
1031
  this.isLoading = !0, this.eventBus.emit("adsLoading"), this.config.debug && console.log("[AdManager] Starting ad request...");
1011
- const t = "conversationContext" in e ? e : { conversationContext: e };
1012
- try {
1013
- const i = await K(
1014
- {
1015
- conversationContext: t.conversationContext,
1016
- userContext: t.userContext ? { ...t.userContext, sessionId: t.userContext.sessionId ?? f() } : { sessionId: f() },
1017
- slots: this.slots_config
1018
- },
1019
- {
1020
- apiBaseUrl: this.config.apiBaseUrl,
1021
- apiKey: this.config.apiKey
1022
- }
1023
- );
1024
- this.currentRequestId = i.requestId, this.adsAnalyticsMap.clear(), i.slots.forEach((s) => {
1025
- s.ads.forEach((r, a) => {
1026
- const c = r.original.id;
1027
- this.adsAnalyticsMap.set(c, {
1028
- requestId: i.requestId,
1029
- slotId: s.slotId,
1030
- position: a,
1031
- totalAds: s.ads.length
1032
+ const s = (async () => {
1033
+ try {
1034
+ const r = await R(
1035
+ {
1036
+ conversationContext: t.conversationContext,
1037
+ userContext: t.userContext ? { ...t.userContext, sessionId: t.userContext.sessionId ?? m() } : { sessionId: m() },
1038
+ slots: this.slots_config
1039
+ },
1040
+ {
1041
+ apiBaseUrl: this.config.apiBaseUrl,
1042
+ apiKey: this.config.apiKey
1043
+ }
1044
+ );
1045
+ this.currentRequestId = r.requestId, this.adsAnalyticsMap.clear(), r.slots.forEach((c) => {
1046
+ c.ads.forEach((d, l) => {
1047
+ const u = d.original.id;
1048
+ this.adsAnalyticsMap.set(u, {
1049
+ requestId: r.requestId,
1050
+ slotId: c.slotId,
1051
+ position: l,
1052
+ totalAds: c.ads.length
1053
+ });
1032
1054
  });
1033
1055
  });
1034
- });
1035
- const n = {};
1036
- return i.slots.forEach((s) => {
1037
- n[s.slotId] = s;
1038
- }), this.slots = n, this.eventBus.emit("adsUpdated", this.slots), this.config.debug && console.log("[AdManager] Ads received:", {
1039
- slotCount: Object.keys(this.slots).length,
1040
- slots: Object.keys(this.slots)
1041
- }), i;
1042
- } catch (i) {
1043
- const n = i;
1044
- throw this.eventBus.emit("adsError", n), n;
1045
- } finally {
1046
- this.isLoading = !1;
1047
- }
1056
+ const a = {};
1057
+ return r.slots.forEach((c) => {
1058
+ a[c.slotId] = c;
1059
+ }), this.slots = a, this.eventBus.emit("adsUpdated", this.slots), this.config.debug && console.log("[AdManager] Ads received:", {
1060
+ slotCount: Object.keys(this.slots).length,
1061
+ slots: Object.keys(this.slots)
1062
+ }), r;
1063
+ } catch (r) {
1064
+ const a = r;
1065
+ throw this.eventBus.emit("adsError", a), a;
1066
+ } finally {
1067
+ this.isLoading = !1, g.inFlightRequests.delete(i);
1068
+ }
1069
+ })();
1070
+ return g.inFlightRequests.set(i, s), s;
1071
+ }
1072
+ buildRequestKey(e) {
1073
+ return JSON.stringify({
1074
+ apiBaseUrl: this.config.apiBaseUrl,
1075
+ apiKey: this.config.apiKey,
1076
+ conversationContext: e.conversationContext,
1077
+ userContext: e.userContext || null,
1078
+ slots: this.slots_config
1079
+ });
1048
1080
  }
1049
1081
  render(e, t, i = {}) {
1050
- const n = typeof e != "string", s = n ? w.DEFAULT_SLOT_ID : e, r = n ? e : t, a = (n ? t : i) || {};
1082
+ const n = typeof e != "string", s = n ? g.DEFAULT_SLOT_ID : e, r = n ? e : t, a = (n ? t : i) || {};
1051
1083
  if (!r)
1052
1084
  return this.config.debug && console.warn("[AdManager] Render container is required"), null;
1053
1085
  const c = this.slots[s];
1054
1086
  if (!c || !c.ads || c.ads.length === 0)
1055
1087
  return this.config.debug && console.warn("[AdManager] No ads in slot:", s), null;
1056
- const l = a.adIndex ?? 0, d = c.ads[l];
1057
- if (!d)
1058
- return this.config.debug && console.warn(`[AdManager] Ad at index ${l} not found in slot:`, s), null;
1059
- const u = this.adsAnalyticsMap.get(d.original.id), p = this.slots_config.find((_) => _.slotId === s), m = (p == null ? void 0 : p.format) || "action_card";
1088
+ const d = a.adIndex ?? 0, l = c.ads[d];
1089
+ if (!l)
1090
+ return this.config.debug && console.warn(`[AdManager] Ad at index ${d} not found in slot:`, s), null;
1091
+ const u = this.adsAnalyticsMap.get(l.original.id), f = this.slots_config.find((w) => w.slotId === s), p = (f == null ? void 0 : f.format) || "action_card";
1060
1092
  r.innerHTML = "";
1061
1093
  const h = {
1062
1094
  analytics: u,
@@ -1064,7 +1096,7 @@ const w = class w {
1064
1096
  onClick: a.onClick,
1065
1097
  onImpression: a.onImpression
1066
1098
  };
1067
- return m === "suffix" ? v.renderSuffixAd(d, r, h) : m === "source" ? v.renderSponsoredSource(d, r, h) : m === "lead_gen" ? v.renderLeadGenAd(d, r, h) : v.renderActionCard(d, r, h);
1099
+ return p === "suffix" ? y.renderSuffixAd(l, r, h) : p === "source" ? y.renderSponsoredSource(l, r, h) : p === "lead_gen" ? y.renderLeadGenAd(l, r, h) : y.renderActionCard(l, r, h);
1068
1100
  }
1069
1101
  /**
1070
1102
  * Get ad slots data
@@ -1103,7 +1135,7 @@ const w = class w {
1103
1135
  * Update configuration
1104
1136
  */
1105
1137
  updateConfig(e) {
1106
- this.config = { ...this.config, ...e }, e.cardOption !== void 0 && (this.slots_config = $(this.config.cardOption)), this.config.debug && console.log("[AdManager] Config updated:", e);
1138
+ this.config = { ...this.config, ...e }, e.cardOption !== void 0 && (this.slots_config = C(this.config.cardOption)), this.config.debug && console.log("[AdManager] Config updated:", e);
1107
1139
  }
1108
1140
  /**
1109
1141
  * Clear all slot data
@@ -1158,7 +1190,7 @@ const w = class w {
1158
1190
  const n = this.adsAnalyticsMap.get(e);
1159
1191
  if (!n || !this.currentRequestId)
1160
1192
  return this.config.debug && console.warn("[AdManager] No analytics info for ad:", e), null;
1161
- const s = f(), r = await A({
1193
+ const s = m(), r = await S({
1162
1194
  requestId: this.currentRequestId,
1163
1195
  adId: e,
1164
1196
  destinationUrl: t,
@@ -1177,7 +1209,7 @@ const w = class w {
1177
1209
  const i = this.adsAnalyticsMap.get(e);
1178
1210
  if (!i || !this.currentRequestId)
1179
1211
  return this.config.debug && console.warn("[AdManager] No analytics info for ad:", e), null;
1180
- const n = f(), s = await x({
1212
+ const n = m(), s = await A({
1181
1213
  requestId: this.currentRequestId,
1182
1214
  adId: e,
1183
1215
  slotId: i.slotId,
@@ -1198,16 +1230,16 @@ const w = class w {
1198
1230
  this.removeAllListeners(), this.clearSlots(), this.config.debug && console.log("[AdManager] Destroyed");
1199
1231
  }
1200
1232
  };
1201
- w.DEFAULT_SLOT_ID = "action_card";
1202
- let I = w;
1203
- function M() {
1233
+ g.DEFAULT_SLOT_ID = "action_card", g.inFlightRequests = /* @__PURE__ */ new Map();
1234
+ let V = g;
1235
+ function E() {
1204
1236
  if (typeof window < "u") {
1205
1237
  const o = window.__AD_CONFIG__;
1206
1238
  if (o != null && o.apiKey)
1207
1239
  return o.apiKey;
1208
1240
  }
1209
1241
  }
1210
- class N {
1242
+ class H {
1211
1243
  constructor(e = {}, t = "/api/v1") {
1212
1244
  var i, n;
1213
1245
  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 = {
@@ -1242,7 +1274,7 @@ class N {
1242
1274
  };
1243
1275
  this.metrics.set(e, r), this.isTracking.set(e, !0), this.eventQueue.set(e, []);
1244
1276
  const a = new IntersectionObserver(
1245
- (l) => this.handleIntersection(e, l[0], i, n, s),
1277
+ (d) => this.handleIntersection(e, d[0], i, n, s),
1246
1278
  {
1247
1279
  threshold: this.createThresholds()
1248
1280
  }
@@ -1274,8 +1306,8 @@ class N {
1274
1306
  if (!r || !this.isTracking.get(e)) return;
1275
1307
  const a = r.isViewable, c = Math.round(t.intersectionRatio * 100);
1276
1308
  r.visiblePercentage = c, r.maxVisiblePercentage = Math.max(r.maxVisiblePercentage, c);
1277
- const l = Date.now(), d = c >= this.config.minVisiblePercentage;
1278
- if (d && !r.enteredViewportAt && (r.enteredViewportAt = l, r.enterCount++, this.log(`[ViewabilityTracker] ${e} entered viewport at ${l}`), this.queueEvent(e, {
1309
+ const d = Date.now(), l = c >= this.config.minVisiblePercentage;
1310
+ if (l && !r.enteredViewportAt && (r.enteredViewportAt = d, r.enterCount++, this.log(`[ViewabilityTracker] ${e} entered viewport at ${d}`), this.queueEvent(e, {
1279
1311
  adId: e,
1280
1312
  sessionId: i,
1281
1313
  requestId: n,
@@ -1285,9 +1317,9 @@ class N {
1285
1317
  maxVisiblePercentage: r.maxVisiblePercentage,
1286
1318
  totalVisibleTimeMs: r.totalVisibleTimeMs,
1287
1319
  isViewable: !1,
1288
- timestamp: l
1289
- })), !d && r.enteredViewportAt) {
1290
- const u = l - r.enteredViewportAt;
1320
+ timestamp: d
1321
+ })), !l && r.enteredViewportAt) {
1322
+ const u = d - r.enteredViewportAt;
1291
1323
  r.totalVisibleTimeMs += u, r.enteredViewportAt = null, r.currentVisibleTimeMs = 0, this.log(`[ViewabilityTracker] ${e} exited viewport, total visible: ${r.totalVisibleTimeMs}ms`), this.queueEvent(e, {
1292
1324
  adId: e,
1293
1325
  sessionId: i,
@@ -1298,7 +1330,7 @@ class N {
1298
1330
  maxVisiblePercentage: r.maxVisiblePercentage,
1299
1331
  totalVisibleTimeMs: r.totalVisibleTimeMs,
1300
1332
  isViewable: r.isViewable,
1301
- timestamp: l
1333
+ timestamp: d
1302
1334
  });
1303
1335
  }
1304
1336
  r.isViewable !== a && r.isViewable && this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`), this.metrics.set(e, r);
@@ -1314,10 +1346,10 @@ class N {
1314
1346
  clearInterval(r);
1315
1347
  return;
1316
1348
  }
1317
- const c = Date.now(), l = a.isViewable;
1349
+ const c = Date.now(), d = a.isViewable;
1318
1350
  if (a.enteredViewportAt) {
1319
- const d = c - a.enteredViewportAt;
1320
- 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, {
1351
+ const l = c - a.enteredViewportAt;
1352
+ a.currentVisibleTimeMs = l, l >= this.config.minViewableDuration && a.visiblePercentage >= this.config.minVisiblePercentage && (a.isViewable = !0, d || (a.viewableAt = a.enteredViewportAt, this.log(`[ViewabilityTracker] ${e} BECAME VIEWABLE at ${a.viewableAt}`), this.queueEvent(e, {
1321
1353
  adId: e,
1322
1354
  sessionId: t,
1323
1355
  requestId: i,
@@ -1351,10 +1383,10 @@ class N {
1351
1383
  else {
1352
1384
  const a = this.batchTimers.get(e);
1353
1385
  a && clearTimeout(a);
1354
- const c = ((r = this.config.batchConfig) == null ? void 0 : r.maxBatchWaitMs) ?? 1e4, l = setTimeout(() => {
1386
+ const c = ((r = this.config.batchConfig) == null ? void 0 : r.maxBatchWaitMs) ?? 1e4, d = setTimeout(() => {
1355
1387
  this.flushQueue(e);
1356
1388
  }, c);
1357
- this.batchTimers.set(e, l);
1389
+ this.batchTimers.set(e, d);
1358
1390
  }
1359
1391
  }
1360
1392
  /**
@@ -1368,7 +1400,7 @@ class N {
1368
1400
  try {
1369
1401
  const n = {
1370
1402
  "Content-Type": "application/json"
1371
- }, s = M();
1403
+ }, s = E();
1372
1404
  s && (n["x-api-key"] = s), await fetch(`${this.baseUrl}/ads/viewability/batch`, {
1373
1405
  method: "POST",
1374
1406
  headers: n,
@@ -1442,7 +1474,7 @@ class N {
1442
1474
  if (t && t.length > 0) {
1443
1475
  const i = {
1444
1476
  "Content-Type": "application/json"
1445
- }, n = M();
1477
+ }, n = E();
1446
1478
  n && (i["x-api-key"] = n), fetch(`${this.baseUrl}/ads/viewability/batch`, {
1447
1479
  method: "POST",
1448
1480
  headers: i,
@@ -1454,39 +1486,39 @@ class N {
1454
1486
  });
1455
1487
  }
1456
1488
  }
1457
- let b = null;
1458
- function te(o, e) {
1459
- return b || (b = new N(o, e), b.setupBeforeUnload()), b;
1489
+ let k = null;
1490
+ function ie(o, e) {
1491
+ return k || (k = new H(o, e), k.setupBeforeUnload()), k;
1460
1492
  }
1461
- const ie = "0.1.0";
1493
+ const ne = "0.1.0";
1462
1494
  export {
1463
- I as AdManager,
1464
- V as AnalyticsSender,
1465
- S as ClientInfoCollector,
1466
- v as DOMRenderer,
1467
- y as HTMLRenderer,
1468
- ie as SDK_VERSION,
1469
- C as SessionManager,
1470
- N as ViewabilityTracker,
1471
- ee as adaptAdToKoahAd,
1472
- X as clearClientInfoCache,
1473
- F as createAnalytics,
1474
- j as createSession,
1475
- K as fetchAds,
1476
- H as generateViewToken,
1477
- W as getAppInfo,
1478
- g as getClientInfo,
1479
- T as getClientInfoCollector,
1480
- Z as getClientInfoJSON,
1481
- Y as getClientInfoSummary,
1482
- z as getDeviceInfo,
1483
- Q as getGeoInfo,
1484
- f as getSessionId,
1485
- J as getUserId,
1486
- q as getUserInfo,
1487
- te as getViewabilityTracker,
1488
- A as trackAdClick,
1489
- x as trackAdImpression,
1490
- G as trackClicksBatch,
1491
- D as trackImpressionsBatch
1495
+ V as AdManager,
1496
+ P as AnalyticsSender,
1497
+ T as ClientInfoCollector,
1498
+ y as DOMRenderer,
1499
+ b as HTMLRenderer,
1500
+ ne as SDK_VERSION,
1501
+ _ as SessionManager,
1502
+ H as ViewabilityTracker,
1503
+ te as adaptAdToKoahAd,
1504
+ Z as clearClientInfoCache,
1505
+ j as createAnalytics,
1506
+ z as createSession,
1507
+ R as fetchAds,
1508
+ F as generateViewToken,
1509
+ Q as getAppInfo,
1510
+ v as getClientInfo,
1511
+ $ as getClientInfoCollector,
1512
+ Y as getClientInfoJSON,
1513
+ ee as getClientInfoSummary,
1514
+ W as getDeviceInfo,
1515
+ J as getGeoInfo,
1516
+ m as getSessionId,
1517
+ X as getUserId,
1518
+ K as getUserInfo,
1519
+ ie as getViewabilityTracker,
1520
+ S as trackAdClick,
1521
+ A as trackAdImpression,
1522
+ D as trackClicksBatch,
1523
+ G as trackImpressionsBatch
1492
1524
  };
package/dist/index.umd.js CHANGED
@@ -1,19 +1,19 @@
1
- (function(c,y){typeof exports=="object"&&typeof module<"u"?y(exports):typeof define=="function"&&define.amd?define(["exports"],y):(c=typeof globalThis<"u"?globalThis:c||self,y(c.ActionXAdSDK={}))})(this,function(c){"use strict";class y{constructor(){this.events={}}on(e,t){this.events[e]||(this.events[e]=[]),this.events[e].push(t)}off(e,t){if(!this.events[e])return;const i=this.events[e].indexOf(t);i>-1&&this.events[e].splice(i,1)}emit(e,...t){this.events[e]&&this.events[e].forEach(i=>{try{i(...t)}catch(n){console.error(`[EventEmitter] Error in event handler for "${e}":`,n)}})}removeAllListeners(e){e?delete this.events[e]:this.events={}}listenerCount(e){var t;return((t=this.events[e])==null?void 0:t.length)||0}}class m{static renderActionCard(e,t={}){var p;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}">`:"",l=(p=s.image)!=null&&p.url?`<img
1
+ (function(l,k){typeof exports=="object"&&typeof module<"u"?k(exports):typeof define=="function"&&define.amd?define(["exports"],k):(l=typeof globalThis<"u"?globalThis:l||self,k(l.ActionXAdSDK={}))})(this,function(l){"use strict";class k{constructor(){this.events={}}on(e,t){this.events[e]||(this.events[e]=[]),this.events[e].push(t)}off(e,t){if(!this.events[e])return;const i=this.events[e].indexOf(t);i>-1&&this.events[e].splice(i,1)}emit(e,...t){this.events[e]&&this.events[e].forEach(i=>{try{i(...t)}catch(n){console.error(`[EventEmitter] Error in event handler for "${e}":`,n)}})}removeAllListeners(e){e?delete this.events[e]:this.events={}}listenerCount(e){var t;return((t=this.events[e])==null?void 0:t.length)||0}}class w{static renderActionCard(e,t={}){var f;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=(f=s.image)!=null&&f.url?`<img
2
2
  src="${s.image.url}"
3
3
  alt="${s.title}"
4
4
  class="ax-ad-image"
5
5
  loading="lazy"
6
- />`:"",d=s.price?`<span class="ax-ad-price">${s.price.display||s.price.value}</span>`:"",u=s.rating?`<div class="ax-ad-rating" aria-label="Rating: ${s.rating}">
6
+ />`:"",u=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}">
7
7
  ${"★".repeat(Math.floor(s.rating))}${"☆".repeat(5-Math.floor(s.rating))}
8
- </div>`:"",h=s.brand?`<span class="ax-ad-brand">${s.brand}</span>`:"",v=`
9
- ${l}
8
+ </div>`:"",h=s.brand?`<span class="ax-ad-brand">${s.brand}</span>`:"",p=`
9
+ ${c}
10
10
  <div class="ax-ad-content">
11
11
  ${h}
12
12
  <h3 class="ax-ad-title">${s.title}</h3>
13
13
  ${s.body?`<p class="ax-ad-body">${s.body}</p>`:""}
14
- ${u}
14
+ ${d}
15
15
  <div class="ax-ad-footer">
16
- ${d}
16
+ ${u}
17
17
  <a
18
18
  href="${r.clickUrl}"
19
19
  class="ax-ad-cta"
@@ -26,7 +26,7 @@
26
26
  </a>
27
27
  </div>
28
28
  </div>
29
- `,w=n?"</div>":"";return a+v+w}static renderSuffixAd(e){const t=e.adapted,i=e.tracking;return`
29
+ `,b=n?"</div>":"";return a+p+b}static renderSuffixAd(e){const t=e.adapted,i=e.tracking;return`
30
30
  <div class="ax-ad-suffix" data-ad-id="${e.original.id}">
31
31
  <div class="ax-ad-suffix-content">
32
32
  ${t.title?`<h4 class="ax-ad-suffix-title">${t.title}</h4>`:""}
@@ -78,8 +78,8 @@
78
78
  </a>
79
79
  </div>
80
80
  </div>
81
- `}static renderAds(e,t=m.renderActionCard.bind(m)){return e.map(t).join(`
82
- `)}}const B="/api/v1/ads",q="ad_session_id";function K(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}class ${constructor(e={}){this.memoryCache=new Map,this.sessionKey=e.sessionKey||q,this.storage=e.storage||"sessionStorage",(typeof window>"u"||typeof window.sessionStorage>"u")&&(this.storage="memory")}getSessionId(){if(this.storage==="sessionStorage"){let e=sessionStorage.getItem(this.sessionKey);return e||(e=this.generateSessionId(),sessionStorage.setItem(this.sessionKey,e)),e}else{let e=this.memoryCache.get(this.sessionKey);return e||(e=this.generateSessionId(),this.memoryCache.set(this.sessionKey,e)),e}}regenerateSessionId(){const e=this.generateSessionId();return this.storage==="sessionStorage"?sessionStorage.setItem(this.sessionKey,e):this.memoryCache.set(this.sessionKey,e),e}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substring(2,11)}`}clearSessionId(){this.storage==="sessionStorage"?sessionStorage.removeItem(this.sessionKey):this.memoryCache.delete(this.sessionKey)}}const R=new $,f=()=>R.getSessionId();class M{constructor(e={}){this.baseUrl=e.baseUrl||B,this.timeout=e.timeout||1e4,this.retryAttempts=e.retryAttempts??2,this.retryDelay=e.retryDelay||1e3,this.debug=e.debug||!1}async trackImpression(e){return this.sendRequest(`${this.baseUrl}/impression`,e,"impression")}async trackClick(e){return this.sendRequest(`${this.baseUrl}/click`,e,"click")}async sendRequest(e,t,i){let n=null;for(let s=0;s<=this.retryAttempts;s++)try{this.debug&&console.log(`[Analytics] Sending ${i} event (attempt ${s+1}):`,t);const r=new AbortController,a=setTimeout(()=>r.abort(),this.timeout),l={"Content-Type":"application/json"},d=K();d&&(l["X-API-Key"]=d,this.debug&&console.log(`[Analytics] Adding X-API-Key header for ${i} event`));const u=await fetch(e,{method:"POST",headers:l,body:JSON.stringify(t),keepalive:!0,signal:r.signal});if(clearTimeout(a),!u.ok)throw new Error(`HTTP ${u.status}: ${u.statusText}`);const h=await u.json();return this.debug&&console.log(`[Analytics] ${i} event tracked successfully:`,h),h}catch(r){n=r,this.debug&&console.warn(`[Analytics] ${i} tracking failed (attempt ${s+1}):`,r),s<this.retryAttempts&&await this.delay(this.retryDelay*(s+1))}return console.error(`[Analytics] ${i} tracking failed after ${this.retryAttempts+1} attempts:`,n),null}delay(e){return new Promise(t=>setTimeout(t,e))}}const E=new M,k=o=>E.trackImpression(o),A=o=>E.trackClick(o);function N(o,e){return`vt_${o}_${e}`}async function H(o){return Promise.all(o.map(e=>k(e)))}async function D(o){return Promise.all(o.map(e=>A(e)))}function G(o){const e=new M(o);return{trackImpression:t=>e.trackImpression(t),trackClick:t=>e.trackClick(t)}}function j(o={}){const e=new $(o);return{getSessionId:()=>e.getSessionId(),regenerateSessionId:()=>e.regenerateSessionId(),clearSessionId:()=>e.clearSessionId()}}class b{static renderActionCard(e,t,i={}){t.innerHTML=m.renderActionCard(e,i);const n=t.querySelector(".ax-ad-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSuffixAd(e,t,i={}){const n=i.variant||"block";t.innerHTML=this.generateSuffixAdHTML(e,n);const s=t.querySelector(".ax-ad-suffix-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSponsoredSource(e,t,i={}){const n=i.variant||"card";t.innerHTML=this.generateSponsoredSourceHTML(e,n);const s=t.querySelector(".ax-ad-source-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderLeadGenAd(e,t,i={}){t.innerHTML=m.renderLeadGenAd(e);const n=t.querySelector(".ax-ad-leadgen-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderAds(e,t,i=this.renderActionCard.bind(this),n){t.innerHTML="";const s=document.createElement("div");return s.className="ax-ads-container",e.forEach(r=>{const a=document.createElement("div");a.className="ax-ad-wrapper",i.call(this,r,a,n),s.appendChild(a)}),t.appendChild(s),t}static handleClick(e,t){const{analytics:i,onClick:n}=t;n&&n(e),i&&A({requestId:i.requestId,adId:e.original.id,destinationUrl:e.tracking.clickUrl,slotId:i.slotId,sessionId:f(),adTitle:e.adapted.title,format:e.original.type}).catch(s=>{console.error("[DOMRenderer] Analytics click tracking failed:",s)}),window.open(e.tracking.clickUrl,"_blank","noopener,noreferrer")}static trackImpression(e,t,i){const{analytics:n,onImpression:s}=i,r=()=>{n?k({requestId:n.requestId,adId:e.original.id,slotId:n.slotId,position:n.position,totalAds:n.totalAds,sessionId:f(),adTitle:e.adapted.title,format:e.original.type,source:"internal",viewToken:e.tracking.viewToken}).then(()=>{s&&s(e)}).catch(a=>{console.error("[DOMRenderer] Analytics impression tracking failed:",a)}):s&&s(e)};if(typeof IntersectionObserver<"u"){const a=new IntersectionObserver(d=>{d.forEach(u=>{u.isIntersecting&&(setTimeout(()=>{r()},1e3),a.disconnect())})},{threshold:.5}),l=t.querySelector(`[data-ad-id="${e.original.id}"]`)||t;a.observe(l)}else r()}static generateSuffixAdHTML(e,t){const i=e.adapted.title||"",n=e.adapted.body||"";return t==="inline"?`
81
+ `}static renderAds(e,t=w.renderActionCard.bind(w)){return e.map(t).join(`
82
+ `)}}const R="ad_session_id";function N(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}function _(o){if(o)return o;if(typeof window<"u"){const e=window.__AD_CONFIG__;if(e!=null&&e.apiBaseUrl)return e.apiBaseUrl}return"/api/v1"}class C{constructor(e={}){this.memoryCache=new Map,this.sessionKey=e.sessionKey||R,this.storage=e.storage||"sessionStorage",(typeof window>"u"||typeof window.sessionStorage>"u")&&(this.storage="memory")}getSessionId(){if(this.storage==="sessionStorage"){let e=sessionStorage.getItem(this.sessionKey);return e||(e=this.generateSessionId(),sessionStorage.setItem(this.sessionKey,e)),e}else{let e=this.memoryCache.get(this.sessionKey);return e||(e=this.generateSessionId(),this.memoryCache.set(this.sessionKey,e)),e}}regenerateSessionId(){const e=this.generateSessionId();return this.storage==="sessionStorage"?sessionStorage.setItem(this.sessionKey,e):this.memoryCache.set(this.sessionKey,e),e}generateSessionId(){return`session_${Date.now()}_${Math.random().toString(36).substring(2,11)}`}clearSessionId(){this.storage==="sessionStorage"?sessionStorage.removeItem(this.sessionKey):this.memoryCache.delete(this.sessionKey)}}const H=new C,m=()=>H.getSessionId();class M{constructor(e={}){this.explicitBaseUrl=e.baseUrl,this.timeout=e.timeout||1e4,this.retryAttempts=e.retryAttempts??2,this.retryDelay=e.retryDelay||1e3,this.debug=e.debug||!1}async trackImpression(e){return this.sendRequest(`${_(this.explicitBaseUrl)}/ads/impression`,e,"impression")}async trackClick(e){return this.sendRequest(`${_(this.explicitBaseUrl)}/ads/click`,e,"click")}async sendRequest(e,t,i){let n=null;for(let s=0;s<=this.retryAttempts;s++)try{this.debug&&console.log(`[Analytics] Sending ${i} event (attempt ${s+1}):`,t);const r=new AbortController,a=setTimeout(()=>r.abort(),this.timeout),c={"Content-Type":"application/json"},u=N();u&&(c["X-API-Key"]=u,this.debug&&console.log(`[Analytics] Adding X-API-Key header for ${i} event`));const d=await fetch(e,{method:"POST",headers:c,body:JSON.stringify(t),keepalive:!0,signal:r.signal});if(clearTimeout(a),!d.ok)throw new Error(`HTTP ${d.status}: ${d.statusText}`);const h=await d.json();return this.debug&&console.log(`[Analytics] ${i} event tracked successfully:`,h),h}catch(r){n=r,this.debug&&console.warn(`[Analytics] ${i} tracking failed (attempt ${s+1}):`,r),s<this.retryAttempts&&await this.delay(this.retryDelay*(s+1))}return console.error(`[Analytics] ${i} tracking failed after ${this.retryAttempts+1} attempts:`,n),null}delay(e){return new Promise(t=>setTimeout(t,e))}}const P=new M,A=o=>P.trackImpression(o),x=o=>P.trackClick(o);function D(o,e){return`vt_${o}_${e}`}async function G(o){return Promise.all(o.map(e=>A(e)))}async function F(o){return Promise.all(o.map(e=>x(e)))}function j(o){const e=new M(o);return{trackImpression:t=>e.trackImpression(t),trackClick:t=>e.trackClick(t)}}function z(o={}){const e=new C(o);return{getSessionId:()=>e.getSessionId(),regenerateSessionId:()=>e.regenerateSessionId(),clearSessionId:()=>e.clearSessionId()}}const U=class U{static renderActionCard(e,t,i={}){t.innerHTML=w.renderActionCard(e,i);const n=t.querySelector(".ax-ad-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSuffixAd(e,t,i={}){const n=i.variant||"block";t.innerHTML=this.generateSuffixAdHTML(e,n);const s=t.querySelector(".ax-ad-suffix-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderSponsoredSource(e,t,i={}){const n=i.variant||"card";t.innerHTML=this.generateSponsoredSourceHTML(e,n);const s=t.querySelector(".ax-ad-source-link");return s&&s.addEventListener("click",r=>{r.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderLeadGenAd(e,t,i={}){t.innerHTML=w.renderLeadGenAd(e);const n=t.querySelector(".ax-ad-leadgen-cta");return n&&n.addEventListener("click",s=>{s.preventDefault(),this.handleClick(e,i)}),this.trackImpression(e,t,i),t}static renderAds(e,t,i=this.renderActionCard.bind(this),n){t.innerHTML="";const s=document.createElement("div");return s.className="ax-ads-container",e.forEach(r=>{const a=document.createElement("div");a.className="ax-ad-wrapper",i.call(this,r,a,n),s.appendChild(a)}),t.appendChild(s),t}static handleClick(e,t){const{analytics:i,onClick:n}=t;n&&n(e),i&&x({requestId:i.requestId,adId:e.original.id,destinationUrl:e.tracking.clickUrl,slotId:i.slotId,sessionId:m(),adTitle:e.adapted.title,format:e.original.type}).catch(s=>{console.error("[DOMRenderer] Analytics click tracking failed:",s)}),window.open(e.tracking.clickUrl,"_blank","noopener,noreferrer")}static trackImpression(e,t,i){const{analytics:n,onImpression:s}=i,r=n?`${n.requestId}:${n.slotId}:${e.original.id}:${e.tracking.viewToken||""}`:`no-analytics:${e.original.id}:${e.tracking.viewToken||""}`;if(this.trackedImpressionKeys.has(r))return;let a=!1;const c=()=>{a||this.trackedImpressionKeys.has(r)||(a=!0,this.trackedImpressionKeys.add(r),u())},u=()=>{n?A({requestId:n.requestId,adId:e.original.id,slotId:n.slotId,position:n.position,totalAds:n.totalAds,sessionId:m(),adTitle:e.adapted.title,format:e.original.type,source:"internal",viewToken:e.tracking.viewToken}).then(()=>{s&&s(e)}).catch(d=>{console.error("[DOMRenderer] Analytics impression tracking failed:",d)}):s&&s(e)};if(typeof IntersectionObserver<"u"){let d=!1,h=null;const p=new IntersectionObserver(f=>{f.forEach($=>{d=$.isIntersecting&&$.intersectionRatio>=.5,d?h||(h=setTimeout(()=>{h=null,d&&(c(),p.disconnect())},1e3)):h&&(clearTimeout(h),h=null)})},{threshold:.5}),b=t.querySelector(`[data-ad-id="${e.original.id}"]`)||t;p.observe(b)}else c()}static generateSuffixAdHTML(e,t){const i=e.adapted.title||"",n=e.adapted.body||"";return t==="inline"?`
83
83
  <span class="ax-ad-suffix-link ax-ad-variant-inline" data-ad-id="${e.original.id}">
84
84
  ${i}
85
85
  <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="display:inline;vertical-align:middle;margin-left:4px">
@@ -136,4 +136,4 @@
136
136
  <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" />
137
137
  </svg>
138
138
  </div>
139
- `}}class x{constructor(){this.cachedStaticInfo=null,this.cacheTimestamp=0,this.CACHE_TTL=36e5}inferAppInfo(){if(typeof window>"u")return{bundle:"unknown",ver:"1.0.0",name:"Unknown App",publisher:{id:"unknown"}};const e=this.inferBundle(),t=this.inferAppName(),i=this.inferAppVersion(),n=this.inferPublisherId();return{bundle:e,ver:i,name:t,publisher:{id:n,domain:window.location.hostname}}}inferBundle(){var t;if(typeof window>"u")return"unknown";const e=(t=window.location)==null?void 0:t.hostname;return e?e.replace(/^www\./,"").replace(/:\d+$/,""):"unknown"}inferAppName(){var n,s;if(typeof document>"u")return"Unknown App";const e=document.title;if(e&&e!=="Loading..."&&e.length<100)return e;const t=(n=document.querySelector('meta[property="og:title"]'))==null?void 0:n.getAttribute("content");if(t)return t;const i=(s=document.querySelector('meta[name="application-name"]'))==null?void 0:s.getAttribute("content");return i||"Unknown App"}inferAppVersion(){if(typeof document>"u")return"1.0.0";const e=['meta[name="version"]','meta[name="app-version"]','meta[name="application-version"]','link[rel="manifest"]'];for(const t of e){const i=document.querySelector(t);if(i){if(t.includes("manifest"))return"1.0.0";const n=i.getAttribute("content");if(n)return n}}return"1.0.0"}inferPublisherId(){var i,n;if(typeof document>"u")return"unknown";const e=(i=document.querySelector('meta[name="publisher-id"]'))==null?void 0:i.getAttribute("content");if(e)return e;const t=(n=window.location)==null?void 0:n.hostname;return t?t.replace(/\./g,"_"):"unknown"}static getInstance(){return this.instance||(this.instance=new x),this.instance}collect(e){const t=Date.now();(!this.cachedStaticInfo||t-this.cacheTimestamp>this.CACHE_TTL)&&(this.cachedStaticInfo=this.collectStaticInfo(),this.cacheTimestamp=t);const i=this.collectDynamicInfo();return{device:{ua:this.cachedStaticInfo.ua,os:this.cachedStaticInfo.os,osv:this.cachedStaticInfo.osv,devicetype:this.cachedStaticInfo.devicetype,model:this.cachedStaticInfo.model,make:this.cachedStaticInfo.make,language:this.cachedStaticInfo.language,connectiontype:i.connectiontype??0,bandwidth:i.bandwidth,...(e==null?void 0:e.device)||{}},app:{...this.inferAppInfo(),...(e==null?void 0:e.app)||{}},user:{...this.collectUserInfo(),...(e==null?void 0:e.user)||{}},geo:{...this.collectGeoInfo(),...(e==null?void 0:e.geo)||{}},timestamp:t}}collectStaticInfo(){if(typeof navigator>"u")return this.getFallbackInfo();const e=navigator.userAgent,t=this.detectOS(e),i=this.detectOSVersion(e);return{ua:e,os:t,osv:i,devicetype:this.detectDeviceType(e),model:this.detectModel(e),make:this.detectMake(e),language:navigator.language||"en-US"}}collectDynamicInfo(){if(typeof navigator>"u")return{connectiontype:0};const e=this.getConnectionInfo();return{connectiontype:(e==null?void 0:e.type)||0,bandwidth:e==null?void 0:e.downlink}}collectUserInfo(){let e;try{e=localStorage.getItem("chatbox_user_id")||"",e||(e=this.generateUUID(),localStorage.setItem("chatbox_user_id",e))}catch{e=this.generateUUID()}return{id:e,language:(navigator==null?void 0:navigator.language)||"en-US"}}collectGeoInfo(){if(typeof navigator>"u"||typeof Intl>"u")return{};const e=Intl.DateTimeFormat().resolvedOptions().timeZone,t=navigator.language;return{country:this.inferCountry(t,e),timezone:e}}detectOS(e){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"}detectOSVersion(e){if(!e)return"Unknown";const t=e.match(/OS (\d+)_(\d+)_?(\d+)?/);if(t)return`${t[1]}.${t[2]}${t[3]?"."+t[3]:""}`;const i=e.match(/Android (\d+)\.(\d+)/);if(i)return`${i[1]}.${i[2]}`;const n=e.match(/Windows NT (\d+\.\d+)/);if(n)return n[1];const s=e.match(/Mac OS X (\d+[._]\d+)/);return s?s[1].replace("_","."):"Unknown"}detectDeviceType(e){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}detectModel(e){if(!e)return;const t=e.match(/iPhone(?:;\s*[^\)]*)?\s*(\d+,\d)?/);if(t)return`iPhone${t[1]||""}`;if(/iPad/.test(e))return"iPad";const i=e.match(/Samsung-.*(SM-\w+)/);if(i)return i[1];if(/Pixel/.test(e))return"Google Pixel";const n=e.match(/(BARC-|HUAWEI-)?([A-Z]{2}\-\w{4})/);if(n)return n[2];const s=e.match(/(MI|Redmi|POCO)\s([\w\s]+)/);if(s)return s[1]+" "+s[2];if(/OnePlus/.test(e)){const r=e.match(/OnePlus\s([A-Z\d]+)/);return r?"OnePlus "+r[1]:"OnePlus"}}detectMake(e){if(e){if(/iPad|iPhone|iPod/.test(e))return"Apple";if(/Samsung/.test(e))return"Samsung";if(/Pixel/.test(e))return"Google";if(/Huawei|HONOR/.test(e))return"Huawei";if(/Xiaomi|Redmi|POCO/.test(e))return"Xiaomi";if(/OnePlus/.test(e))return"OnePlus";if(/Sony/.test(e))return"Sony";if(/LG/.test(e))return"LG";if(/Motorola|Moto/.test(e))return"Motorola";if(/Nokia/.test(e))return"Nokia";if(/HTC/.test(e))return"HTC";if(/OPPO/.test(e))return"OPPO";if(/vivo/.test(e))return"vivo";if(/Realme/.test(e))return"Realme"}}getConnectionInfo(){if(typeof navigator>"u")return null;const e=navigator,t=e.connection||e.mozConnection||e.webkitConnection;return t?{type:this.mapConnectionType(t.effectiveType),downlink:t.downlink,rtt:t.rtt}:null}mapConnectionType(e){if(!e)return 0;switch(e.toLowerCase()){case"slow-2g":case"2g":return 3;case"3g":return 4;case"4g":return 5;default:return 0}}inferCountry(e,t){if(!e&&!t)return;const i={"Asia/Shanghai":"CN","Asia/Hong_Kong":"HK","Asia/Taipei":"TW","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Singapore":"SG","Asia/Dubai":"AE","Asia/Kolkata":"IN","Asia/Jakarta":"ID","Asia/Manila":"PH","Asia/Bangkok":"TH","Asia/Kuala_Lumpur":"MY","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Mexico_City":"MX","America/Sao_Paulo":"BR","America/Buenos_Aires":"AR","Europe/London":"GB","Europe/Paris":"FR","Europe/Berlin":"DE","Europe/Madrid":"ES","Europe/Rome":"IT","Europe/Amsterdam":"NL","Europe/Brussels":"BE","Europe/Zurich":"CH","Europe/Vienna":"AT","Europe/Stockholm":"SE","Europe/Oslo":"NO","Europe/Copenhagen":"DK","Europe/Helsinki":"FI","Europe/Dublin":"IE","Europe/Lisbon":"PT","Europe/Athens":"GR","Europe/Prague":"CZ","Europe/Warsaw":"PL","Europe/Budapest":"HU","Europe/Bucharest":"RO","Australia/Sydney":"AU","Pacific/Auckland":"NZ"};if(t&&i[t])return i[t];if(e){const n=e.split("-")[1];if(n)return n.toUpperCase()}}generateUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}getFallbackInfo(){return{ua:"Unknown",os:"Unknown",osv:"Unknown",devicetype:0,language:"en-US"}}clearCache(){this.cachedStaticInfo=null,this.cacheTimestamp=0}}const S=()=>x.getInstance();function g(o){return S().collect(o)}function F(){return g().device}function _(){return g().user}function z(){return g().app}function W(){return g().geo}function Q(){return _().id}function J(){S().clearCache()}function X(o){const e=g(o);return JSON.stringify(e,null,2)}function Z(){var t;const o=g();return[o.device.os,o.device.osv,o.app.name,o.user.id.slice(0,8)+"...",((t=o.geo)==null?void 0:t.country)||"Unknown"].join(" / ")}function Y(o){var e,t;return{id:o.original.id,type:o.original.type,score:o.original.score,source:"internal",content:{title:o.adapted.title,body:o.adapted.body,image:(e=o.adapted.image)==null?void 0:e.url,cta_text:o.adapted.ctaText,price:(t=o.adapted.price)==null?void 0:t.display,rating:o.adapted.rating,brand:o.adapted.brand},tracking:{click_url:o.tracking.clickUrl,impression_url:o.tracking.impressionUrl},metadata:{viewToken:o.tracking.viewToken,styling:o.adapted.styling}}}async function P(o,e={}){const t=e.apiBaseUrl||"/api/v1",n=S().collect(),s={...o,clientInfo:n},r={"Content-Type":"application/json"};e.apiKey&&(r["X-API-Key"]=e.apiKey);const a=await fetch(`${t}/ads/request`,{method:"POST",headers:r,body:JSON.stringify(s)});if(!a.ok)throw new Error(`Ad request failed: ${a.status} ${a.statusText}`);const l=await a.json();if(!l.success)throw new Error(l.error||"Ad request failed");return l.data}const ee={width:400,height:200},C={variant:"horizontal",count:1,preferences:{}};function U(o={}){const e={variant:o.variant??C.variant,count:o.count??C.count,preferences:{...C.preferences,...o.preferences}};return[{slotId:"action_card",slotName:"Action Card",format:"action_card",variant:e.variant,size:ee,count:e.count,preferences:e.preferences,placement:{position:"below_fold",context:"post_response"}}]}const I=class I{constructor(e){this.slots={},this.isLoading=!1,this.currentRequestId=null,this.adsAnalyticsMap=new Map,this.config=e,this.enabled=e.enabled!==!1,this.eventBus=new y,this.slots_config=U(e.cardOption),typeof window<"u"&&(window.__AD_CONFIG__={...window.__AD_CONFIG__||{},apiKey:e.apiKey,apiBaseUrl:e.apiBaseUrl}),this.config.debug&&console.log("[AdManager] Initialized with config:",{apiBaseUrl:e.apiBaseUrl,hasApiKey:!!e.apiKey,enabled:this.enabled})}async requestAds(e){if(!this.enabled)throw this.config.debug&&console.warn("[AdManager] Ads are disabled, skipping request"),new Error("Ads are disabled");if(this.isLoading)throw this.config.debug&&console.warn("[AdManager] Request already in progress"),new Error("Request already in progress");this.isLoading=!0,this.eventBus.emit("adsLoading"),this.config.debug&&console.log("[AdManager] Starting ad request...");const t="conversationContext"in e?e:{conversationContext:e};try{const i=await P({conversationContext:t.conversationContext,userContext:t.userContext?{...t.userContext,sessionId:t.userContext.sessionId??f()}:{sessionId:f()},slots:this.slots_config},{apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey});this.currentRequestId=i.requestId,this.adsAnalyticsMap.clear(),i.slots.forEach(s=>{s.ads.forEach((r,a)=>{const l=r.original.id;this.adsAnalyticsMap.set(l,{requestId:i.requestId,slotId:s.slotId,position:a,totalAds:s.ads.length})})});const n={};return i.slots.forEach(s=>{n[s.slotId]=s}),this.slots=n,this.eventBus.emit("adsUpdated",this.slots),this.config.debug&&console.log("[AdManager] Ads received:",{slotCount:Object.keys(this.slots).length,slots:Object.keys(this.slots)}),i}catch(i){const n=i;throw this.eventBus.emit("adsError",n),n}finally{this.isLoading=!1}}render(e,t,i={}){const n=typeof e!="string",s=n?I.DEFAULT_SLOT_ID:e,r=n?e:t,a=(n?t:i)||{};if(!r)return this.config.debug&&console.warn("[AdManager] Render container is required"),null;const l=this.slots[s];if(!l||!l.ads||l.ads.length===0)return this.config.debug&&console.warn("[AdManager] No ads in slot:",s),null;const d=a.adIndex??0,u=l.ads[d];if(!u)return this.config.debug&&console.warn(`[AdManager] Ad at index ${d} not found in slot:`,s),null;const h=this.adsAnalyticsMap.get(u.original.id),v=this.slots_config.find(ne=>ne.slotId===s),w=(v==null?void 0:v.format)||"action_card";r.innerHTML="";const p={analytics:h,variant:a.variant,onClick:a.onClick,onImpression:a.onImpression};return w==="suffix"?b.renderSuffixAd(u,r,p):w==="source"?b.renderSponsoredSource(u,r,p):w==="lead_gen"?b.renderLeadGenAd(u,r,p):b.renderActionCard(u,r,p)}getSlots(e){return e?this.slots[e]:this.slots}hasAds(e){var i;const t=this.slots[e];return(t==null?void 0:t.status)==="filled"&&((i=t.ads)==null?void 0:i.length)>0}getAds(e){var t;return((t=this.slots[e])==null?void 0:t.ads)||[]}getLoadingStatus(){return this.isLoading}setEnabled(e){this.enabled=e,this.config.debug&&console.log("[AdManager] Ads",e?"enabled":"disabled")}updateConfig(e){this.config={...this.config,...e},e.cardOption!==void 0&&(this.slots_config=U(this.config.cardOption)),this.config.debug&&console.log("[AdManager] Config updated:",e)}clearSlots(){this.slots={},this.currentRequestId=null,this.adsAnalyticsMap.clear(),this.config.debug&&console.log("[AdManager] Slots and analytics cleared")}on(e,t){this.eventBus.on(e,t)}off(e,t){this.eventBus.off(e,t)}removeAllListeners(e){this.eventBus.removeAllListeners(e)}getCurrentRequestId(){return this.currentRequestId}getAdAnalytics(e){return this.adsAnalyticsMap.get(e)||null}getAdsAnalytics(e){const t=new Map;return e.forEach(i=>{const n=this.adsAnalyticsMap.get(i);n&&t.set(i,n)}),t}async trackAdClick(e,t,i){const n=this.adsAnalyticsMap.get(e);if(!n||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const s=f(),r=await A({requestId:this.currentRequestId,adId:e,destinationUrl:t,sessionId:s,slotId:n.slotId,adTitle:i==null?void 0:i.title,format:i==null?void 0:i.format,source:i==null?void 0:i.source});return r&&(this.eventBus.emit("adClicked",e,n.slotId),this.config.debug&&console.log("[AdManager] Click tracked via Analytics API:",r.eventId)),r}async trackAdImpressionAPI(e,t){const i=this.adsAnalyticsMap.get(e);if(!i||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const n=f(),s=await k({requestId:this.currentRequestId,adId:e,slotId:i.slotId,position:i.position,totalAds:i.totalAds,sessionId:n,adTitle:t==null?void 0:t.title,format:t==null?void 0:t.format,source:t==null?void 0:t.source,viewToken:t==null?void 0:t.viewToken});return s&&(this.eventBus.emit("adImpression",e,i.slotId),this.config.debug&&console.log("[AdManager] Impression tracked via Analytics API:",s.eventId)),s}destroy(){this.removeAllListeners(),this.clearSlots(),this.config.debug&&console.log("[AdManager] Destroyed")}};I.DEFAULT_SLOT_ID="action_card";let V=I;function O(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}class L{constructor(e={},t="/api/v1"){var i,n;this.observers=new Map,this.metrics=new Map,this.timers=new Map,this.batchTimers=new Map,this.eventQueue=new Map,this.isTracking=new Map,this.config={minVisiblePercentage:e.minVisiblePercentage??50,minViewableDuration:e.minViewableDuration??1e3,maxTrackingDuration:e.maxTrackingDuration??6e4,batchConfig:{maxBatchSize:((i=e.batchConfig)==null?void 0:i.maxBatchSize)??5,maxBatchWaitMs:((n=e.batchConfig)==null?void 0:n.maxBatchWaitMs)??1e4},debug:e.debug??!1},this.baseUrl=t}startTracking(e,t,i,n,s){if(this.isTracking.get(e)){this.log(`[ViewabilityTracker] Already tracking ${e}, skipping`);return}this.log(`[ViewabilityTracker] Starting tracking for ${e}`);const r={visiblePercentage:0,maxVisiblePercentage:0,totalVisibleTimeMs:0,currentVisibleTimeMs:0,isViewable:!1,viewableAt:null,enteredViewportAt:null,enterCount:0};this.metrics.set(e,r),this.isTracking.set(e,!0),this.eventQueue.set(e,[]);const a=new IntersectionObserver(d=>this.handleIntersection(e,d[0],i,n,s),{threshold:this.createThresholds()});a.observe(t),this.observers.set(e,a),this.startMonitoring(e,i,n,s);const l=setTimeout(()=>{this.endTracking(e,i,n,s)},this.config.maxTrackingDuration);this.timers.set(e,l)}stopTracking(e){this.log(`[ViewabilityTracker] Manual stop tracking for ${e}`),this.cleanup(e)}getMetrics(e){return this.metrics.get(e)}handleIntersection(e,t,i,n,s){const r=this.metrics.get(e);if(!r||!this.isTracking.get(e))return;const a=r.isViewable,l=Math.round(t.intersectionRatio*100);r.visiblePercentage=l,r.maxVisiblePercentage=Math.max(r.maxVisiblePercentage,l);const d=Date.now(),u=l>=this.config.minVisiblePercentage;if(u&&!r.enteredViewportAt&&(r.enteredViewportAt=d,r.enterCount++,this.log(`[ViewabilityTracker] ${e} entered viewport at ${d}`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"enter_viewport",visiblePercentage:l,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:!1,timestamp:d})),!u&&r.enteredViewportAt){const h=d-r.enteredViewportAt;r.totalVisibleTimeMs+=h,r.enteredViewportAt=null,r.currentVisibleTimeMs=0,this.log(`[ViewabilityTracker] ${e} exited viewport, total visible: ${r.totalVisibleTimeMs}ms`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"exit_viewport",visiblePercentage:l,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:r.isViewable,timestamp:d})}r.isViewable!==a&&r.isViewable&&this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`),this.metrics.set(e,r)}startMonitoring(e,t,i,n){const r=setInterval(()=>{const a=this.metrics.get(e);if(!a||!this.isTracking.get(e)){clearInterval(r);return}const l=Date.now(),d=a.isViewable;if(a.enteredViewportAt){const u=l-a.enteredViewportAt;a.currentVisibleTimeMs=u,u>=this.config.minViewableDuration&&a.visiblePercentage>=this.config.minVisiblePercentage&&(a.isViewable=!0,d||(a.viewableAt=a.enteredViewportAt,this.log(`[ViewabilityTracker] ${e} BECAME VIEWABLE at ${a.viewableAt}`),this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"become_viewable",visiblePercentage:a.visiblePercentage,maxVisiblePercentage:a.maxVisiblePercentage,totalVisibleTimeMs:a.totalVisibleTimeMs,isViewable:!0,timestamp:l})))}a.enteredViewportAt&&(a.totalVisibleTimeMs+=100),this.metrics.set(e,a)},100);this.timers.set(`${e}_monitoring`,r)}queueEvent(e,t){var s,r;const i=this.eventQueue.get(e);if(!i){this.log(`[ViewabilityTracker] No queue found for ${e}, skipping event`);return}i.push(t),this.log(`[ViewabilityTracker] Queued ${t.eventType} for ${e} (queue size: ${i.length})`);const n=((s=this.config.batchConfig)==null?void 0:s.maxBatchSize)??5;if(i.length>=n)this.flushQueue(e);else{const a=this.batchTimers.get(e);a&&clearTimeout(a);const l=((r=this.config.batchConfig)==null?void 0:r.maxBatchWaitMs)??1e4,d=setTimeout(()=>{this.flushQueue(e)},l);this.batchTimers.set(e,d)}}async flushQueue(e){const t=this.eventQueue.get(e);if(!t||t.length===0)return;const i=[...t];t.length=0,this.log(`[ViewabilityTracker] Flushing ${i.length} events for ${e}`);try{const n={"Content-Type":"application/json"},s=O();s&&(n["x-api-key"]=s),await fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:n,body:JSON.stringify({events:i})}),this.log(`[ViewabilityTracker] Successfully sent ${i.length} events for ${e}`)}catch(n){this.log(`[ViewabilityTracker] Failed to send events for ${e}:`,n),t.unshift(...i)}}endTracking(e,t,i,n){const s=this.metrics.get(e);if(!s)return;this.log(`[ViewabilityTracker] Ending tracking for ${e}`),this.flushQueue(e);const r=Date.now();this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"end_tracking",visiblePercentage:s.visiblePercentage,maxVisiblePercentage:s.maxVisiblePercentage,totalVisibleTimeMs:s.totalVisibleTimeMs,isViewable:s.isViewable,timestamp:r}),this.flushQueue(e),this.cleanup(e)}cleanup(e){const t=this.observers.get(e);t&&(t.disconnect(),this.observers.delete(e));const i=this.timers.get(e);i&&(clearTimeout(i),this.timers.delete(e));const n=this.timers.get(`${e}_monitoring`);n&&(clearInterval(n),this.timers.delete(`${e}_monitoring`));const s=this.batchTimers.get(e);s&&(clearTimeout(s),this.batchTimers.delete(e)),this.flushQueue(e),this.isTracking.delete(e),this.metrics.delete(e),this.eventQueue.delete(e)}createThresholds(){return[0,.25,.5,.75,.9,1]}stopAll(){const e=Array.from(this.isTracking.keys());for(const t of e)this.cleanup(t)}log(...e){this.config.debug&&console.log(...e)}setupBeforeUnload(){typeof window<"u"&&window.addEventListener("beforeunload",()=>{for(const e of this.eventQueue.keys()){const t=this.eventQueue.get(e);if(t&&t.length>0){const i={"Content-Type":"application/json"},n=O();n&&(i["x-api-key"]=n),fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:i,keepalive:!0,body:JSON.stringify({events:t})})}}})}}let T=null;function te(o,e){return T||(T=new L(o,e),T.setupBeforeUnload()),T}const ie="0.1.0";c.AdManager=V,c.AnalyticsSender=M,c.ClientInfoCollector=x,c.DOMRenderer=b,c.HTMLRenderer=m,c.SDK_VERSION=ie,c.SessionManager=$,c.ViewabilityTracker=L,c.adaptAdToKoahAd=Y,c.clearClientInfoCache=J,c.createAnalytics=G,c.createSession=j,c.fetchAds=P,c.generateViewToken=N,c.getAppInfo=z,c.getClientInfo=g,c.getClientInfoCollector=S,c.getClientInfoJSON=X,c.getClientInfoSummary=Z,c.getDeviceInfo=F,c.getGeoInfo=W,c.getSessionId=f,c.getUserId=Q,c.getUserInfo=_,c.getViewabilityTracker=te,c.trackAdClick=A,c.trackAdImpression=k,c.trackClicksBatch=D,c.trackImpressionsBatch=H,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
139
+ `}};U.trackedImpressionKeys=new Set;let y=U;class S{constructor(){this.cachedStaticInfo=null,this.cacheTimestamp=0,this.CACHE_TTL=36e5}inferAppInfo(){if(typeof window>"u")return{bundle:"unknown",ver:"1.0.0",name:"Unknown App",publisher:{id:"unknown"}};const e=this.inferBundle(),t=this.inferAppName(),i=this.inferAppVersion(),n=this.inferPublisherId();return{bundle:e,ver:i,name:t,publisher:{id:n,domain:window.location.hostname}}}inferBundle(){var t;if(typeof window>"u")return"unknown";const e=(t=window.location)==null?void 0:t.hostname;return e?e.replace(/^www\./,"").replace(/:\d+$/,""):"unknown"}inferAppName(){var n,s;if(typeof document>"u")return"Unknown App";const e=document.title;if(e&&e!=="Loading..."&&e.length<100)return e;const t=(n=document.querySelector('meta[property="og:title"]'))==null?void 0:n.getAttribute("content");if(t)return t;const i=(s=document.querySelector('meta[name="application-name"]'))==null?void 0:s.getAttribute("content");return i||"Unknown App"}inferAppVersion(){if(typeof document>"u")return"1.0.0";const e=['meta[name="version"]','meta[name="app-version"]','meta[name="application-version"]','link[rel="manifest"]'];for(const t of e){const i=document.querySelector(t);if(i){if(t.includes("manifest"))return"1.0.0";const n=i.getAttribute("content");if(n)return n}}return"1.0.0"}inferPublisherId(){var i,n;if(typeof document>"u")return"unknown";const e=(i=document.querySelector('meta[name="publisher-id"]'))==null?void 0:i.getAttribute("content");if(e)return e;const t=(n=window.location)==null?void 0:n.hostname;return t?t.replace(/\./g,"_"):"unknown"}static getInstance(){return this.instance||(this.instance=new S),this.instance}collect(e){const t=Date.now();(!this.cachedStaticInfo||t-this.cacheTimestamp>this.CACHE_TTL)&&(this.cachedStaticInfo=this.collectStaticInfo(),this.cacheTimestamp=t);const i=this.collectDynamicInfo();return{device:{ua:this.cachedStaticInfo.ua,os:this.cachedStaticInfo.os,osv:this.cachedStaticInfo.osv,devicetype:this.cachedStaticInfo.devicetype,model:this.cachedStaticInfo.model,make:this.cachedStaticInfo.make,language:this.cachedStaticInfo.language,connectiontype:i.connectiontype??0,bandwidth:i.bandwidth,...(e==null?void 0:e.device)||{}},app:{...this.inferAppInfo(),...(e==null?void 0:e.app)||{}},user:{...this.collectUserInfo(),...(e==null?void 0:e.user)||{}},geo:{...this.collectGeoInfo(),...(e==null?void 0:e.geo)||{}},timestamp:t}}collectStaticInfo(){if(typeof navigator>"u")return this.getFallbackInfo();const e=navigator.userAgent,t=this.detectOS(e),i=this.detectOSVersion(e);return{ua:e,os:t,osv:i,devicetype:this.detectDeviceType(e),model:this.detectModel(e),make:this.detectMake(e),language:navigator.language||"en-US"}}collectDynamicInfo(){if(typeof navigator>"u")return{connectiontype:0};const e=this.getConnectionInfo();return{connectiontype:(e==null?void 0:e.type)||0,bandwidth:e==null?void 0:e.downlink}}collectUserInfo(){let e;try{e=localStorage.getItem("chatbox_user_id")||"",e||(e=this.generateUUID(),localStorage.setItem("chatbox_user_id",e))}catch{e=this.generateUUID()}return{id:e,language:(navigator==null?void 0:navigator.language)||"en-US"}}collectGeoInfo(){if(typeof navigator>"u"||typeof Intl>"u")return{};const e=Intl.DateTimeFormat().resolvedOptions().timeZone,t=navigator.language;return{country:this.inferCountry(t,e),timezone:e}}detectOS(e){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"}detectOSVersion(e){if(!e)return"Unknown";const t=e.match(/OS (\d+)_(\d+)_?(\d+)?/);if(t)return`${t[1]}.${t[2]}${t[3]?"."+t[3]:""}`;const i=e.match(/Android (\d+)\.(\d+)/);if(i)return`${i[1]}.${i[2]}`;const n=e.match(/Windows NT (\d+\.\d+)/);if(n)return n[1];const s=e.match(/Mac OS X (\d+[._]\d+)/);return s?s[1].replace("_","."):"Unknown"}detectDeviceType(e){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}detectModel(e){if(!e)return;const t=e.match(/iPhone(?:;\s*[^\)]*)?\s*(\d+,\d)?/);if(t)return`iPhone${t[1]||""}`;if(/iPad/.test(e))return"iPad";const i=e.match(/Samsung-.*(SM-\w+)/);if(i)return i[1];if(/Pixel/.test(e))return"Google Pixel";const n=e.match(/(BARC-|HUAWEI-)?([A-Z]{2}\-\w{4})/);if(n)return n[2];const s=e.match(/(MI|Redmi|POCO)\s([\w\s]+)/);if(s)return s[1]+" "+s[2];if(/OnePlus/.test(e)){const r=e.match(/OnePlus\s([A-Z\d]+)/);return r?"OnePlus "+r[1]:"OnePlus"}}detectMake(e){if(e){if(/iPad|iPhone|iPod/.test(e))return"Apple";if(/Samsung/.test(e))return"Samsung";if(/Pixel/.test(e))return"Google";if(/Huawei|HONOR/.test(e))return"Huawei";if(/Xiaomi|Redmi|POCO/.test(e))return"Xiaomi";if(/OnePlus/.test(e))return"OnePlus";if(/Sony/.test(e))return"Sony";if(/LG/.test(e))return"LG";if(/Motorola|Moto/.test(e))return"Motorola";if(/Nokia/.test(e))return"Nokia";if(/HTC/.test(e))return"HTC";if(/OPPO/.test(e))return"OPPO";if(/vivo/.test(e))return"vivo";if(/Realme/.test(e))return"Realme"}}getConnectionInfo(){if(typeof navigator>"u")return null;const e=navigator,t=e.connection||e.mozConnection||e.webkitConnection;return t?{type:this.mapConnectionType(t.effectiveType),downlink:t.downlink,rtt:t.rtt}:null}mapConnectionType(e){if(!e)return 0;switch(e.toLowerCase()){case"slow-2g":case"2g":return 3;case"3g":return 4;case"4g":return 5;default:return 0}}inferCountry(e,t){if(!e&&!t)return;const i={"Asia/Shanghai":"CN","Asia/Hong_Kong":"HK","Asia/Taipei":"TW","Asia/Tokyo":"JP","Asia/Seoul":"KR","Asia/Singapore":"SG","Asia/Dubai":"AE","Asia/Kolkata":"IN","Asia/Jakarta":"ID","Asia/Manila":"PH","Asia/Bangkok":"TH","Asia/Kuala_Lumpur":"MY","America/New_York":"US","America/Chicago":"US","America/Denver":"US","America/Los_Angeles":"US","America/Toronto":"CA","America/Vancouver":"CA","America/Mexico_City":"MX","America/Sao_Paulo":"BR","America/Buenos_Aires":"AR","Europe/London":"GB","Europe/Paris":"FR","Europe/Berlin":"DE","Europe/Madrid":"ES","Europe/Rome":"IT","Europe/Amsterdam":"NL","Europe/Brussels":"BE","Europe/Zurich":"CH","Europe/Vienna":"AT","Europe/Stockholm":"SE","Europe/Oslo":"NO","Europe/Copenhagen":"DK","Europe/Helsinki":"FI","Europe/Dublin":"IE","Europe/Lisbon":"PT","Europe/Athens":"GR","Europe/Prague":"CZ","Europe/Warsaw":"PL","Europe/Budapest":"HU","Europe/Bucharest":"RO","Australia/Sydney":"AU","Pacific/Auckland":"NZ"};if(t&&i[t])return i[t];if(e){const n=e.split("-")[1];if(n)return n.toUpperCase()}}generateUUID(){return typeof crypto<"u"&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,e=>{const t=Math.random()*16|0;return(e==="x"?t:t&3|8).toString(16)})}getFallbackInfo(){return{ua:"Unknown",os:"Unknown",osv:"Unknown",devicetype:0,language:"en-US"}}clearCache(){this.cachedStaticInfo=null,this.cacheTimestamp=0}}const T=()=>S.getInstance();function v(o){return T().collect(o)}function W(){return v().device}function O(){return v().user}function Q(){return v().app}function J(){return v().geo}function X(){return O().id}function Z(){T().clearCache()}function Y(o){const e=v(o);return JSON.stringify(e,null,2)}function ee(){var t;const o=v();return[o.device.os,o.device.osv,o.app.name,o.user.id.slice(0,8)+"...",((t=o.geo)==null?void 0:t.country)||"Unknown"].join(" / ")}function te(o){var e,t;return{id:o.original.id,type:o.original.type,score:o.original.score,source:"internal",content:{title:o.adapted.title,body:o.adapted.body,image:(e=o.adapted.image)==null?void 0:e.url,cta_text:o.adapted.ctaText,price:(t=o.adapted.price)==null?void 0:t.display,rating:o.adapted.rating,brand:o.adapted.brand},tracking:{click_url:o.tracking.clickUrl,impression_url:o.tracking.impressionUrl},metadata:{viewToken:o.tracking.viewToken,styling:o.adapted.styling}}}async function B(o,e={}){const t=e.apiBaseUrl||"/api/v1",n=T().collect(),s={...o,clientInfo:n},r={"Content-Type":"application/json"};e.apiKey&&(r["X-API-Key"]=e.apiKey);const a=await fetch(`${t}/ads/request`,{method:"POST",headers:r,body:JSON.stringify(s)});if(!a.ok)throw new Error(`Ad request failed: ${a.status} ${a.statusText}`);const c=await a.json();if(!c.success)throw new Error(c.error||"Ad request failed");return c.data}const ie={width:400,height:200},V={variant:"horizontal",count:1,preferences:{}};function q(o={}){const e={variant:o.variant??V.variant,count:o.count??V.count,preferences:{...V.preferences,...o.preferences}};return[{slotId:"action_card",slotName:"Action Card",format:"action_card",variant:e.variant,size:ie,count:e.count,preferences:e.preferences,placement:{position:"below_fold",context:"post_response"}}]}const g=class g{constructor(e){this.slots={},this.isLoading=!1,this.currentRequestId=null,this.adsAnalyticsMap=new Map,this.config=e,this.enabled=e.enabled!==!1,this.eventBus=new k,this.slots_config=q(e.cardOption),typeof window<"u"&&(window.__AD_CONFIG__={...window.__AD_CONFIG__||{},apiKey:e.apiKey,apiBaseUrl:e.apiBaseUrl}),this.config.debug&&console.log("[AdManager] Initialized with config:",{apiBaseUrl:e.apiBaseUrl,hasApiKey:!!e.apiKey,enabled:this.enabled})}async requestAds(e){const t="conversationContext"in e?e:{conversationContext:e},i=this.buildRequestKey(t),n=g.inFlightRequests.get(i);if(n)return this.config.debug&&console.log("[AdManager] Reusing in-flight request for identical context"),n;if(!this.enabled)throw this.config.debug&&console.warn("[AdManager] Ads are disabled, skipping request"),new Error("Ads are disabled");if(this.isLoading)throw this.config.debug&&console.warn("[AdManager] Request already in progress"),new Error("Request already in progress");this.isLoading=!0,this.eventBus.emit("adsLoading"),this.config.debug&&console.log("[AdManager] Starting ad request...");const s=(async()=>{try{const r=await B({conversationContext:t.conversationContext,userContext:t.userContext?{...t.userContext,sessionId:t.userContext.sessionId??m()}:{sessionId:m()},slots:this.slots_config},{apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey});this.currentRequestId=r.requestId,this.adsAnalyticsMap.clear(),r.slots.forEach(c=>{c.ads.forEach((u,d)=>{const h=u.original.id;this.adsAnalyticsMap.set(h,{requestId:r.requestId,slotId:c.slotId,position:d,totalAds:c.ads.length})})});const a={};return r.slots.forEach(c=>{a[c.slotId]=c}),this.slots=a,this.eventBus.emit("adsUpdated",this.slots),this.config.debug&&console.log("[AdManager] Ads received:",{slotCount:Object.keys(this.slots).length,slots:Object.keys(this.slots)}),r}catch(r){const a=r;throw this.eventBus.emit("adsError",a),a}finally{this.isLoading=!1,g.inFlightRequests.delete(i)}})();return g.inFlightRequests.set(i,s),s}buildRequestKey(e){return JSON.stringify({apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey,conversationContext:e.conversationContext,userContext:e.userContext||null,slots:this.slots_config})}render(e,t,i={}){const n=typeof e!="string",s=n?g.DEFAULT_SLOT_ID:e,r=n?e:t,a=(n?t:i)||{};if(!r)return this.config.debug&&console.warn("[AdManager] Render container is required"),null;const c=this.slots[s];if(!c||!c.ads||c.ads.length===0)return this.config.debug&&console.warn("[AdManager] No ads in slot:",s),null;const u=a.adIndex??0,d=c.ads[u];if(!d)return this.config.debug&&console.warn(`[AdManager] Ad at index ${u} not found in slot:`,s),null;const h=this.adsAnalyticsMap.get(d.original.id),p=this.slots_config.find($=>$.slotId===s),b=(p==null?void 0:p.format)||"action_card";r.innerHTML="";const f={analytics:h,variant:a.variant,onClick:a.onClick,onImpression:a.onImpression};return b==="suffix"?y.renderSuffixAd(d,r,f):b==="source"?y.renderSponsoredSource(d,r,f):b==="lead_gen"?y.renderLeadGenAd(d,r,f):y.renderActionCard(d,r,f)}getSlots(e){return e?this.slots[e]:this.slots}hasAds(e){var i;const t=this.slots[e];return(t==null?void 0:t.status)==="filled"&&((i=t.ads)==null?void 0:i.length)>0}getAds(e){var t;return((t=this.slots[e])==null?void 0:t.ads)||[]}getLoadingStatus(){return this.isLoading}setEnabled(e){this.enabled=e,this.config.debug&&console.log("[AdManager] Ads",e?"enabled":"disabled")}updateConfig(e){this.config={...this.config,...e},e.cardOption!==void 0&&(this.slots_config=q(this.config.cardOption)),this.config.debug&&console.log("[AdManager] Config updated:",e)}clearSlots(){this.slots={},this.currentRequestId=null,this.adsAnalyticsMap.clear(),this.config.debug&&console.log("[AdManager] Slots and analytics cleared")}on(e,t){this.eventBus.on(e,t)}off(e,t){this.eventBus.off(e,t)}removeAllListeners(e){this.eventBus.removeAllListeners(e)}getCurrentRequestId(){return this.currentRequestId}getAdAnalytics(e){return this.adsAnalyticsMap.get(e)||null}getAdsAnalytics(e){const t=new Map;return e.forEach(i=>{const n=this.adsAnalyticsMap.get(i);n&&t.set(i,n)}),t}async trackAdClick(e,t,i){const n=this.adsAnalyticsMap.get(e);if(!n||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const s=m(),r=await x({requestId:this.currentRequestId,adId:e,destinationUrl:t,sessionId:s,slotId:n.slotId,adTitle:i==null?void 0:i.title,format:i==null?void 0:i.format,source:i==null?void 0:i.source});return r&&(this.eventBus.emit("adClicked",e,n.slotId),this.config.debug&&console.log("[AdManager] Click tracked via Analytics API:",r.eventId)),r}async trackAdImpressionAPI(e,t){const i=this.adsAnalyticsMap.get(e);if(!i||!this.currentRequestId)return this.config.debug&&console.warn("[AdManager] No analytics info for ad:",e),null;const n=m(),s=await A({requestId:this.currentRequestId,adId:e,slotId:i.slotId,position:i.position,totalAds:i.totalAds,sessionId:n,adTitle:t==null?void 0:t.title,format:t==null?void 0:t.format,source:t==null?void 0:t.source,viewToken:t==null?void 0:t.viewToken});return s&&(this.eventBus.emit("adImpression",e,i.slotId),this.config.debug&&console.log("[AdManager] Impression tracked via Analytics API:",s.eventId)),s}destroy(){this.removeAllListeners(),this.clearSlots(),this.config.debug&&console.log("[AdManager] Destroyed")}};g.DEFAULT_SLOT_ID="action_card",g.inFlightRequests=new Map;let E=g;function L(){if(typeof window<"u"){const o=window.__AD_CONFIG__;if(o!=null&&o.apiKey)return o.apiKey}}class K{constructor(e={},t="/api/v1"){var i,n;this.observers=new Map,this.metrics=new Map,this.timers=new Map,this.batchTimers=new Map,this.eventQueue=new Map,this.isTracking=new Map,this.config={minVisiblePercentage:e.minVisiblePercentage??50,minViewableDuration:e.minViewableDuration??1e3,maxTrackingDuration:e.maxTrackingDuration??6e4,batchConfig:{maxBatchSize:((i=e.batchConfig)==null?void 0:i.maxBatchSize)??5,maxBatchWaitMs:((n=e.batchConfig)==null?void 0:n.maxBatchWaitMs)??1e4},debug:e.debug??!1},this.baseUrl=t}startTracking(e,t,i,n,s){if(this.isTracking.get(e)){this.log(`[ViewabilityTracker] Already tracking ${e}, skipping`);return}this.log(`[ViewabilityTracker] Starting tracking for ${e}`);const r={visiblePercentage:0,maxVisiblePercentage:0,totalVisibleTimeMs:0,currentVisibleTimeMs:0,isViewable:!1,viewableAt:null,enteredViewportAt:null,enterCount:0};this.metrics.set(e,r),this.isTracking.set(e,!0),this.eventQueue.set(e,[]);const a=new IntersectionObserver(u=>this.handleIntersection(e,u[0],i,n,s),{threshold:this.createThresholds()});a.observe(t),this.observers.set(e,a),this.startMonitoring(e,i,n,s);const c=setTimeout(()=>{this.endTracking(e,i,n,s)},this.config.maxTrackingDuration);this.timers.set(e,c)}stopTracking(e){this.log(`[ViewabilityTracker] Manual stop tracking for ${e}`),this.cleanup(e)}getMetrics(e){return this.metrics.get(e)}handleIntersection(e,t,i,n,s){const r=this.metrics.get(e);if(!r||!this.isTracking.get(e))return;const a=r.isViewable,c=Math.round(t.intersectionRatio*100);r.visiblePercentage=c,r.maxVisiblePercentage=Math.max(r.maxVisiblePercentage,c);const u=Date.now(),d=c>=this.config.minVisiblePercentage;if(d&&!r.enteredViewportAt&&(r.enteredViewportAt=u,r.enterCount++,this.log(`[ViewabilityTracker] ${e} entered viewport at ${u}`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"enter_viewport",visiblePercentage:c,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:!1,timestamp:u})),!d&&r.enteredViewportAt){const h=u-r.enteredViewportAt;r.totalVisibleTimeMs+=h,r.enteredViewportAt=null,r.currentVisibleTimeMs=0,this.log(`[ViewabilityTracker] ${e} exited viewport, total visible: ${r.totalVisibleTimeMs}ms`),this.queueEvent(e,{adId:e,sessionId:i,requestId:n,viewToken:s,eventType:"exit_viewport",visiblePercentage:c,maxVisiblePercentage:r.maxVisiblePercentage,totalVisibleTimeMs:r.totalVisibleTimeMs,isViewable:r.isViewable,timestamp:u})}r.isViewable!==a&&r.isViewable&&this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`),this.metrics.set(e,r)}startMonitoring(e,t,i,n){const r=setInterval(()=>{const a=this.metrics.get(e);if(!a||!this.isTracking.get(e)){clearInterval(r);return}const c=Date.now(),u=a.isViewable;if(a.enteredViewportAt){const d=c-a.enteredViewportAt;a.currentVisibleTimeMs=d,d>=this.config.minViewableDuration&&a.visiblePercentage>=this.config.minVisiblePercentage&&(a.isViewable=!0,u||(a.viewableAt=a.enteredViewportAt,this.log(`[ViewabilityTracker] ${e} BECAME VIEWABLE at ${a.viewableAt}`),this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"become_viewable",visiblePercentage:a.visiblePercentage,maxVisiblePercentage:a.maxVisiblePercentage,totalVisibleTimeMs:a.totalVisibleTimeMs,isViewable:!0,timestamp:c})))}a.enteredViewportAt&&(a.totalVisibleTimeMs+=100),this.metrics.set(e,a)},100);this.timers.set(`${e}_monitoring`,r)}queueEvent(e,t){var s,r;const i=this.eventQueue.get(e);if(!i){this.log(`[ViewabilityTracker] No queue found for ${e}, skipping event`);return}i.push(t),this.log(`[ViewabilityTracker] Queued ${t.eventType} for ${e} (queue size: ${i.length})`);const n=((s=this.config.batchConfig)==null?void 0:s.maxBatchSize)??5;if(i.length>=n)this.flushQueue(e);else{const a=this.batchTimers.get(e);a&&clearTimeout(a);const c=((r=this.config.batchConfig)==null?void 0:r.maxBatchWaitMs)??1e4,u=setTimeout(()=>{this.flushQueue(e)},c);this.batchTimers.set(e,u)}}async flushQueue(e){const t=this.eventQueue.get(e);if(!t||t.length===0)return;const i=[...t];t.length=0,this.log(`[ViewabilityTracker] Flushing ${i.length} events for ${e}`);try{const n={"Content-Type":"application/json"},s=L();s&&(n["x-api-key"]=s),await fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:n,body:JSON.stringify({events:i})}),this.log(`[ViewabilityTracker] Successfully sent ${i.length} events for ${e}`)}catch(n){this.log(`[ViewabilityTracker] Failed to send events for ${e}:`,n),t.unshift(...i)}}endTracking(e,t,i,n){const s=this.metrics.get(e);if(!s)return;this.log(`[ViewabilityTracker] Ending tracking for ${e}`),this.flushQueue(e);const r=Date.now();this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"end_tracking",visiblePercentage:s.visiblePercentage,maxVisiblePercentage:s.maxVisiblePercentage,totalVisibleTimeMs:s.totalVisibleTimeMs,isViewable:s.isViewable,timestamp:r}),this.flushQueue(e),this.cleanup(e)}cleanup(e){const t=this.observers.get(e);t&&(t.disconnect(),this.observers.delete(e));const i=this.timers.get(e);i&&(clearTimeout(i),this.timers.delete(e));const n=this.timers.get(`${e}_monitoring`);n&&(clearInterval(n),this.timers.delete(`${e}_monitoring`));const s=this.batchTimers.get(e);s&&(clearTimeout(s),this.batchTimers.delete(e)),this.flushQueue(e),this.isTracking.delete(e),this.metrics.delete(e),this.eventQueue.delete(e)}createThresholds(){return[0,.25,.5,.75,.9,1]}stopAll(){const e=Array.from(this.isTracking.keys());for(const t of e)this.cleanup(t)}log(...e){this.config.debug&&console.log(...e)}setupBeforeUnload(){typeof window<"u"&&window.addEventListener("beforeunload",()=>{for(const e of this.eventQueue.keys()){const t=this.eventQueue.get(e);if(t&&t.length>0){const i={"Content-Type":"application/json"},n=L();n&&(i["x-api-key"]=n),fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:i,keepalive:!0,body:JSON.stringify({events:t})})}}})}}let I=null;function ne(o,e){return I||(I=new K(o,e),I.setupBeforeUnload()),I}const se="0.1.0";l.AdManager=E,l.AnalyticsSender=M,l.ClientInfoCollector=S,l.DOMRenderer=y,l.HTMLRenderer=w,l.SDK_VERSION=se,l.SessionManager=C,l.ViewabilityTracker=K,l.adaptAdToKoahAd=te,l.clearClientInfoCache=Z,l.createAnalytics=j,l.createSession=z,l.fetchAds=B,l.generateViewToken=D,l.getAppInfo=Q,l.getClientInfo=v,l.getClientInfoCollector=T,l.getClientInfoJSON=Y,l.getClientInfoSummary=ee,l.getDeviceInfo=W,l.getGeoInfo=J,l.getSessionId=m,l.getUserId=X,l.getUserInfo=O,l.getViewabilityTracker=ne,l.trackAdClick=x,l.trackAdImpression=A,l.trackClicksBatch=F,l.trackImpressionsBatch=G,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@action-x/ad-sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Zero-dependency ad SDK for ActionX",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -20,7 +20,10 @@
20
20
  "scripts": {
21
21
  "build": "tsc --noEmit && vite build",
22
22
  "dev": "vite build --watch",
23
- "typecheck": "tsc --noEmit"
23
+ "typecheck": "tsc --noEmit",
24
+ "release": "npm version patch && git push && git push --tags",
25
+ "release:minor": "npm version minor && git push && git push --tags",
26
+ "release:major": "npm version major && git push && git push --tags"
24
27
  },
25
28
  "devDependencies": {
26
29
  "typescript": "^5.3.0",