@action-x/ad-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,139 @@
1
+ (function(c,p){typeof exports=="object"&&typeof module<"u"?p(exports):typeof define=="function"&&define.amd?define(["exports"],p):(c=typeof globalThis<"u"?globalThis:c||self,p(c.ActionXAdSDK={}))})(this,function(c){"use strict";class p{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 g{static renderActionCard(e,t={}){var V;const{variant:i="horizontal",includeWrapper:n=!0}=t,s=e.adapted,r=e.tracking,o=n?`<div class="ax-ad-card ax-ad-card-${i}" data-ad-id="${e.original.id}">`:"",l=(V=s.image)!=null&&V.url?`<img
2
+ src="${s.image.url}"
3
+ alt="${s.title}"
4
+ class="ax-ad-image"
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}">
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>`:"",X=`
9
+ ${l}
10
+ <div class="ax-ad-content">
11
+ ${h}
12
+ <h3 class="ax-ad-title">${s.title}</h3>
13
+ ${s.body?`<p class="ax-ad-body">${s.body}</p>`:""}
14
+ ${u}
15
+ <div class="ax-ad-footer">
16
+ ${d}
17
+ <a
18
+ href="${r.clickUrl}"
19
+ class="ax-ad-cta"
20
+ target="_blank"
21
+ rel="sponsored noopener noreferrer"
22
+ data-ad-id="${e.original.id}"
23
+ data-impression-url="${r.impressionUrl}"
24
+ >
25
+ ${s.ctaText||"Learn More"}
26
+ </a>
27
+ </div>
28
+ </div>
29
+ `,Z=n?"</div>":"";return o+X+Z}static renderSuffixAd(e){const t=e.adapted,i=e.tracking;return`
30
+ <div class="ax-ad-suffix" data-ad-id="${e.original.id}">
31
+ <div class="ax-ad-suffix-content">
32
+ ${t.title?`<h4 class="ax-ad-suffix-title">${t.title}</h4>`:""}
33
+ ${t.body?`<p class="ax-ad-suffix-body">${t.body}</p>`:""}
34
+ <a
35
+ href="${i.clickUrl}"
36
+ class="ax-ad-suffix-link"
37
+ target="_blank"
38
+ rel="sponsored noopener noreferrer"
39
+ data-ad-id="${e.original.id}"
40
+ data-impression-url="${i.impressionUrl}"
41
+ >
42
+ ${t.ctaText||"Learn More"}
43
+ </a>
44
+ </div>
45
+ </div>
46
+ `}static renderSponsoredSource(e){var n;const t=e.adapted,i=e.tracking;return`
47
+ <div class="ax-ad-source" data-ad-id="${e.original.id}">
48
+ <a
49
+ href="${i.clickUrl}"
50
+ class="ax-ad-source-link"
51
+ target="_blank"
52
+ rel="sponsored noopener noreferrer"
53
+ data-ad-id="${e.original.id}"
54
+ data-impression-url="${i.impressionUrl}"
55
+ >
56
+ ${(n=t.image)!=null&&n.url?`<img src="${t.image.url}" alt="" class="ax-ad-source-icon" />`:""}
57
+ <div class="ax-ad-source-info">
58
+ <span class="ax-ad-source-name">${t.title}</span>
59
+ ${t.body?`<span class="ax-ad-source-desc">${t.body}</span>`:""}
60
+ </div>
61
+ <span class="ax-ad-source-badge">Sponsored</span>
62
+ </a>
63
+ </div>
64
+ `}static renderLeadGenAd(e){const t=e.adapted,i=e.tracking;return`
65
+ <div class="ax-ad-leadgen" data-ad-id="${e.original.id}">
66
+ <div class="ax-ad-leadgen-content">
67
+ ${t.title?`<h3 class="ax-ad-leadgen-title">${t.title}</h3>`:""}
68
+ ${t.body?`<p class="ax-ad-leadgen-body">${t.body}</p>`:""}
69
+ <a
70
+ href="${i.clickUrl}"
71
+ class="ax-ad-leadgen-cta"
72
+ target="_blank"
73
+ rel="sponsored noopener noreferrer"
74
+ data-ad-id="${e.original.id}"
75
+ data-impression-url="${i.impressionUrl}"
76
+ >
77
+ ${t.ctaText||"Get Started"}
78
+ </a>
79
+ </div>
80
+ </div>
81
+ `}static renderAds(e,t=g.renderActionCard.bind(g)){return e.map(t).join(`
82
+ `)}}const E="/api/v1/ads",P="ad_session_id";function U(){if(typeof window<"u"){const a=window.__AD_CONFIG__;if(a!=null&&a.apiKey)return a.apiKey}}class x{constructor(e={}){this.memoryCache=new Map,this.sessionKey=e.sessionKey||P,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 _=new x,m=()=>_.getSessionId();class S{constructor(e={}){this.baseUrl=e.baseUrl||E,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,o=setTimeout(()=>r.abort(),this.timeout),l={"Content-Type":"application/json"},d=U();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(o),!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 T=new S,b=a=>T.trackImpression(a),v=a=>T.trackClick(a);function O(a,e){return`vt_${a}_${e}`}async function B(a){return Promise.all(a.map(e=>b(e)))}async function L(a){return Promise.all(a.map(e=>v(e)))}function q(a){const e=new S(a);return{trackImpression:t=>e.trackImpression(t),trackClick:t=>e.trackClick(t)}}function K(a={}){const e=new x(a);return{getSessionId:()=>e.getSessionId(),regenerateSessionId:()=>e.regenerateSessionId(),clearSessionId:()=>e.clearSessionId()}}class y{static renderActionCard(e,t,i={}){t.innerHTML=g.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=g.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 o=document.createElement("div");o.className="ax-ad-wrapper",i.call(this,r,o,n),s.appendChild(o)}),t.appendChild(s),t}static handleClick(e,t){const{analytics:i,onClick:n}=t;n&&n(e),i&&v({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?b({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(o=>{console.error("[DOMRenderer] Analytics impression tracking failed:",o)}):s&&s(e)};if(typeof IntersectionObserver<"u"){const o=new IntersectionObserver(d=>{d.forEach(u=>{u.isIntersecting&&(setTimeout(()=>{r()},1e3),o.disconnect())})},{threshold:.5}),l=t.querySelector(`[data-ad-id="${e.original.id}"]`)||t;o.observe(l)}else r()}static generateSuffixAdHTML(e,t){const i=e.adapted.title||"",n=e.adapted.body||"";return t==="inline"?`
83
+ <span class="ax-ad-suffix-link ax-ad-variant-inline" data-ad-id="${e.original.id}">
84
+ ${i}
85
+ <svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24" style="display:inline;vertical-align:middle;margin-left:4px">
86
+ <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" />
87
+ </svg>
88
+ </span>
89
+ `:t==="minimal"?`
90
+ <span class="ax-ad-suffix-link ax-ad-variant-minimal" data-ad-id="${e.original.id}">
91
+ ${i}
92
+ </span>
93
+ `:`
94
+ <div class="ax-ad-suffix ax-ad-variant-block" data-ad-id="${e.original.id}">
95
+ <div class="ax-ad-suffix-content">
96
+ <p class="ax-ad-suffix-title">${i}</p>
97
+ <div style="display:flex;align-items:center;gap:8px;flex-shrink:0">
98
+ <span class="ax-ad-sponsored-badge">Sponsored</span>
99
+ <svg width="16" height="16" fill="none" stroke="#d1d5db" viewBox="0 0 24 24">
100
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 8l4 4m0 0l-4 4m4-4H3" />
101
+ </svg>
102
+ </div>
103
+ </div>
104
+ ${n?`<p class="ax-ad-suffix-body">${n}</p>`:""}
105
+ </div>
106
+ `}static generateSponsoredSourceHTML(e,t){var r;const i=e.adapted.title||"",n=e.adapted.body||"",s=((r=e.adapted.image)==null?void 0:r.url)||"";return t==="list-item"?`
107
+ <div class="ax-ad-source ax-ad-variant-list-item" data-ad-id="${e.original.id}">
108
+ ${s?`<img src="${s}" alt="${i}" loading="lazy" />`:""}
109
+ <div class="ax-ad-source-info">
110
+ <span class="ax-ad-source-name">${i}</span>
111
+ <span class="ax-ad-sponsored-badge" style="font-size:9px">Ad</span>
112
+ </div>
113
+ <svg width="12" height="12" fill="none" stroke="#d1d5db" viewBox="0 0 24 24" style="flex-shrink:0">
114
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
115
+ </svg>
116
+ </div>
117
+ `:t==="minimal"?`
118
+ <div data-ad-id="${e.original.id}" style="display:inline-flex">
119
+ <a class="ax-ad-source-link ax-ad-variant-minimal">
120
+ ${s?`<img src="${s}" alt="${i}" loading="lazy" style="width:16px;height:16px;border-radius:2px;object-fit:cover" />`:""}
121
+ <span>${i}</span>
122
+ <span class="ax-ad-sponsored-badge" style="font-size:9px">Ad</span>
123
+ </a>
124
+ </div>
125
+ `:`
126
+ <div class="ax-ad-source ax-ad-variant-card" data-ad-id="${e.original.id}">
127
+ ${s?`<img src="${s}" alt="${i}" loading="lazy" style="width:48px;height:48px;border-radius:6px;object-fit:cover;flex-shrink:0" />`:""}
128
+ <div class="ax-ad-source-info">
129
+ <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
130
+ <span class="ax-ad-source-name">${i}</span>
131
+ <span class="ax-ad-sponsored-badge">Sponsored</span>
132
+ </div>
133
+ ${n?`<p class="ax-ad-source-desc">${n}</p>`:""}
134
+ </div>
135
+ <svg width="16" height="16" fill="none" stroke="#d1d5db" viewBox="0 0 24 24" style="flex-shrink:0">
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
+ </svg>
138
+ </div>
139
+ `}}class w{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 w),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 k=()=>w.getInstance();function f(a){return k().collect(a)}function R(){return f().device}function $(){return f().user}function N(){return f().app}function H(){return f().geo}function G(){return $().id}function j(){k().clearCache()}function D(a){const e=f(a);return JSON.stringify(e,null,2)}function W(){var t;const a=f();return[a.device.os,a.device.osv,a.app.name,a.user.id.slice(0,8)+"...",((t=a.geo)==null?void 0:t.country)||"Unknown"].join(" / ")}function Q(a){var e,t;return{id:a.original.id,type:a.original.type,score:a.original.score,source:"internal",content:{title:a.adapted.title,body:a.adapted.body,image:(e=a.adapted.image)==null?void 0:e.url,cta_text:a.adapted.ctaText,price:(t=a.adapted.price)==null?void 0:t.display,rating:a.adapted.rating,brand:a.adapted.brand},tracking:{click_url:a.tracking.clickUrl,impression_url:a.tracking.impressionUrl},metadata:{viewToken:a.tracking.viewToken,styling:a.adapted.styling}}}async function I(a,e={}){const t=e.apiBaseUrl||"/api/v1",n=k().collect(),s={...a,clientInfo:n},r={"Content-Type":"application/json"};e.apiKey&&(r["X-API-Key"]=e.apiKey);const o=await fetch(`${t}/ads/request`,{method:"POST",headers:r,body:JSON.stringify(s)});if(!o.ok)throw new Error(`Ad request failed: ${o.status} ${o.statusText}`);const l=await o.json();if(!l.success)throw new Error(l.error||"Ad request failed");return l.data}class z{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 p,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,slotCount:e.slots.length,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...");try{const t=await I({conversationContext:e.conversationContext,userContext:e.userContext,slots:this.config.slots},{apiBaseUrl:this.config.apiBaseUrl,apiKey:this.config.apiKey});this.currentRequestId=t.requestId,this.adsAnalyticsMap.clear(),t.slots.forEach(n=>{n.ads.forEach((s,r)=>{const o=s.original.id;this.adsAnalyticsMap.set(o,{requestId:t.requestId,slotId:n.slotId,position:r,totalAds:n.ads.length})})});const i={};return t.slots.forEach(n=>{i[n.slotId]=n}),this.slots=i,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)}),t}catch(t){const i=t;throw this.eventBus.emit("adsError",i),i}finally{this.isLoading=!1}}render(e,t,i={}){const n=this.slots[e];if(!n||!n.ads||n.ads.length===0)return this.config.debug&&console.warn("[AdManager] No ads in slot:",e),null;const s=i.adIndex??0,r=n.ads[s];if(!r)return this.config.debug&&console.warn(`[AdManager] Ad at index ${s} not found in slot:`,e),null;const o=this.adsAnalyticsMap.get(r.original.id),l=this.config.slots.find(h=>h.slotId===e),d=(l==null?void 0:l.format)||"action_card";t.innerHTML="";const u={analytics:o,variant:i.variant,onClick:i.onClick,onImpression:i.onImpression};return d==="suffix"?y.renderSuffixAd(r,t,u):d==="source"?y.renderSponsoredSource(r,t,u):d==="lead_gen"?y.renderLeadGenAd(r,t,u):y.renderActionCard(r,t,u)}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},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 v({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 b({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")}}function M(){if(typeof window<"u"){const a=window.__AD_CONFIG__;if(a!=null&&a.apiKey)return a.apiKey}}class C{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 o=new IntersectionObserver(d=>this.handleIntersection(e,d[0],i,n,s),{threshold:this.createThresholds()});o.observe(t),this.observers.set(e,o),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 o=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!==o&&r.isViewable&&this.log(`[ViewabilityTracker] ${e} became VIEWABLE!`),this.metrics.set(e,r)}startMonitoring(e,t,i,n){const r=setInterval(()=>{const o=this.metrics.get(e);if(!o||!this.isTracking.get(e)){clearInterval(r);return}const l=Date.now(),d=o.isViewable;if(o.enteredViewportAt){const u=l-o.enteredViewportAt;o.currentVisibleTimeMs=u,u>=this.config.minViewableDuration&&o.visiblePercentage>=this.config.minVisiblePercentage&&(o.isViewable=!0,d||(o.viewableAt=o.enteredViewportAt,this.log(`[ViewabilityTracker] ${e} BECAME VIEWABLE at ${o.viewableAt}`),this.queueEvent(e,{adId:e,sessionId:t,requestId:i,viewToken:n,eventType:"become_viewable",visiblePercentage:o.visiblePercentage,maxVisiblePercentage:o.maxVisiblePercentage,totalVisibleTimeMs:o.totalVisibleTimeMs,isViewable:!0,timestamp:l})))}o.enteredViewportAt&&(o.totalVisibleTimeMs+=100),this.metrics.set(e,o)},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 o=this.batchTimers.get(e);o&&clearTimeout(o);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=M();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=M();n&&(i["x-api-key"]=n),fetch(`${this.baseUrl}/ads/viewability/batch`,{method:"POST",headers:i,keepalive:!0,body:JSON.stringify({events:t})})}}})}}let A=null;function F(a,e){return A||(A=new C(a,e),A.setupBeforeUnload()),A}const J="0.1.0";c.AdManager=z,c.AnalyticsSender=S,c.ClientInfoCollector=w,c.DOMRenderer=y,c.HTMLRenderer=g,c.SDK_VERSION=J,c.SessionManager=x,c.ViewabilityTracker=C,c.adaptAdToKoahAd=Q,c.clearClientInfoCache=j,c.createAnalytics=q,c.createSession=K,c.fetchAds=I,c.generateViewToken=O,c.getAppInfo=N,c.getClientInfo=f,c.getClientInfoCollector=k,c.getClientInfoJSON=D,c.getClientInfoSummary=W,c.getDeviceInfo=R,c.getGeoInfo=H,c.getSessionId=m,c.getUserId=G,c.getUserInfo=$,c.getViewabilityTracker=F,c.trackAdClick=v,c.trackAdImpression=b,c.trackClicksBatch=L,c.trackImpressionsBatch=B,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})});
package/dist/style.css ADDED
@@ -0,0 +1 @@
1
+ .ax-ad-card{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;box-shadow:0 1px 3px #0000001a}.ax-ad-card a{text-decoration:none;color:inherit}.ax-ad-card-horizontal{display:flex;flex-direction:row}.ax-ad-card-vertical{display:flex;flex-direction:column}.ax-ad-card-compact{display:flex;flex-direction:row;padding:8px}.ax-ad-image{width:100%;height:auto;object-fit:cover}.ax-ad-card-horizontal .ax-ad-image{width:120px;height:120px}.ax-ad-content{padding:12px 16px;flex:1}.ax-ad-title{font-size:16px;font-weight:600;margin:0 0 8px;color:#111827}.ax-ad-body{font-size:14px;color:#6b7280;margin:0 0 12px;line-height:1.5}.ax-ad-price{font-weight:600;color:#059669;margin-right:8px}.ax-ad-brand{font-size:12px;color:#9ca3af;text-transform:uppercase}.ax-ad-rating{color:#f59e0b;font-size:14px;margin:4px 0}.ax-ad-footer{display:flex;align-items:center;justify-content:space-between;margin-top:12px}.ax-ad-cta{padding:8px 16px;background:#3b82f6;color:#fff;border-radius:6px;font-weight:500;transition:background .2s;text-decoration:none;cursor:pointer}.ax-ad-cta:hover{background:#2563eb}.ax-ad-suffix{border-top:1px solid #e5e7eb;padding:16px 0}.ax-ad-suffix-content{display:flex;justify-content:space-between;align-items:center}.ax-ad-suffix-title{margin:0;font-size:14px;font-weight:500;color:#374151}.ax-ad-suffix-body{font-size:12px;color:#6b7280;margin:4px 0 0}.ax-ad-suffix-link{color:#3b82f6;font-weight:500;text-decoration:none;cursor:pointer}.ax-ad-suffix-link:hover{text-decoration:underline}.ax-ad-variant-block{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border:1px solid #e5e7eb;border-radius:8px;background:#f9fafb}.ax-ad-variant-block .ax-ad-suffix-text{font-size:14px;color:#374151;flex:1}.ax-ad-variant-block .ax-ad-suffix-badge{font-size:10px;color:#9ca3af;text-transform:uppercase;flex-shrink:0}.ax-ad-variant-inline{display:inline-flex;align-items:center;gap:4px;color:#3b82f6;font-weight:500;cursor:pointer}.ax-ad-variant-minimal{display:inline;color:#3b82f6;font-weight:500;cursor:pointer}.ax-ad-source-link{display:flex;align-items:center;gap:8px;padding:8px 0;text-decoration:none;cursor:pointer}.ax-ad-source-icon{width:24px;height:24px;border-radius:4px;object-fit:cover}.ax-ad-source-info{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.ax-ad-source-name{font-weight:500;color:#111827;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ax-ad-source-desc{font-size:12px;color:#6b7280;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ax-ad-source-badge{font-size:10px;color:#9ca3af;text-transform:uppercase;flex-shrink:0}.ax-ad-variant-card{display:flex;align-items:center;gap:12px;padding:12px;border:1px solid #e5e7eb;border-radius:8px;background:#fff;cursor:pointer}.ax-ad-variant-card:hover{background:#f9fafb}.ax-ad-variant-list-item{display:flex;align-items:center;gap:8px;padding:8px 0;border-bottom:1px solid #f3f4f6;cursor:pointer}.ax-ad-variant-list-item img{width:32px;height:32px;border-radius:4px;object-fit:cover;flex-shrink:0}.ax-ad-variant-minimal{display:inline-flex;align-items:center;gap:4px;cursor:pointer}.ax-ad-leadgen{border:1px solid #e5e7eb;border-radius:8px;overflow:hidden;background:#fff}.ax-ad-leadgen-content{padding:16px}.ax-ad-leadgen-title{font-size:18px;font-weight:600;margin:0 0 8px;color:#111827}.ax-ad-leadgen-body{font-size:14px;color:#6b7280;margin:0 0 16px;line-height:1.5}.ax-ad-leadgen-cta{display:inline-block;padding:10px 20px;background:#3b82f6;color:#fff;border-radius:6px;font-weight:500;text-decoration:none;transition:background .2s;cursor:pointer}.ax-ad-leadgen-cta:hover{background:#2563eb}.ax-ads-container{display:flex;flex-direction:column;gap:12px}.ax-ad-wrapper{width:100%}.ax-ad-sponsored-badge{display:inline-flex;align-items:center;padding:2px 6px;background:#f3f4f6;border-radius:4px;font-size:10px;color:#9ca3af;text-transform:uppercase;letter-spacing:.05em;flex-shrink:0}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@action-x/ad-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Zero-dependency ad SDK for ActionX",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
14
+ },
15
+ "./style.css": "./dist/style.css"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc --noEmit && vite build",
22
+ "dev": "vite build --watch",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.3.0",
27
+ "vite": "^5.0.0",
28
+ "vite-plugin-dts": "^3.7.0"
29
+ }
30
+ }