@adxensor/publisher-sdk 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cdn/tag.js CHANGED
@@ -1 +1 @@
1
- "use strict";(()=>{var M=Object.defineProperty,R=Object.defineProperties;var H=Object.getOwnPropertyDescriptors;var b=Object.getOwnPropertySymbols;var D=Object.prototype.hasOwnProperty,z=Object.prototype.propertyIsEnumerable;var k=(s,e,t)=>e in s?M(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,c=(s,e)=>{for(var t in e||(e={}))D.call(e,t)&&k(s,t,e[t]);if(b)for(var t of b(e))z.call(e,t)&&k(s,t,e[t]);return s},p=(s,e)=>R(s,H(e));var f=class{constructor(e,t){this.baseUrl=e;this.apiKey=t}headers(){let e={"Content-Type":"application/json"};return this.apiKey&&(e["X-Publisher-Key"]=this.apiKey),e}async serveAd(e){var t,i;try{let r=new URLSearchParams({zone:e.size,site:e.siteId});e.url&&r.set("url",e.url),e.referrer&&r.set("ref",e.referrer);let n=await fetch("".concat(this.baseUrl,"/ad?").concat(r.toString()),{method:"GET",headers:this.headers()});if(n.status===204||!n.ok)return null;let o=await n.json(),a=(t=o.impression_url.split("/").pop())!=null?t:"",[d,y]=((i=o.size)!=null?i:"300x250").split("x").map(Number);return{adId:a,campaignId:o.campaign_id,type:"image",imageUrl:o.creative_url,clickHref:o.click_url,width:d!=null?d:300,height:y!=null?y:250,altText:"",impressionToken:a,clickToken:a}}catch(r){return null}}sendEvent(e,t){let i="".concat(this.baseUrl,"/events/").concat(e),r=JSON.stringify(t);typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(i,new Blob([r],{type:"application/json"})):fetch(i,{method:"POST",headers:this.headers(),body:r,keepalive:!0}).catch(()=>{})}};var x="_adx_sid",g="_adx_fired";function A(){try{let s=sessionStorage.getItem(x);return s||(s=typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():Math.random().toString(36).slice(2)+Date.now().toString(36),sessionStorage.setItem(x,s)),s}catch(s){return Math.random().toString(36).slice(2)}}function w(s){try{let e=JSON.parse(sessionStorage.getItem(g)||"{}");return s in e}catch(e){return!1}}function S(s){try{let e=JSON.parse(sessionStorage.getItem(g)||"{}");e[s]=Date.now(),sessionStorage.setItem(g,JSON.stringify(e))}catch(e){}}function h(){if(typeof window=="undefined")return"desktop";let s=window.innerWidth;return s<768?"mobile":s<1024?"tablet":"desktop"}function T(s,e){if(s!=="auto")return s;let t=h(),i=e.offsetWidth||window.innerWidth;return t==="mobile"?i>=320?"320x50":"300x250":t==="tablet"?i>=468?"468x60":"300x250":i>=728?"728x90":i>=468?"468x60":"300x250"}function C(){var s;return((s=navigator.language)!=null?s:"fr").split("-")[0].toLowerCase()}var m=class{constructor(e,t,i){this.api=e;this.siteId=t;this.sessionId=i}base(){return{siteId:this.siteId,url:location.href,device:h(),sessionId:this.sessionId,language:C(),screenWidth:screen.width,ts:Date.now()}}pageview(){this.api.sendEvent("pageview",p(c({},this.base()),{referrer:document.referrer}))}impression(e,t,i){let r="imp:".concat(e,":").concat(this.sessionId);w(r)||(S(r),this.api.sendEvent("impression",p(c({},this.base()),{adId:e,slotId:t,impressionToken:i})))}click(e,t,i){let r="clk_ts:".concat(e);try{let n=parseInt(sessionStorage.getItem(r)||"0",10);if(Date.now()-n<500)return;sessionStorage.setItem(r,String(Date.now()))}catch(n){}this.api.sendEvent("click",p(c({},this.base()),{adId:e,slotId:t,clickToken:i}))}view(e,t){let i="view:".concat(e,":").concat(this.sessionId);w(i)||(S(i),this.api.sendEvent("view",p(c({},this.base()),{adId:e,slotId:t})))}};function E(s,e,t){var n;if(s.style.overflow="hidden",s.style.display="block",s.style.lineHeight="0",e.type==="html"&&e.htmlContent){s.innerHTML=e.htmlContent;return}let i=document.createElement("a");i.href=t,i.target="_blank",i.rel="noopener noreferrer",i.setAttribute("data-adx-click","1"),i.style.display="block";let r=document.createElement("img");r.src=(n=e.imageUrl)!=null?n:"",r.width=e.width,r.height=e.height,r.alt=e.altText||"",r.style.display="block",r.style.maxWidth="100%",r.loading="lazy",i.appendChild(r),s.innerHTML="",s.appendChild(i)}var O=.5,U=1e3,v=class{constructor(e,t,i,r,n,o={}){this.el=e;this.api=t;this.tracker=i;this.siteId=r;this.sessionId=n;this.options=o;this.observer=null;this.viewTimer=null;var a,d;this.slotId=(d=(a=o.slotId)!=null?a:e.dataset.adSlot)!=null?d:e.id||"adx-".concat(Math.random().toString(36).slice(2,9))}load(e=!0){this.el.dataset.adxState||(e&&typeof IntersectionObserver!="undefined"?this.watchForEntry():this.fetch())}watchForEntry(){let e=new IntersectionObserver(t=>{var i;(i=t[0])!=null&&i.isIntersecting&&(e.disconnect(),this.fetch())},{rootMargin:"200px",threshold:0});e.observe(this.el)}async fetch(){var e,t,i;if(!this.el.dataset.adxState){this.setState("loading");try{let r=T((t=(e=this.options.format)!=null?e:this.el.dataset.adFormat)!=null?t:"auto",this.el),[n,o]=r.split("x").map(Number);n&&o&&(this.el.style.width="".concat(n,"px"),this.el.style.maxWidth="100%",this.el.style.minHeight="".concat(o,"px"));let a=await this.api.serveAd({siteId:this.siteId,slotId:this.slotId,size:r,device:h(),url:location.href,referrer:document.referrer,language:((i=navigator.language)!=null?i:"fr").split("-")[0].toLowerCase(),screenWidth:screen.width,sessionId:this.sessionId});if(!a){this.setState("empty"),this.el.style.display="none";return}E(this.el,a,a.clickHref),this.setState("filled"),this.el.addEventListener("click",d=>{d.target.closest("[data-adx-click]")&&this.tracker.click(a.adId,this.slotId,a.clickToken)},{passive:!0}),this.tracker.impression(a.adId,this.slotId,a.impressionToken),this.watchVisibility(a.adId,this.slotId)}catch(r){this.setState("error"),this.el.style.display="none"}}}watchVisibility(e,t){if(typeof IntersectionObserver=="undefined"){this.tracker.view(e,t);return}this.observer=new IntersectionObserver(([i])=>{i&&(i.intersectionRatio>=O?this.viewTimer===null&&(this.viewTimer=setTimeout(()=>{this.tracker.view(e,t),this.disconnect()},U)):this.viewTimer!==null&&(clearTimeout(this.viewTimer),this.viewTimer=null))},{threshold:[O]}),this.observer.observe(this.el)}setState(e){this.el.dataset.adxState=e}disconnect(){var e;(e=this.observer)==null||e.disconnect(),this.observer=null,this.viewTimer!==null&&(clearTimeout(this.viewTimer),this.viewTimer=null)}};var X="https://core.adxensor.com/v1",I="ins.adxensor:not([data-adx-state])",l=class l{constructor(e){this.initialized=!1;this.domObserver=null;var t;this.config=e,this.sessionId=A(),this.api=new f((t=e.apiUrl)!=null?t:X,e.apiKey),this.tracker=new m(this.api,e.siteId,this.sessionId)}static getInstance(e){return l.instance||(l.instance=new l(e)),l.instance}static reset(){var e;(e=l.instance)==null||e.destroy(),l.instance=null}init(){return this.initialized?this:(this.initialized=!0,this.tracker.pageview(),this.fillAll(),this.watchDom(),this)}fillAll(){document.querySelectorAll(I).forEach(e=>this.fill(e))}push(e={}){let t=document.querySelector(I);t&&this.fill(t,e)}fill(e,t={}){var n,o;if(e.dataset.adxState)return;e.style.display==="none"&&(e.style.display="block");let i=new v(e,this.api,this.tracker,this.config.siteId,this.sessionId,t),r=(o=(n=t.lazy)!=null?n:this.config.lazyLoad)!=null?o:!0;i.load(r)}defineSlot(e,t={}){let i=document.querySelector(e);if(!i){this.config.debug&&console.warn('[AdXensor] defineSlot: "'.concat(e,'" not found'));return}this.fill(i,t)}watchDom(){typeof MutationObserver!="undefined"&&(this.domObserver=new MutationObserver(e=>{for(let{addedNodes:t}of e)t.forEach(i=>{i instanceof HTMLElement&&(i.matches("ins.adxensor")&&!i.dataset.adxState&&this.fill(i),i.querySelectorAll(I).forEach(r=>this.fill(r)))})}),this.domObserver.observe(document.body,{childList:!0,subtree:!0}))}destroy(){var e;(e=this.domObserver)==null||e.disconnect(),this.domObserver=null,this.initialized=!1}};l.instance=null;var u=l;window.AdXensor=u;function L(){var r,n;let s=(r=document.currentScript)!=null?r:document.querySelector("script[data-ad-client]");if(!s){console.error("[AdXensor] Cannot find script tag with data-ad-client");return}let e=s.dataset.adClient;if(!e){console.error("[AdXensor] data-ad-client is required");return}let t=u.getInstance({siteId:e,apiKey:s.dataset.apiKey,apiUrl:s.dataset.apiUrl,debug:s.dataset.debug!==void 0,lazyLoad:s.dataset.lazyLoad!=="false"}),i=Array.isArray(window.adxensor)?window.adxensor:[];for(let o of i)if(Array.isArray(o)){let[a,...d]=o;a==="push"&&t.push((n=d[0])!=null?n:{})}else t.push(o);window.adxensor={push(o={}){t.push(o)}},t.init(),s.dataset.debug!==void 0&&(window._adx=t)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",L):L();})();
1
+ "use strict";(()=>{var M=Object.defineProperty,R=Object.defineProperties;var H=Object.getOwnPropertyDescriptors;var b=Object.getOwnPropertySymbols;var D=Object.prototype.hasOwnProperty,z=Object.prototype.propertyIsEnumerable;var k=(s,e,t)=>e in s?M(s,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):s[e]=t,c=(s,e)=>{for(var t in e||(e={}))D.call(e,t)&&k(s,t,e[t]);if(b)for(var t of b(e))z.call(e,t)&&k(s,t,e[t]);return s},p=(s,e)=>R(s,H(e));var f=class{constructor(e,t){this.baseUrl=e;this.apiKey=t}headers(){let e={"Content-Type":"application/json"};return this.apiKey&&(e["X-Publisher-Key"]=this.apiKey),e}async serveAd(e){var t,i;try{let r=new URLSearchParams({zone:e.size,site:e.siteId});e.url&&r.set("url",e.url),e.referrer&&r.set("ref",e.referrer);let n=await fetch("".concat(this.baseUrl,"/ad?").concat(r.toString()),{method:"GET",headers:this.headers()});if(n.status===204||!n.ok)return null;let o=await n.json(),a=(t=o.impression_url.split("/").pop())!=null?t:"",[d,y]=((i=o.size)!=null?i:"300x250").split("x").map(Number);return{adId:a,campaignId:o.campaign_id,type:"image",imageUrl:o.creative_url,clickHref:o.click_url,width:d!=null?d:300,height:y!=null?y:250,altText:"",impressionToken:a,clickToken:a}}catch(r){return null}}ping(e){fetch("".concat(this.baseUrl,"/ping"),{method:"POST",headers:this.headers(),body:JSON.stringify({siteId:e}),keepalive:!0}).catch(()=>{})}sendEvent(e,t){let i="".concat(this.baseUrl,"/events/").concat(e),r=JSON.stringify(t);typeof navigator!="undefined"&&navigator.sendBeacon?navigator.sendBeacon(i,new Blob([r],{type:"application/json"})):fetch(i,{method:"POST",headers:this.headers(),body:r,keepalive:!0}).catch(()=>{})}};var T="_adx_sid",g="_adx_fired";function x(){try{let s=sessionStorage.getItem(T);return s||(s=typeof crypto!="undefined"&&crypto.randomUUID?crypto.randomUUID():Math.random().toString(36).slice(2)+Date.now().toString(36),sessionStorage.setItem(T,s)),s}catch(s){return Math.random().toString(36).slice(2)}}function w(s){try{let e=JSON.parse(sessionStorage.getItem(g)||"{}");return s in e}catch(e){return!1}}function S(s){try{let e=JSON.parse(sessionStorage.getItem(g)||"{}");e[s]=Date.now(),sessionStorage.setItem(g,JSON.stringify(e))}catch(e){}}function h(){if(typeof window=="undefined")return"desktop";let s=window.innerWidth;return s<768?"mobile":s<1024?"tablet":"desktop"}function A(s,e){if(s!=="auto")return s;let t=h(),i=e.offsetWidth||window.innerWidth;return t==="mobile"?i>=320?"320x50":"300x250":t==="tablet"?i>=468?"468x60":"300x250":i>=728?"728x90":i>=468?"468x60":"300x250"}function C(){var s;return((s=navigator.language)!=null?s:"fr").split("-")[0].toLowerCase()}var m=class{constructor(e,t,i){this.api=e;this.siteId=t;this.sessionId=i}base(){return{siteId:this.siteId,url:location.href,device:h(),sessionId:this.sessionId,language:C(),screenWidth:screen.width,ts:Date.now()}}pageview(){this.api.sendEvent("pageview",p(c({},this.base()),{referrer:document.referrer}))}impression(e,t,i){let r="imp:".concat(e,":").concat(this.sessionId);w(r)||(S(r),this.api.sendEvent("impression",p(c({},this.base()),{adId:e,slotId:t,impressionToken:i})))}click(e,t,i){let r="clk_ts:".concat(e);try{let n=parseInt(sessionStorage.getItem(r)||"0",10);if(Date.now()-n<500)return;sessionStorage.setItem(r,String(Date.now()))}catch(n){}this.api.sendEvent("click",p(c({},this.base()),{adId:e,slotId:t,clickToken:i}))}view(e,t){let i="view:".concat(e,":").concat(this.sessionId);w(i)||(S(i),this.api.sendEvent("view",p(c({},this.base()),{adId:e,slotId:t})))}};function E(s,e,t){var n;if(s.style.overflow="hidden",s.style.display="block",s.style.lineHeight="0",e.type==="html"&&e.htmlContent){s.innerHTML=e.htmlContent;return}let i=document.createElement("a");i.href=t,i.target="_blank",i.rel="noopener noreferrer",i.setAttribute("data-adx-click","1"),i.style.display="block";let r=document.createElement("img");r.src=(n=e.imageUrl)!=null?n:"",r.width=e.width,r.height=e.height,r.alt=e.altText||"",r.style.display="block",r.style.maxWidth="100%",r.loading="lazy",i.appendChild(r),s.innerHTML="",s.appendChild(i)}var O=.5,U=1e3,v=class{constructor(e,t,i,r,n,o={}){this.el=e;this.api=t;this.tracker=i;this.siteId=r;this.sessionId=n;this.options=o;this.observer=null;this.viewTimer=null;var a,d;this.slotId=(d=(a=o.slotId)!=null?a:e.dataset.adSlot)!=null?d:e.id||"adx-".concat(Math.random().toString(36).slice(2,9))}load(e=!0){this.el.dataset.adxState||(e&&typeof IntersectionObserver!="undefined"?this.watchForEntry():this.fetch())}watchForEntry(){let e=new IntersectionObserver(t=>{var i;(i=t[0])!=null&&i.isIntersecting&&(e.disconnect(),this.fetch())},{rootMargin:"200px",threshold:0});e.observe(this.el)}async fetch(){var e,t,i;if(!this.el.dataset.adxState){this.setState("loading");try{let r=A((t=(e=this.options.format)!=null?e:this.el.dataset.adFormat)!=null?t:"auto",this.el),[n,o]=r.split("x").map(Number);n&&o&&(this.el.style.width="".concat(n,"px"),this.el.style.maxWidth="100%",this.el.style.minHeight="".concat(o,"px"));let a=await this.api.serveAd({siteId:this.siteId,slotId:this.slotId,size:r,device:h(),url:location.href,referrer:document.referrer,language:((i=navigator.language)!=null?i:"fr").split("-")[0].toLowerCase(),screenWidth:screen.width,sessionId:this.sessionId});if(!a){this.setState("empty"),this.el.style.display="none";return}E(this.el,a,a.clickHref),this.setState("filled"),this.el.addEventListener("click",d=>{d.target.closest("[data-adx-click]")&&this.tracker.click(a.adId,this.slotId,a.clickToken)},{passive:!0}),this.tracker.impression(a.adId,this.slotId,a.impressionToken),this.watchVisibility(a.adId,this.slotId)}catch(r){this.setState("error"),this.el.style.display="none"}}}watchVisibility(e,t){if(typeof IntersectionObserver=="undefined"){this.tracker.view(e,t);return}this.observer=new IntersectionObserver(([i])=>{i&&(i.intersectionRatio>=O?this.viewTimer===null&&(this.viewTimer=setTimeout(()=>{this.tracker.view(e,t),this.disconnect()},U)):this.viewTimer!==null&&(clearTimeout(this.viewTimer),this.viewTimer=null))},{threshold:[O]}),this.observer.observe(this.el)}setState(e){this.el.dataset.adxState=e}disconnect(){var e;(e=this.observer)==null||e.disconnect(),this.observer=null,this.viewTimer!==null&&(clearTimeout(this.viewTimer),this.viewTimer=null)}};var X="https://core.adxensor.com/v1",I="ins.adxensor:not([data-adx-state])",l=class l{constructor(e){this.initialized=!1;this.domObserver=null;var t;this.config=e,this.sessionId=x(),this.api=new f((t=e.apiUrl)!=null?t:X,e.apiKey),this.tracker=new m(this.api,e.siteId,this.sessionId)}static getInstance(e){return l.instance||(l.instance=new l(e)),l.instance}static reset(){var e;(e=l.instance)==null||e.destroy(),l.instance=null}init(){return this.initialized?this:(this.initialized=!0,this.tracker.pageview(),this.api.ping(this.config.siteId),this.fillAll(),this.watchDom(),this)}fillAll(){document.querySelectorAll(I).forEach(e=>this.fill(e))}push(e={}){let t=document.querySelector(I);t&&this.fill(t,e)}fill(e,t={}){var n,o;if(e.dataset.adxState)return;e.style.display==="none"&&(e.style.display="block");let i=new v(e,this.api,this.tracker,this.config.siteId,this.sessionId,t),r=(o=(n=t.lazy)!=null?n:this.config.lazyLoad)!=null?o:!0;i.load(r)}defineSlot(e,t={}){let i=document.querySelector(e);if(!i){this.config.debug&&console.warn('[AdXensor] defineSlot: "'.concat(e,'" not found'));return}this.fill(i,t)}watchDom(){typeof MutationObserver!="undefined"&&(this.domObserver=new MutationObserver(e=>{for(let{addedNodes:t}of e)t.forEach(i=>{i instanceof HTMLElement&&(i.matches("ins.adxensor")&&!i.dataset.adxState&&this.fill(i),i.querySelectorAll(I).forEach(r=>this.fill(r)))})}),this.domObserver.observe(document.body,{childList:!0,subtree:!0}))}destroy(){var e;(e=this.domObserver)==null||e.disconnect(),this.domObserver=null,this.initialized=!1}};l.instance=null;var u=l;window.AdXensor=u;function L(){var r,n;let s=(r=document.currentScript)!=null?r:document.querySelector("script[data-ad-client]");if(!s){console.error("[AdXensor] Cannot find script tag with data-ad-client");return}let e=s.dataset.adClient;if(!e){console.error("[AdXensor] data-ad-client is required");return}let t=u.getInstance({siteId:e,apiKey:s.dataset.apiKey,apiUrl:s.dataset.apiUrl,debug:s.dataset.debug!==void 0,lazyLoad:s.dataset.lazyLoad!=="false"}),i=Array.isArray(window.adxensor)?window.adxensor:[];for(let o of i)if(Array.isArray(o)){let[a,...d]=o;a==="push"&&t.push((n=d[0])!=null?n:{})}else t.push(o);window.adxensor={push(o={}){t.push(o)}},t.init(),s.dataset.debug!==void 0&&(window._adx=t)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",L):L();})();
@@ -93,6 +93,16 @@ var AdXensorApi = class {
93
93
  return null;
94
94
  }
95
95
  }
96
+ // ─── Integration ping — called once on SDK init ───────────────────────────
97
+ ping(siteId) {
98
+ fetch(`${this.baseUrl}/ping`, {
99
+ method: "POST",
100
+ headers: this.headers(),
101
+ body: JSON.stringify({ siteId }),
102
+ keepalive: true
103
+ }).catch(() => {
104
+ });
105
+ }
96
106
  // ─── Fire-and-forget event (uses sendBeacon when available) ───────────────
97
107
  sendEvent(path, payload) {
98
108
  const url = `${this.baseUrl}/events/${path}`;
@@ -431,6 +441,7 @@ var _AdXensor = class _AdXensor {
431
441
  if (this.initialized) return this;
432
442
  this.initialized = true;
433
443
  this.tracker.pageview();
444
+ this.api.ping(this.config.siteId);
434
445
  this.fillAll();
435
446
  this.watchDom();
436
447
  return this;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/index.ts", "../../../src/core/api.ts", "../../../src/core/session.ts", "../../../src/core/device.ts", "../../../src/core/tracker.ts", "../../../src/core/renderer.ts", "../../../src/core/AdSlot.ts", "../../../src/core/AdXensor.ts"],
4
- "sourcesContent": ["// \u2500\u2500\u2500 Main class (primary entrypoint) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { AdXensor } from './core/AdXensor.js';\n\n// \u2500\u2500\u2500 Lower-level building blocks (advanced / custom integrations) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { AdXensorApi } from './core/api.js';\nexport { Tracker } from './core/tracker.js';\nexport { AdSlot } from './core/AdSlot.js';\nexport { renderAd } from './core/renderer.js';\nexport { getDevice, resolveSize } from './core/device.js';\nexport { getSessionId, hasFired, markFired } from './core/session.js';\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport type {\n AdXensorConfig,\n SlotOptions,\n SlotState,\n AdFormat,\n AdSize, // deprecated alias for AdFormat \u2014 kept for backwards compat\n DeviceType,\n AdResponse,\n ServeRequest,\n AdEventPayload,\n PageviewPayload,\n} from './core/types.js';\n", "import type { ServeRequest, AdResponse, AdEventPayload, PageviewPayload } from './types.js';\n\n/** ads_core /v1/ad response shape */\ninterface AdsCoreAdResponse {\n creative_url: string;\n click_url: string; // full URL e.g. https://ads.adxensor.com/v1/click/TOKEN\n impression_url: string; // full URL e.g. https://ads.adxensor.com/v1/impression/TOKEN\n campaign_id: string;\n size: string; // \"300x250\"\n}\n\nexport class AdXensorApi {\n constructor(\n private readonly baseUrl: string, // e.g. \"https://ads.adxensor.com/v1\"\n private readonly apiKey?: string,\n ) {}\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) h['X-Publisher-Key'] = this.apiKey;\n return h;\n }\n\n // \u2500\u2500\u2500 Fetch the best ad for a slot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async serveAd(req: ServeRequest): Promise<AdResponse | null> {\n try {\n // Map SDK params \u2192 ads_core query schema\n const params = new URLSearchParams({ zone: req.size, site: req.siteId });\n if (req.url) params.set('url', req.url);\n if (req.referrer) params.set('ref', req.referrer);\n\n const res = await fetch(`${this.baseUrl}/ad?${params.toString()}`, {\n method: 'GET',\n headers: this.headers(),\n });\n\n // 204 = no ad available for this slot\n if (res.status === 204 || !res.ok) return null;\n\n const data = await res.json() as AdsCoreAdResponse;\n\n // Extract shared event token from either tracking URL\n const eventId = data.impression_url.split('/').pop() ?? '';\n\n const [w, h] = (data.size ?? '300x250').split('x').map(Number);\n\n return {\n adId: eventId,\n campaignId: data.campaign_id,\n type: 'image',\n imageUrl: data.creative_url,\n clickHref: data.click_url, // full URL \u2014 use directly as <a> href\n width: w ?? 300,\n height: h ?? 250,\n altText: '',\n impressionToken: eventId,\n clickToken: eventId,\n };\n } catch {\n return null;\n }\n }\n\n // \u2500\u2500\u2500 Fire-and-forget event (uses sendBeacon when available) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n sendEvent(path: string, payload: AdEventPayload | PageviewPayload): void {\n const url = `${this.baseUrl}/events/${path}`;\n const body = JSON.stringify(payload);\n\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));\n } else {\n fetch(url, {\n method: 'POST',\n headers: this.headers(),\n body,\n keepalive: true,\n }).catch(() => { /* silent */ });\n }\n }\n}\n", "const SESSION_KEY = '_adx_sid';\nconst FIRED_KEY = '_adx_fired';\n\n/** Returns (or creates) a session ID stored in sessionStorage. */\nexport function getSessionId(): string {\n try {\n let id = sessionStorage.getItem(SESSION_KEY);\n if (!id) {\n id = typeof crypto !== 'undefined' && crypto.randomUUID\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2) + Date.now().toString(36);\n sessionStorage.setItem(SESSION_KEY, id);\n }\n return id;\n } catch {\n // sessionStorage blocked (iframe sandbox, private mode, etc.)\n return Math.random().toString(36).slice(2);\n }\n}\n\n/** Returns true if this event key was already fired this session. */\nexport function hasFired(key: string): boolean {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n return key in map;\n } catch {\n return false;\n }\n}\n\n/** Mark an event key as fired. */\nexport function markFired(key: string): void {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n map[key] = Date.now();\n sessionStorage.setItem(FIRED_KEY, JSON.stringify(map));\n } catch { /* ignore */ }\n}\n", "import type { DeviceType } from './types.js';\n\nexport function getDevice(): DeviceType {\n if (typeof window === 'undefined') return 'desktop';\n const w = window.innerWidth;\n if (w < 768) return 'mobile';\n if (w < 1024) return 'tablet';\n return 'desktop';\n}\n\n/**\n * Resolve 'auto' to a concrete size based on the container width + device.\n * Falls back to a safe default for each device tier.\n */\nexport function resolveSize(requested: string, container: HTMLElement): string {\n if (requested !== 'auto') return requested;\n\n const device = getDevice();\n const w = container.offsetWidth || window.innerWidth;\n\n if (device === 'mobile') {\n return w >= 320 ? '320x50' : '300x250';\n }\n if (device === 'tablet') {\n return w >= 468 ? '468x60' : '300x250';\n }\n // desktop\n if (w >= 728) return '728x90';\n if (w >= 468) return '468x60';\n return '300x250';\n}\n", "import { hasFired, markFired } from './session.js';\nimport { getDevice } from './device.js';\nimport type { AdXensorApi } from './api.js';\n\n/**\n * Normalise navigator.language (e.g. \"fr-FR\", \"en-US\") to a 2-char\n * ISO 639-1 code (\"fr\", \"en\"). Country is resolved server-side from IP.\n */\nfunction getLang(): string {\n return (navigator.language ?? 'fr').split('-')[0].toLowerCase();\n}\n\nexport class Tracker {\n constructor(\n private readonly api: AdXensorApi,\n private readonly siteId: string,\n private readonly sessionId: string,\n ) {}\n\n // \u2500\u2500\u2500 Base context \u2014 attached to every event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n private base() {\n return {\n siteId: this.siteId,\n url: location.href,\n device: getDevice(),\n sessionId: this.sessionId,\n language: getLang(), // \"fr\" | \"en\" | \"pt\" | \u2026\n screenWidth: screen.width, // for device analytics breakdowns\n ts: Date.now(),\n };\n }\n\n // \u2500\u2500\u2500 Pageview: once per page load \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n pageview(): void {\n this.api.sendEvent('pageview', {\n ...this.base(),\n referrer: document.referrer,\n });\n }\n\n // \u2500\u2500\u2500 Impression: once per adId \u00D7 session \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n impression(adId: string, slotId: string, impressionToken: string): void {\n const key = `imp:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('impression', {\n ...this.base(),\n adId,\n slotId,\n impressionToken,\n });\n }\n\n // \u2500\u2500\u2500 Click: deduplicated within 500 ms window per ad \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n click(adId: string, slotId: string, clickToken: string): void {\n const tsKey = `clk_ts:${adId}`;\n try {\n const last = parseInt(sessionStorage.getItem(tsKey) || '0', 10);\n if (Date.now() - last < 500) return; // double-click guard\n sessionStorage.setItem(tsKey, String(Date.now()));\n } catch { /* ignore */ }\n this.api.sendEvent('click', {\n ...this.base(),\n adId,\n slotId,\n clickToken,\n });\n }\n\n // \u2500\u2500\u2500 View: once per adId \u00D7 session (50% visible for \u22651 s) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n view(adId: string, slotId: string): void {\n const key = `view:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('view', {\n ...this.base(),\n adId,\n slotId,\n });\n }\n}\n", "import type { AdResponse } from './types.js';\n\n/**\n * Inject the ad creative into `container`.\n * - type 'html': raw HTML creative (rich media, animated banners)\n * - type 'image': <a><img></a> wrapped in a click-tracked link\n *\n * The caller handles the click event via addEventListener; the rendered\n * anchor uses href=\"#\" so the tracker can intercept and open the real URL.\n */\nexport function renderAd(container: HTMLElement, ad: AdResponse, clickHref: string): void {\n container.style.overflow = 'hidden';\n container.style.display = 'block';\n container.style.lineHeight = '0'; // removes bottom gap under <img>\n\n if (ad.type === 'html' && ad.htmlContent) {\n container.innerHTML = ad.htmlContent;\n return;\n }\n\n const a = document.createElement('a');\n a.href = clickHref;\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.setAttribute('data-adx-click', '1');\n a.style.display = 'block';\n\n const img = document.createElement('img');\n img.src = ad.imageUrl ?? '';\n img.width = ad.width;\n img.height = ad.height;\n img.alt = ad.altText || '';\n img.style.display = 'block';\n img.style.maxWidth = '100%';\n img.loading = 'lazy';\n\n a.appendChild(img);\n container.innerHTML = '';\n container.appendChild(a);\n}\n", "import type { AdXensorApi } from './api.js';\nimport type { Tracker } from './tracker.js';\nimport type { SlotOptions } from './types.js';\nimport { getDevice, resolveSize } from './device.js';\nimport { renderAd } from './renderer.js';\n\nconst VIEW_THRESHOLD = 0.5; // 50% visible\nconst VIEW_DURATION = 1000; // 1 second held in view = fires view event\n\nexport class AdSlot {\n private readonly slotId: string;\n private observer: IntersectionObserver | null = null;\n private viewTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n private readonly el: HTMLElement,\n private readonly api: AdXensorApi,\n private readonly tracker: Tracker,\n private readonly siteId: string,\n private readonly sessionId: string,\n private readonly options: SlotOptions = {},\n ) {\n // AdSense-style: data-ad-slot. Falls back to element id or a random id.\n this.slotId =\n options.slotId ??\n el.dataset.adSlot ?? // data-ad-slot=\"banner-top\"\n (el.id || `adx-${Math.random().toString(36).slice(2, 9)}`);\n }\n\n // \u2500\u2500\u2500 Load entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n load(lazy = true): void {\n if (this.el.dataset.adxState) return; // idempotent \u2014 already in-flight or done\n\n if (lazy && typeof IntersectionObserver !== 'undefined') {\n this.watchForEntry();\n } else {\n void this.fetch();\n }\n }\n\n // \u2500\u2500\u2500 Lazy entry: wait until element is near the viewport \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchForEntry(): void {\n const io = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n io.disconnect();\n void this.fetch();\n }\n },\n { rootMargin: '200px', threshold: 0 },\n );\n io.observe(this.el);\n }\n\n // \u2500\u2500\u2500 Fetch ad from API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async fetch(): Promise<void> {\n if (this.el.dataset.adxState) return; // double-check after async gap\n this.setState('loading');\n\n try {\n // AdSense-style: data-ad-format. Falls back to options.format then 'auto'.\n const size = resolveSize(\n this.options.format ?? this.el.dataset.adFormat ?? 'auto',\n this.el,\n );\n const [w, h] = size.split('x').map(Number);\n\n // Reserve exact dimensions to prevent layout shift (CLS)\n if (w && h) {\n this.el.style.width = `${w}px`;\n this.el.style.maxWidth = '100%';\n this.el.style.minHeight = `${h}px`;\n }\n\n const ad = await this.api.serveAd({\n siteId: this.siteId,\n slotId: this.slotId,\n size,\n device: getDevice(),\n url: location.href,\n referrer: document.referrer,\n // Normalise \"fr-FR\" \u2192 \"fr\"; server resolves country from IP separately\n language: (navigator.language ?? 'fr').split('-')[0].toLowerCase(),\n screenWidth: screen.width,\n sessionId: this.sessionId,\n });\n\n if (!ad) {\n this.setState('empty');\n this.el.style.display = 'none';\n return;\n }\n\n // ads_core provides the full click URL directly (handles redirect internally)\n renderAd(this.el, ad, ad.clickHref);\n this.setState('filled');\n\n // Click tracking \u2014 passive: true because we never call preventDefault\n this.el.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('[data-adx-click]')) {\n this.tracker.click(ad.adId, this.slotId, ad.clickToken);\n }\n }, { passive: true });\n\n // Impression fires immediately after render (once per adId \u00D7 session)\n this.tracker.impression(ad.adId, this.slotId, ad.impressionToken);\n\n // View fires after \u226550% visible for \u22651s (IAB standard)\n this.watchVisibility(ad.adId, this.slotId);\n\n } catch {\n this.setState('error');\n this.el.style.display = 'none';\n }\n }\n\n // \u2500\u2500\u2500 Viewability (IAB MRC standard) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchVisibility(adId: string, slotId: string): void {\n if (typeof IntersectionObserver === 'undefined') {\n this.tracker.view(adId, slotId); // fallback: fire immediately\n return;\n }\n\n this.observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n\n if (entry.intersectionRatio >= VIEW_THRESHOLD) {\n if (this.viewTimer === null) {\n this.viewTimer = setTimeout(() => {\n this.tracker.view(adId, slotId);\n this.disconnect(); // stop observing once view is counted\n }, VIEW_DURATION);\n }\n } else {\n // Left viewport before 1s \u2014 reset timer\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n },\n { threshold: [VIEW_THRESHOLD] },\n );\n\n this.observer.observe(this.el);\n }\n\n // \u2500\u2500\u2500 State machine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setState(state: 'loading' | 'filled' | 'empty' | 'error'): void {\n this.el.dataset.adxState = state;\n }\n\n // \u2500\u2500\u2500 Cleanup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n disconnect(): void {\n this.observer?.disconnect();\n this.observer = null;\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n}\n", "import { AdXensorApi } from './api.js';\nimport { Tracker } from './tracker.js';\nimport { AdSlot } from './AdSlot.js';\nimport { getSessionId } from './session.js';\nimport type { AdXensorConfig, SlotOptions } from './types.js';\n\n/** ads_core public base URL \u2014 must include /v1 suffix */\nconst DEFAULT_API_URL = 'https://core.adxensor.com/v1';\n\n/** Selector for unfilled <ins class=\"adxensor\"> slots */\nconst INS_SELECTOR = 'ins.adxensor:not([data-adx-state])';\n\nexport class AdXensor {\n /** One global instance per page (like AdSense). */\n private static instance: AdXensor | null = null;\n\n private readonly api: AdXensorApi;\n private readonly tracker: Tracker;\n private readonly sessionId: string;\n private readonly config: AdXensorConfig;\n private initialized = false;\n private domObserver: MutationObserver | null = null;\n\n constructor(config: AdXensorConfig) {\n this.config = config;\n this.sessionId = getSessionId();\n this.api = new AdXensorApi(\n config.apiUrl ?? DEFAULT_API_URL,\n config.apiKey,\n );\n this.tracker = new Tracker(this.api, config.siteId, this.sessionId);\n }\n\n // \u2500\u2500\u2500 Singleton helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static getInstance(config: AdXensorConfig): AdXensor {\n if (!AdXensor.instance) AdXensor.instance = new AdXensor(config);\n return AdXensor.instance;\n }\n\n static reset(): void {\n AdXensor.instance?.destroy();\n AdXensor.instance = null;\n }\n\n // \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Idempotent init \u2014 safe to call multiple times.\n * Fires pageview, fills existing slots, watches DOM for new ones.\n */\n init(): this {\n if (this.initialized) return this;\n this.initialized = true;\n this.tracker.pageview();\n this.fillAll();\n this.watchDom();\n return this;\n }\n\n /** Fill every unfilled <ins class=\"adxensor\"> currently in the DOM. */\n fillAll(): void {\n document.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n }\n\n /**\n * AdSense-style push \u2014 fills the next unfilled slot in DOM order.\n * Use alongside <script>(window.adxensor = window.adxensor || []).push({})</script>\n */\n push(options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(INS_SELECTOR);\n if (el) this.fill(el, options);\n }\n\n /**\n * Fill a specific element.\n * No-op if the element is already loading/filled/empty/error (idempotent).\n */\n fill(el: HTMLElement, options: SlotOptions = {}): void {\n if (el.dataset.adxState) return; // state machine guard\n\n // Enforce display:block \u2014 required like AdSense\n if (el.style.display === 'none') el.style.display = 'block';\n\n const slot = new AdSlot(\n el,\n this.api,\n this.tracker,\n this.config.siteId,\n this.sessionId,\n options,\n );\n\n const lazy = options.lazy ?? this.config.lazyLoad ?? true;\n slot.load(lazy);\n }\n\n /**\n * Programmatic slot fill by CSS selector.\n * Logs a warning in debug mode if the element is not found.\n */\n defineSlot(selector: string, options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(selector);\n if (!el) {\n if (this.config.debug) console.warn(`[AdXensor] defineSlot: \"${selector}\" not found`);\n return;\n }\n this.fill(el, options);\n }\n\n // \u2500\u2500\u2500 DOM watcher (SPA support) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchDom(): void {\n if (typeof MutationObserver === 'undefined') return;\n\n this.domObserver = new MutationObserver((mutations) => {\n for (const { addedNodes } of mutations) {\n addedNodes.forEach((node) => {\n if (!(node instanceof HTMLElement)) return;\n if (node.matches('ins.adxensor') && !node.dataset.adxState) {\n this.fill(node);\n }\n node.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n });\n }\n });\n\n this.domObserver.observe(document.body, { childList: true, subtree: true });\n }\n\n destroy(): void {\n this.domObserver?.disconnect();\n this.domObserver = null;\n this.initialized = false;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// \u2500\u2500\u2500 Main class (primary entrypoint) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { AdXensor } from './core/AdXensor.js';\n\n// \u2500\u2500\u2500 Lower-level building blocks (advanced / custom integrations) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport { AdXensorApi } from './core/api.js';\nexport { Tracker } from './core/tracker.js';\nexport { AdSlot } from './core/AdSlot.js';\nexport { renderAd } from './core/renderer.js';\nexport { getDevice, resolveSize } from './core/device.js';\nexport { getSessionId, hasFired, markFired } from './core/session.js';\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nexport type {\n AdXensorConfig,\n SlotOptions,\n SlotState,\n AdFormat,\n AdSize, // deprecated alias for AdFormat \u2014 kept for backwards compat\n DeviceType,\n AdResponse,\n ServeRequest,\n AdEventPayload,\n PageviewPayload,\n} from './core/types.js';\n", "import type { ServeRequest, AdResponse, AdEventPayload, PageviewPayload } from './types.js';\n\n/** ads_core /v1/ad response shape */\ninterface AdsCoreAdResponse {\n creative_url: string;\n click_url: string; // full URL e.g. https://ads.adxensor.com/v1/click/TOKEN\n impression_url: string; // full URL e.g. https://ads.adxensor.com/v1/impression/TOKEN\n campaign_id: string;\n size: string; // \"300x250\"\n}\n\nexport class AdXensorApi {\n constructor(\n private readonly baseUrl: string, // e.g. \"https://ads.adxensor.com/v1\"\n private readonly apiKey?: string,\n ) {}\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) h['X-Publisher-Key'] = this.apiKey;\n return h;\n }\n\n // \u2500\u2500\u2500 Fetch the best ad for a slot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async serveAd(req: ServeRequest): Promise<AdResponse | null> {\n try {\n // Map SDK params \u2192 ads_core query schema\n const params = new URLSearchParams({ zone: req.size, site: req.siteId });\n if (req.url) params.set('url', req.url);\n if (req.referrer) params.set('ref', req.referrer);\n\n const res = await fetch(`${this.baseUrl}/ad?${params.toString()}`, {\n method: 'GET',\n headers: this.headers(),\n });\n\n // 204 = no ad available for this slot\n if (res.status === 204 || !res.ok) return null;\n\n const data = await res.json() as AdsCoreAdResponse;\n\n // Extract shared event token from either tracking URL\n const eventId = data.impression_url.split('/').pop() ?? '';\n\n const [w, h] = (data.size ?? '300x250').split('x').map(Number);\n\n return {\n adId: eventId,\n campaignId: data.campaign_id,\n type: 'image',\n imageUrl: data.creative_url,\n clickHref: data.click_url, // full URL \u2014 use directly as <a> href\n width: w ?? 300,\n height: h ?? 250,\n altText: '',\n impressionToken: eventId,\n clickToken: eventId,\n };\n } catch {\n return null;\n }\n }\n\n // \u2500\u2500\u2500 Integration ping \u2014 called once on SDK init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ping(siteId: string): void {\n fetch(`${this.baseUrl}/ping`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify({ siteId }),\n keepalive: true,\n }).catch(() => { /* silent */ });\n }\n\n // \u2500\u2500\u2500 Fire-and-forget event (uses sendBeacon when available) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n sendEvent(path: string, payload: AdEventPayload | PageviewPayload): void {\n const url = `${this.baseUrl}/events/${path}`;\n const body = JSON.stringify(payload);\n\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));\n } else {\n fetch(url, {\n method: 'POST',\n headers: this.headers(),\n body,\n keepalive: true,\n }).catch(() => { /* silent */ });\n }\n }\n}\n", "const SESSION_KEY = '_adx_sid';\nconst FIRED_KEY = '_adx_fired';\n\n/** Returns (or creates) a session ID stored in sessionStorage. */\nexport function getSessionId(): string {\n try {\n let id = sessionStorage.getItem(SESSION_KEY);\n if (!id) {\n id = typeof crypto !== 'undefined' && crypto.randomUUID\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2) + Date.now().toString(36);\n sessionStorage.setItem(SESSION_KEY, id);\n }\n return id;\n } catch {\n // sessionStorage blocked (iframe sandbox, private mode, etc.)\n return Math.random().toString(36).slice(2);\n }\n}\n\n/** Returns true if this event key was already fired this session. */\nexport function hasFired(key: string): boolean {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n return key in map;\n } catch {\n return false;\n }\n}\n\n/** Mark an event key as fired. */\nexport function markFired(key: string): void {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n map[key] = Date.now();\n sessionStorage.setItem(FIRED_KEY, JSON.stringify(map));\n } catch { /* ignore */ }\n}\n", "import type { DeviceType } from './types.js';\n\nexport function getDevice(): DeviceType {\n if (typeof window === 'undefined') return 'desktop';\n const w = window.innerWidth;\n if (w < 768) return 'mobile';\n if (w < 1024) return 'tablet';\n return 'desktop';\n}\n\n/**\n * Resolve 'auto' to a concrete size based on the container width + device.\n * Falls back to a safe default for each device tier.\n */\nexport function resolveSize(requested: string, container: HTMLElement): string {\n if (requested !== 'auto') return requested;\n\n const device = getDevice();\n const w = container.offsetWidth || window.innerWidth;\n\n if (device === 'mobile') {\n return w >= 320 ? '320x50' : '300x250';\n }\n if (device === 'tablet') {\n return w >= 468 ? '468x60' : '300x250';\n }\n // desktop\n if (w >= 728) return '728x90';\n if (w >= 468) return '468x60';\n return '300x250';\n}\n", "import { hasFired, markFired } from './session.js';\nimport { getDevice } from './device.js';\nimport type { AdXensorApi } from './api.js';\n\n/**\n * Normalise navigator.language (e.g. \"fr-FR\", \"en-US\") to a 2-char\n * ISO 639-1 code (\"fr\", \"en\"). Country is resolved server-side from IP.\n */\nfunction getLang(): string {\n return (navigator.language ?? 'fr').split('-')[0].toLowerCase();\n}\n\nexport class Tracker {\n constructor(\n private readonly api: AdXensorApi,\n private readonly siteId: string,\n private readonly sessionId: string,\n ) {}\n\n // \u2500\u2500\u2500 Base context \u2014 attached to every event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n private base() {\n return {\n siteId: this.siteId,\n url: location.href,\n device: getDevice(),\n sessionId: this.sessionId,\n language: getLang(), // \"fr\" | \"en\" | \"pt\" | \u2026\n screenWidth: screen.width, // for device analytics breakdowns\n ts: Date.now(),\n };\n }\n\n // \u2500\u2500\u2500 Pageview: once per page load \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n pageview(): void {\n this.api.sendEvent('pageview', {\n ...this.base(),\n referrer: document.referrer,\n });\n }\n\n // \u2500\u2500\u2500 Impression: once per adId \u00D7 session \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n impression(adId: string, slotId: string, impressionToken: string): void {\n const key = `imp:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('impression', {\n ...this.base(),\n adId,\n slotId,\n impressionToken,\n });\n }\n\n // \u2500\u2500\u2500 Click: deduplicated within 500 ms window per ad \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n click(adId: string, slotId: string, clickToken: string): void {\n const tsKey = `clk_ts:${adId}`;\n try {\n const last = parseInt(sessionStorage.getItem(tsKey) || '0', 10);\n if (Date.now() - last < 500) return; // double-click guard\n sessionStorage.setItem(tsKey, String(Date.now()));\n } catch { /* ignore */ }\n this.api.sendEvent('click', {\n ...this.base(),\n adId,\n slotId,\n clickToken,\n });\n }\n\n // \u2500\u2500\u2500 View: once per adId \u00D7 session (50% visible for \u22651 s) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n view(adId: string, slotId: string): void {\n const key = `view:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('view', {\n ...this.base(),\n adId,\n slotId,\n });\n }\n}\n", "import type { AdResponse } from './types.js';\n\n/**\n * Inject the ad creative into `container`.\n * - type 'html': raw HTML creative (rich media, animated banners)\n * - type 'image': <a><img></a> wrapped in a click-tracked link\n *\n * The caller handles the click event via addEventListener; the rendered\n * anchor uses href=\"#\" so the tracker can intercept and open the real URL.\n */\nexport function renderAd(container: HTMLElement, ad: AdResponse, clickHref: string): void {\n container.style.overflow = 'hidden';\n container.style.display = 'block';\n container.style.lineHeight = '0'; // removes bottom gap under <img>\n\n if (ad.type === 'html' && ad.htmlContent) {\n container.innerHTML = ad.htmlContent;\n return;\n }\n\n const a = document.createElement('a');\n a.href = clickHref;\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.setAttribute('data-adx-click', '1');\n a.style.display = 'block';\n\n const img = document.createElement('img');\n img.src = ad.imageUrl ?? '';\n img.width = ad.width;\n img.height = ad.height;\n img.alt = ad.altText || '';\n img.style.display = 'block';\n img.style.maxWidth = '100%';\n img.loading = 'lazy';\n\n a.appendChild(img);\n container.innerHTML = '';\n container.appendChild(a);\n}\n", "import type { AdXensorApi } from './api.js';\nimport type { Tracker } from './tracker.js';\nimport type { SlotOptions } from './types.js';\nimport { getDevice, resolveSize } from './device.js';\nimport { renderAd } from './renderer.js';\n\nconst VIEW_THRESHOLD = 0.5; // 50% visible\nconst VIEW_DURATION = 1000; // 1 second held in view = fires view event\n\nexport class AdSlot {\n private readonly slotId: string;\n private observer: IntersectionObserver | null = null;\n private viewTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n private readonly el: HTMLElement,\n private readonly api: AdXensorApi,\n private readonly tracker: Tracker,\n private readonly siteId: string,\n private readonly sessionId: string,\n private readonly options: SlotOptions = {},\n ) {\n // AdSense-style: data-ad-slot. Falls back to element id or a random id.\n this.slotId =\n options.slotId ??\n el.dataset.adSlot ?? // data-ad-slot=\"banner-top\"\n (el.id || `adx-${Math.random().toString(36).slice(2, 9)}`);\n }\n\n // \u2500\u2500\u2500 Load entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n load(lazy = true): void {\n if (this.el.dataset.adxState) return; // idempotent \u2014 already in-flight or done\n\n if (lazy && typeof IntersectionObserver !== 'undefined') {\n this.watchForEntry();\n } else {\n void this.fetch();\n }\n }\n\n // \u2500\u2500\u2500 Lazy entry: wait until element is near the viewport \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchForEntry(): void {\n const io = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n io.disconnect();\n void this.fetch();\n }\n },\n { rootMargin: '200px', threshold: 0 },\n );\n io.observe(this.el);\n }\n\n // \u2500\u2500\u2500 Fetch ad from API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async fetch(): Promise<void> {\n if (this.el.dataset.adxState) return; // double-check after async gap\n this.setState('loading');\n\n try {\n // AdSense-style: data-ad-format. Falls back to options.format then 'auto'.\n const size = resolveSize(\n this.options.format ?? this.el.dataset.adFormat ?? 'auto',\n this.el,\n );\n const [w, h] = size.split('x').map(Number);\n\n // Reserve exact dimensions to prevent layout shift (CLS)\n if (w && h) {\n this.el.style.width = `${w}px`;\n this.el.style.maxWidth = '100%';\n this.el.style.minHeight = `${h}px`;\n }\n\n const ad = await this.api.serveAd({\n siteId: this.siteId,\n slotId: this.slotId,\n size,\n device: getDevice(),\n url: location.href,\n referrer: document.referrer,\n // Normalise \"fr-FR\" \u2192 \"fr\"; server resolves country from IP separately\n language: (navigator.language ?? 'fr').split('-')[0].toLowerCase(),\n screenWidth: screen.width,\n sessionId: this.sessionId,\n });\n\n if (!ad) {\n this.setState('empty');\n this.el.style.display = 'none';\n return;\n }\n\n // ads_core provides the full click URL directly (handles redirect internally)\n renderAd(this.el, ad, ad.clickHref);\n this.setState('filled');\n\n // Click tracking \u2014 passive: true because we never call preventDefault\n this.el.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('[data-adx-click]')) {\n this.tracker.click(ad.adId, this.slotId, ad.clickToken);\n }\n }, { passive: true });\n\n // Impression fires immediately after render (once per adId \u00D7 session)\n this.tracker.impression(ad.adId, this.slotId, ad.impressionToken);\n\n // View fires after \u226550% visible for \u22651s (IAB standard)\n this.watchVisibility(ad.adId, this.slotId);\n\n } catch {\n this.setState('error');\n this.el.style.display = 'none';\n }\n }\n\n // \u2500\u2500\u2500 Viewability (IAB MRC standard) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchVisibility(adId: string, slotId: string): void {\n if (typeof IntersectionObserver === 'undefined') {\n this.tracker.view(adId, slotId); // fallback: fire immediately\n return;\n }\n\n this.observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n\n if (entry.intersectionRatio >= VIEW_THRESHOLD) {\n if (this.viewTimer === null) {\n this.viewTimer = setTimeout(() => {\n this.tracker.view(adId, slotId);\n this.disconnect(); // stop observing once view is counted\n }, VIEW_DURATION);\n }\n } else {\n // Left viewport before 1s \u2014 reset timer\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n },\n { threshold: [VIEW_THRESHOLD] },\n );\n\n this.observer.observe(this.el);\n }\n\n // \u2500\u2500\u2500 State machine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setState(state: 'loading' | 'filled' | 'empty' | 'error'): void {\n this.el.dataset.adxState = state;\n }\n\n // \u2500\u2500\u2500 Cleanup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n disconnect(): void {\n this.observer?.disconnect();\n this.observer = null;\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n}\n", "import { AdXensorApi } from './api.js';\nimport { Tracker } from './tracker.js';\nimport { AdSlot } from './AdSlot.js';\nimport { getSessionId } from './session.js';\nimport type { AdXensorConfig, SlotOptions } from './types.js';\n\n/** ads_core public base URL \u2014 must include /v1 suffix */\nconst DEFAULT_API_URL = 'https://core.adxensor.com/v1';\n\n/** Selector for unfilled <ins class=\"adxensor\"> slots */\nconst INS_SELECTOR = 'ins.adxensor:not([data-adx-state])';\n\nexport class AdXensor {\n /** One global instance per page (like AdSense). */\n private static instance: AdXensor | null = null;\n\n private readonly api: AdXensorApi;\n private readonly tracker: Tracker;\n private readonly sessionId: string;\n private readonly config: AdXensorConfig;\n private initialized = false;\n private domObserver: MutationObserver | null = null;\n\n constructor(config: AdXensorConfig) {\n this.config = config;\n this.sessionId = getSessionId();\n this.api = new AdXensorApi(\n config.apiUrl ?? DEFAULT_API_URL,\n config.apiKey,\n );\n this.tracker = new Tracker(this.api, config.siteId, this.sessionId);\n }\n\n // \u2500\u2500\u2500 Singleton helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static getInstance(config: AdXensorConfig): AdXensor {\n if (!AdXensor.instance) AdXensor.instance = new AdXensor(config);\n return AdXensor.instance;\n }\n\n static reset(): void {\n AdXensor.instance?.destroy();\n AdXensor.instance = null;\n }\n\n // \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Idempotent init \u2014 safe to call multiple times.\n * Fires pageview, fills existing slots, watches DOM for new ones.\n */\n init(): this {\n if (this.initialized) return this;\n this.initialized = true;\n this.tracker.pageview();\n this.api.ping(this.config.siteId);\n this.fillAll();\n this.watchDom();\n return this;\n }\n\n /** Fill every unfilled <ins class=\"adxensor\"> currently in the DOM. */\n fillAll(): void {\n document.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n }\n\n /**\n * AdSense-style push \u2014 fills the next unfilled slot in DOM order.\n * Use alongside <script>(window.adxensor = window.adxensor || []).push({})</script>\n */\n push(options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(INS_SELECTOR);\n if (el) this.fill(el, options);\n }\n\n /**\n * Fill a specific element.\n * No-op if the element is already loading/filled/empty/error (idempotent).\n */\n fill(el: HTMLElement, options: SlotOptions = {}): void {\n if (el.dataset.adxState) return; // state machine guard\n\n // Enforce display:block \u2014 required like AdSense\n if (el.style.display === 'none') el.style.display = 'block';\n\n const slot = new AdSlot(\n el,\n this.api,\n this.tracker,\n this.config.siteId,\n this.sessionId,\n options,\n );\n\n const lazy = options.lazy ?? this.config.lazyLoad ?? true;\n slot.load(lazy);\n }\n\n /**\n * Programmatic slot fill by CSS selector.\n * Logs a warning in debug mode if the element is not found.\n */\n defineSlot(selector: string, options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(selector);\n if (!el) {\n if (this.config.debug) console.warn(`[AdXensor] defineSlot: \"${selector}\" not found`);\n return;\n }\n this.fill(el, options);\n }\n\n // \u2500\u2500\u2500 DOM watcher (SPA support) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchDom(): void {\n if (typeof MutationObserver === 'undefined') return;\n\n this.domObserver = new MutationObserver((mutations) => {\n for (const { addedNodes } of mutations) {\n addedNodes.forEach((node) => {\n if (!(node instanceof HTMLElement)) return;\n if (node.matches('ins.adxensor') && !node.dataset.adxState) {\n this.fill(node);\n }\n node.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n });\n }\n });\n\n this.domObserver.observe(document.body, { childList: true, subtree: true });\n }\n\n destroy(): void {\n this.domObserver?.disconnect();\n this.domObserver = null;\n this.initialized = false;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -61,6 +61,16 @@ var AdXensorApi = class {
61
61
  return null;
62
62
  }
63
63
  }
64
+ // ─── Integration ping — called once on SDK init ───────────────────────────
65
+ ping(siteId) {
66
+ fetch(`${this.baseUrl}/ping`, {
67
+ method: "POST",
68
+ headers: this.headers(),
69
+ body: JSON.stringify({ siteId }),
70
+ keepalive: true
71
+ }).catch(() => {
72
+ });
73
+ }
64
74
  // ─── Fire-and-forget event (uses sendBeacon when available) ───────────────
65
75
  sendEvent(path, payload) {
66
76
  const url = `${this.baseUrl}/events/${path}`;
@@ -399,6 +409,7 @@ var _AdXensor = class _AdXensor {
399
409
  if (this.initialized) return this;
400
410
  this.initialized = true;
401
411
  this.tracker.pageview();
412
+ this.api.ping(this.config.siteId);
402
413
  this.fillAll();
403
414
  this.watchDom();
404
415
  return this;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/api.ts", "../../../src/core/session.ts", "../../../src/core/device.ts", "../../../src/core/tracker.ts", "../../../src/core/renderer.ts", "../../../src/core/AdSlot.ts", "../../../src/core/AdXensor.ts"],
4
- "sourcesContent": ["import type { ServeRequest, AdResponse, AdEventPayload, PageviewPayload } from './types.js';\n\n/** ads_core /v1/ad response shape */\ninterface AdsCoreAdResponse {\n creative_url: string;\n click_url: string; // full URL e.g. https://ads.adxensor.com/v1/click/TOKEN\n impression_url: string; // full URL e.g. https://ads.adxensor.com/v1/impression/TOKEN\n campaign_id: string;\n size: string; // \"300x250\"\n}\n\nexport class AdXensorApi {\n constructor(\n private readonly baseUrl: string, // e.g. \"https://ads.adxensor.com/v1\"\n private readonly apiKey?: string,\n ) {}\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) h['X-Publisher-Key'] = this.apiKey;\n return h;\n }\n\n // \u2500\u2500\u2500 Fetch the best ad for a slot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async serveAd(req: ServeRequest): Promise<AdResponse | null> {\n try {\n // Map SDK params \u2192 ads_core query schema\n const params = new URLSearchParams({ zone: req.size, site: req.siteId });\n if (req.url) params.set('url', req.url);\n if (req.referrer) params.set('ref', req.referrer);\n\n const res = await fetch(`${this.baseUrl}/ad?${params.toString()}`, {\n method: 'GET',\n headers: this.headers(),\n });\n\n // 204 = no ad available for this slot\n if (res.status === 204 || !res.ok) return null;\n\n const data = await res.json() as AdsCoreAdResponse;\n\n // Extract shared event token from either tracking URL\n const eventId = data.impression_url.split('/').pop() ?? '';\n\n const [w, h] = (data.size ?? '300x250').split('x').map(Number);\n\n return {\n adId: eventId,\n campaignId: data.campaign_id,\n type: 'image',\n imageUrl: data.creative_url,\n clickHref: data.click_url, // full URL \u2014 use directly as <a> href\n width: w ?? 300,\n height: h ?? 250,\n altText: '',\n impressionToken: eventId,\n clickToken: eventId,\n };\n } catch {\n return null;\n }\n }\n\n // \u2500\u2500\u2500 Fire-and-forget event (uses sendBeacon when available) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n sendEvent(path: string, payload: AdEventPayload | PageviewPayload): void {\n const url = `${this.baseUrl}/events/${path}`;\n const body = JSON.stringify(payload);\n\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));\n } else {\n fetch(url, {\n method: 'POST',\n headers: this.headers(),\n body,\n keepalive: true,\n }).catch(() => { /* silent */ });\n }\n }\n}\n", "const SESSION_KEY = '_adx_sid';\nconst FIRED_KEY = '_adx_fired';\n\n/** Returns (or creates) a session ID stored in sessionStorage. */\nexport function getSessionId(): string {\n try {\n let id = sessionStorage.getItem(SESSION_KEY);\n if (!id) {\n id = typeof crypto !== 'undefined' && crypto.randomUUID\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2) + Date.now().toString(36);\n sessionStorage.setItem(SESSION_KEY, id);\n }\n return id;\n } catch {\n // sessionStorage blocked (iframe sandbox, private mode, etc.)\n return Math.random().toString(36).slice(2);\n }\n}\n\n/** Returns true if this event key was already fired this session. */\nexport function hasFired(key: string): boolean {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n return key in map;\n } catch {\n return false;\n }\n}\n\n/** Mark an event key as fired. */\nexport function markFired(key: string): void {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n map[key] = Date.now();\n sessionStorage.setItem(FIRED_KEY, JSON.stringify(map));\n } catch { /* ignore */ }\n}\n", "import type { DeviceType } from './types.js';\n\nexport function getDevice(): DeviceType {\n if (typeof window === 'undefined') return 'desktop';\n const w = window.innerWidth;\n if (w < 768) return 'mobile';\n if (w < 1024) return 'tablet';\n return 'desktop';\n}\n\n/**\n * Resolve 'auto' to a concrete size based on the container width + device.\n * Falls back to a safe default for each device tier.\n */\nexport function resolveSize(requested: string, container: HTMLElement): string {\n if (requested !== 'auto') return requested;\n\n const device = getDevice();\n const w = container.offsetWidth || window.innerWidth;\n\n if (device === 'mobile') {\n return w >= 320 ? '320x50' : '300x250';\n }\n if (device === 'tablet') {\n return w >= 468 ? '468x60' : '300x250';\n }\n // desktop\n if (w >= 728) return '728x90';\n if (w >= 468) return '468x60';\n return '300x250';\n}\n", "import { hasFired, markFired } from './session.js';\nimport { getDevice } from './device.js';\nimport type { AdXensorApi } from './api.js';\n\n/**\n * Normalise navigator.language (e.g. \"fr-FR\", \"en-US\") to a 2-char\n * ISO 639-1 code (\"fr\", \"en\"). Country is resolved server-side from IP.\n */\nfunction getLang(): string {\n return (navigator.language ?? 'fr').split('-')[0].toLowerCase();\n}\n\nexport class Tracker {\n constructor(\n private readonly api: AdXensorApi,\n private readonly siteId: string,\n private readonly sessionId: string,\n ) {}\n\n // \u2500\u2500\u2500 Base context \u2014 attached to every event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n private base() {\n return {\n siteId: this.siteId,\n url: location.href,\n device: getDevice(),\n sessionId: this.sessionId,\n language: getLang(), // \"fr\" | \"en\" | \"pt\" | \u2026\n screenWidth: screen.width, // for device analytics breakdowns\n ts: Date.now(),\n };\n }\n\n // \u2500\u2500\u2500 Pageview: once per page load \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n pageview(): void {\n this.api.sendEvent('pageview', {\n ...this.base(),\n referrer: document.referrer,\n });\n }\n\n // \u2500\u2500\u2500 Impression: once per adId \u00D7 session \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n impression(adId: string, slotId: string, impressionToken: string): void {\n const key = `imp:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('impression', {\n ...this.base(),\n adId,\n slotId,\n impressionToken,\n });\n }\n\n // \u2500\u2500\u2500 Click: deduplicated within 500 ms window per ad \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n click(adId: string, slotId: string, clickToken: string): void {\n const tsKey = `clk_ts:${adId}`;\n try {\n const last = parseInt(sessionStorage.getItem(tsKey) || '0', 10);\n if (Date.now() - last < 500) return; // double-click guard\n sessionStorage.setItem(tsKey, String(Date.now()));\n } catch { /* ignore */ }\n this.api.sendEvent('click', {\n ...this.base(),\n adId,\n slotId,\n clickToken,\n });\n }\n\n // \u2500\u2500\u2500 View: once per adId \u00D7 session (50% visible for \u22651 s) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n view(adId: string, slotId: string): void {\n const key = `view:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('view', {\n ...this.base(),\n adId,\n slotId,\n });\n }\n}\n", "import type { AdResponse } from './types.js';\n\n/**\n * Inject the ad creative into `container`.\n * - type 'html': raw HTML creative (rich media, animated banners)\n * - type 'image': <a><img></a> wrapped in a click-tracked link\n *\n * The caller handles the click event via addEventListener; the rendered\n * anchor uses href=\"#\" so the tracker can intercept and open the real URL.\n */\nexport function renderAd(container: HTMLElement, ad: AdResponse, clickHref: string): void {\n container.style.overflow = 'hidden';\n container.style.display = 'block';\n container.style.lineHeight = '0'; // removes bottom gap under <img>\n\n if (ad.type === 'html' && ad.htmlContent) {\n container.innerHTML = ad.htmlContent;\n return;\n }\n\n const a = document.createElement('a');\n a.href = clickHref;\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.setAttribute('data-adx-click', '1');\n a.style.display = 'block';\n\n const img = document.createElement('img');\n img.src = ad.imageUrl ?? '';\n img.width = ad.width;\n img.height = ad.height;\n img.alt = ad.altText || '';\n img.style.display = 'block';\n img.style.maxWidth = '100%';\n img.loading = 'lazy';\n\n a.appendChild(img);\n container.innerHTML = '';\n container.appendChild(a);\n}\n", "import type { AdXensorApi } from './api.js';\nimport type { Tracker } from './tracker.js';\nimport type { SlotOptions } from './types.js';\nimport { getDevice, resolveSize } from './device.js';\nimport { renderAd } from './renderer.js';\n\nconst VIEW_THRESHOLD = 0.5; // 50% visible\nconst VIEW_DURATION = 1000; // 1 second held in view = fires view event\n\nexport class AdSlot {\n private readonly slotId: string;\n private observer: IntersectionObserver | null = null;\n private viewTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n private readonly el: HTMLElement,\n private readonly api: AdXensorApi,\n private readonly tracker: Tracker,\n private readonly siteId: string,\n private readonly sessionId: string,\n private readonly options: SlotOptions = {},\n ) {\n // AdSense-style: data-ad-slot. Falls back to element id or a random id.\n this.slotId =\n options.slotId ??\n el.dataset.adSlot ?? // data-ad-slot=\"banner-top\"\n (el.id || `adx-${Math.random().toString(36).slice(2, 9)}`);\n }\n\n // \u2500\u2500\u2500 Load entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n load(lazy = true): void {\n if (this.el.dataset.adxState) return; // idempotent \u2014 already in-flight or done\n\n if (lazy && typeof IntersectionObserver !== 'undefined') {\n this.watchForEntry();\n } else {\n void this.fetch();\n }\n }\n\n // \u2500\u2500\u2500 Lazy entry: wait until element is near the viewport \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchForEntry(): void {\n const io = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n io.disconnect();\n void this.fetch();\n }\n },\n { rootMargin: '200px', threshold: 0 },\n );\n io.observe(this.el);\n }\n\n // \u2500\u2500\u2500 Fetch ad from API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async fetch(): Promise<void> {\n if (this.el.dataset.adxState) return; // double-check after async gap\n this.setState('loading');\n\n try {\n // AdSense-style: data-ad-format. Falls back to options.format then 'auto'.\n const size = resolveSize(\n this.options.format ?? this.el.dataset.adFormat ?? 'auto',\n this.el,\n );\n const [w, h] = size.split('x').map(Number);\n\n // Reserve exact dimensions to prevent layout shift (CLS)\n if (w && h) {\n this.el.style.width = `${w}px`;\n this.el.style.maxWidth = '100%';\n this.el.style.minHeight = `${h}px`;\n }\n\n const ad = await this.api.serveAd({\n siteId: this.siteId,\n slotId: this.slotId,\n size,\n device: getDevice(),\n url: location.href,\n referrer: document.referrer,\n // Normalise \"fr-FR\" \u2192 \"fr\"; server resolves country from IP separately\n language: (navigator.language ?? 'fr').split('-')[0].toLowerCase(),\n screenWidth: screen.width,\n sessionId: this.sessionId,\n });\n\n if (!ad) {\n this.setState('empty');\n this.el.style.display = 'none';\n return;\n }\n\n // ads_core provides the full click URL directly (handles redirect internally)\n renderAd(this.el, ad, ad.clickHref);\n this.setState('filled');\n\n // Click tracking \u2014 passive: true because we never call preventDefault\n this.el.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('[data-adx-click]')) {\n this.tracker.click(ad.adId, this.slotId, ad.clickToken);\n }\n }, { passive: true });\n\n // Impression fires immediately after render (once per adId \u00D7 session)\n this.tracker.impression(ad.adId, this.slotId, ad.impressionToken);\n\n // View fires after \u226550% visible for \u22651s (IAB standard)\n this.watchVisibility(ad.adId, this.slotId);\n\n } catch {\n this.setState('error');\n this.el.style.display = 'none';\n }\n }\n\n // \u2500\u2500\u2500 Viewability (IAB MRC standard) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchVisibility(adId: string, slotId: string): void {\n if (typeof IntersectionObserver === 'undefined') {\n this.tracker.view(adId, slotId); // fallback: fire immediately\n return;\n }\n\n this.observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n\n if (entry.intersectionRatio >= VIEW_THRESHOLD) {\n if (this.viewTimer === null) {\n this.viewTimer = setTimeout(() => {\n this.tracker.view(adId, slotId);\n this.disconnect(); // stop observing once view is counted\n }, VIEW_DURATION);\n }\n } else {\n // Left viewport before 1s \u2014 reset timer\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n },\n { threshold: [VIEW_THRESHOLD] },\n );\n\n this.observer.observe(this.el);\n }\n\n // \u2500\u2500\u2500 State machine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setState(state: 'loading' | 'filled' | 'empty' | 'error'): void {\n this.el.dataset.adxState = state;\n }\n\n // \u2500\u2500\u2500 Cleanup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n disconnect(): void {\n this.observer?.disconnect();\n this.observer = null;\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n}\n", "import { AdXensorApi } from './api.js';\nimport { Tracker } from './tracker.js';\nimport { AdSlot } from './AdSlot.js';\nimport { getSessionId } from './session.js';\nimport type { AdXensorConfig, SlotOptions } from './types.js';\n\n/** ads_core public base URL \u2014 must include /v1 suffix */\nconst DEFAULT_API_URL = 'https://core.adxensor.com/v1';\n\n/** Selector for unfilled <ins class=\"adxensor\"> slots */\nconst INS_SELECTOR = 'ins.adxensor:not([data-adx-state])';\n\nexport class AdXensor {\n /** One global instance per page (like AdSense). */\n private static instance: AdXensor | null = null;\n\n private readonly api: AdXensorApi;\n private readonly tracker: Tracker;\n private readonly sessionId: string;\n private readonly config: AdXensorConfig;\n private initialized = false;\n private domObserver: MutationObserver | null = null;\n\n constructor(config: AdXensorConfig) {\n this.config = config;\n this.sessionId = getSessionId();\n this.api = new AdXensorApi(\n config.apiUrl ?? DEFAULT_API_URL,\n config.apiKey,\n );\n this.tracker = new Tracker(this.api, config.siteId, this.sessionId);\n }\n\n // \u2500\u2500\u2500 Singleton helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static getInstance(config: AdXensorConfig): AdXensor {\n if (!AdXensor.instance) AdXensor.instance = new AdXensor(config);\n return AdXensor.instance;\n }\n\n static reset(): void {\n AdXensor.instance?.destroy();\n AdXensor.instance = null;\n }\n\n // \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Idempotent init \u2014 safe to call multiple times.\n * Fires pageview, fills existing slots, watches DOM for new ones.\n */\n init(): this {\n if (this.initialized) return this;\n this.initialized = true;\n this.tracker.pageview();\n this.fillAll();\n this.watchDom();\n return this;\n }\n\n /** Fill every unfilled <ins class=\"adxensor\"> currently in the DOM. */\n fillAll(): void {\n document.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n }\n\n /**\n * AdSense-style push \u2014 fills the next unfilled slot in DOM order.\n * Use alongside <script>(window.adxensor = window.adxensor || []).push({})</script>\n */\n push(options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(INS_SELECTOR);\n if (el) this.fill(el, options);\n }\n\n /**\n * Fill a specific element.\n * No-op if the element is already loading/filled/empty/error (idempotent).\n */\n fill(el: HTMLElement, options: SlotOptions = {}): void {\n if (el.dataset.adxState) return; // state machine guard\n\n // Enforce display:block \u2014 required like AdSense\n if (el.style.display === 'none') el.style.display = 'block';\n\n const slot = new AdSlot(\n el,\n this.api,\n this.tracker,\n this.config.siteId,\n this.sessionId,\n options,\n );\n\n const lazy = options.lazy ?? this.config.lazyLoad ?? true;\n slot.load(lazy);\n }\n\n /**\n * Programmatic slot fill by CSS selector.\n * Logs a warning in debug mode if the element is not found.\n */\n defineSlot(selector: string, options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(selector);\n if (!el) {\n if (this.config.debug) console.warn(`[AdXensor] defineSlot: \"${selector}\" not found`);\n return;\n }\n this.fill(el, options);\n }\n\n // \u2500\u2500\u2500 DOM watcher (SPA support) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchDom(): void {\n if (typeof MutationObserver === 'undefined') return;\n\n this.domObserver = new MutationObserver((mutations) => {\n for (const { addedNodes } of mutations) {\n addedNodes.forEach((node) => {\n if (!(node instanceof HTMLElement)) return;\n if (node.matches('ins.adxensor') && !node.dataset.adxState) {\n this.fill(node);\n }\n node.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n });\n }\n });\n\n this.domObserver.observe(document.body, { childList: true, subtree: true });\n }\n\n destroy(): void {\n this.domObserver?.disconnect();\n this.domObserver = null;\n this.initialized = false;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ServeRequest, AdResponse, AdEventPayload, PageviewPayload } from './types.js';\n\n/** ads_core /v1/ad response shape */\ninterface AdsCoreAdResponse {\n creative_url: string;\n click_url: string; // full URL e.g. https://ads.adxensor.com/v1/click/TOKEN\n impression_url: string; // full URL e.g. https://ads.adxensor.com/v1/impression/TOKEN\n campaign_id: string;\n size: string; // \"300x250\"\n}\n\nexport class AdXensorApi {\n constructor(\n private readonly baseUrl: string, // e.g. \"https://ads.adxensor.com/v1\"\n private readonly apiKey?: string,\n ) {}\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { 'Content-Type': 'application/json' };\n if (this.apiKey) h['X-Publisher-Key'] = this.apiKey;\n return h;\n }\n\n // \u2500\u2500\u2500 Fetch the best ad for a slot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n async serveAd(req: ServeRequest): Promise<AdResponse | null> {\n try {\n // Map SDK params \u2192 ads_core query schema\n const params = new URLSearchParams({ zone: req.size, site: req.siteId });\n if (req.url) params.set('url', req.url);\n if (req.referrer) params.set('ref', req.referrer);\n\n const res = await fetch(`${this.baseUrl}/ad?${params.toString()}`, {\n method: 'GET',\n headers: this.headers(),\n });\n\n // 204 = no ad available for this slot\n if (res.status === 204 || !res.ok) return null;\n\n const data = await res.json() as AdsCoreAdResponse;\n\n // Extract shared event token from either tracking URL\n const eventId = data.impression_url.split('/').pop() ?? '';\n\n const [w, h] = (data.size ?? '300x250').split('x').map(Number);\n\n return {\n adId: eventId,\n campaignId: data.campaign_id,\n type: 'image',\n imageUrl: data.creative_url,\n clickHref: data.click_url, // full URL \u2014 use directly as <a> href\n width: w ?? 300,\n height: h ?? 250,\n altText: '',\n impressionToken: eventId,\n clickToken: eventId,\n };\n } catch {\n return null;\n }\n }\n\n // \u2500\u2500\u2500 Integration ping \u2014 called once on SDK init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n ping(siteId: string): void {\n fetch(`${this.baseUrl}/ping`, {\n method: 'POST',\n headers: this.headers(),\n body: JSON.stringify({ siteId }),\n keepalive: true,\n }).catch(() => { /* silent */ });\n }\n\n // \u2500\u2500\u2500 Fire-and-forget event (uses sendBeacon when available) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n sendEvent(path: string, payload: AdEventPayload | PageviewPayload): void {\n const url = `${this.baseUrl}/events/${path}`;\n const body = JSON.stringify(payload);\n\n if (typeof navigator !== 'undefined' && navigator.sendBeacon) {\n navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));\n } else {\n fetch(url, {\n method: 'POST',\n headers: this.headers(),\n body,\n keepalive: true,\n }).catch(() => { /* silent */ });\n }\n }\n}\n", "const SESSION_KEY = '_adx_sid';\nconst FIRED_KEY = '_adx_fired';\n\n/** Returns (or creates) a session ID stored in sessionStorage. */\nexport function getSessionId(): string {\n try {\n let id = sessionStorage.getItem(SESSION_KEY);\n if (!id) {\n id = typeof crypto !== 'undefined' && crypto.randomUUID\n ? crypto.randomUUID()\n : Math.random().toString(36).slice(2) + Date.now().toString(36);\n sessionStorage.setItem(SESSION_KEY, id);\n }\n return id;\n } catch {\n // sessionStorage blocked (iframe sandbox, private mode, etc.)\n return Math.random().toString(36).slice(2);\n }\n}\n\n/** Returns true if this event key was already fired this session. */\nexport function hasFired(key: string): boolean {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n return key in map;\n } catch {\n return false;\n }\n}\n\n/** Mark an event key as fired. */\nexport function markFired(key: string): void {\n try {\n const map = JSON.parse(sessionStorage.getItem(FIRED_KEY) || '{}') as Record<string, number>;\n map[key] = Date.now();\n sessionStorage.setItem(FIRED_KEY, JSON.stringify(map));\n } catch { /* ignore */ }\n}\n", "import type { DeviceType } from './types.js';\n\nexport function getDevice(): DeviceType {\n if (typeof window === 'undefined') return 'desktop';\n const w = window.innerWidth;\n if (w < 768) return 'mobile';\n if (w < 1024) return 'tablet';\n return 'desktop';\n}\n\n/**\n * Resolve 'auto' to a concrete size based on the container width + device.\n * Falls back to a safe default for each device tier.\n */\nexport function resolveSize(requested: string, container: HTMLElement): string {\n if (requested !== 'auto') return requested;\n\n const device = getDevice();\n const w = container.offsetWidth || window.innerWidth;\n\n if (device === 'mobile') {\n return w >= 320 ? '320x50' : '300x250';\n }\n if (device === 'tablet') {\n return w >= 468 ? '468x60' : '300x250';\n }\n // desktop\n if (w >= 728) return '728x90';\n if (w >= 468) return '468x60';\n return '300x250';\n}\n", "import { hasFired, markFired } from './session.js';\nimport { getDevice } from './device.js';\nimport type { AdXensorApi } from './api.js';\n\n/**\n * Normalise navigator.language (e.g. \"fr-FR\", \"en-US\") to a 2-char\n * ISO 639-1 code (\"fr\", \"en\"). Country is resolved server-side from IP.\n */\nfunction getLang(): string {\n return (navigator.language ?? 'fr').split('-')[0].toLowerCase();\n}\n\nexport class Tracker {\n constructor(\n private readonly api: AdXensorApi,\n private readonly siteId: string,\n private readonly sessionId: string,\n ) {}\n\n // \u2500\u2500\u2500 Base context \u2014 attached to every event \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n private base() {\n return {\n siteId: this.siteId,\n url: location.href,\n device: getDevice(),\n sessionId: this.sessionId,\n language: getLang(), // \"fr\" | \"en\" | \"pt\" | \u2026\n screenWidth: screen.width, // for device analytics breakdowns\n ts: Date.now(),\n };\n }\n\n // \u2500\u2500\u2500 Pageview: once per page load \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n pageview(): void {\n this.api.sendEvent('pageview', {\n ...this.base(),\n referrer: document.referrer,\n });\n }\n\n // \u2500\u2500\u2500 Impression: once per adId \u00D7 session \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n impression(adId: string, slotId: string, impressionToken: string): void {\n const key = `imp:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('impression', {\n ...this.base(),\n adId,\n slotId,\n impressionToken,\n });\n }\n\n // \u2500\u2500\u2500 Click: deduplicated within 500 ms window per ad \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n click(adId: string, slotId: string, clickToken: string): void {\n const tsKey = `clk_ts:${adId}`;\n try {\n const last = parseInt(sessionStorage.getItem(tsKey) || '0', 10);\n if (Date.now() - last < 500) return; // double-click guard\n sessionStorage.setItem(tsKey, String(Date.now()));\n } catch { /* ignore */ }\n this.api.sendEvent('click', {\n ...this.base(),\n adId,\n slotId,\n clickToken,\n });\n }\n\n // \u2500\u2500\u2500 View: once per adId \u00D7 session (50% visible for \u22651 s) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n view(adId: string, slotId: string): void {\n const key = `view:${adId}:${this.sessionId}`;\n if (hasFired(key)) return;\n markFired(key);\n this.api.sendEvent('view', {\n ...this.base(),\n adId,\n slotId,\n });\n }\n}\n", "import type { AdResponse } from './types.js';\n\n/**\n * Inject the ad creative into `container`.\n * - type 'html': raw HTML creative (rich media, animated banners)\n * - type 'image': <a><img></a> wrapped in a click-tracked link\n *\n * The caller handles the click event via addEventListener; the rendered\n * anchor uses href=\"#\" so the tracker can intercept and open the real URL.\n */\nexport function renderAd(container: HTMLElement, ad: AdResponse, clickHref: string): void {\n container.style.overflow = 'hidden';\n container.style.display = 'block';\n container.style.lineHeight = '0'; // removes bottom gap under <img>\n\n if (ad.type === 'html' && ad.htmlContent) {\n container.innerHTML = ad.htmlContent;\n return;\n }\n\n const a = document.createElement('a');\n a.href = clickHref;\n a.target = '_blank';\n a.rel = 'noopener noreferrer';\n a.setAttribute('data-adx-click', '1');\n a.style.display = 'block';\n\n const img = document.createElement('img');\n img.src = ad.imageUrl ?? '';\n img.width = ad.width;\n img.height = ad.height;\n img.alt = ad.altText || '';\n img.style.display = 'block';\n img.style.maxWidth = '100%';\n img.loading = 'lazy';\n\n a.appendChild(img);\n container.innerHTML = '';\n container.appendChild(a);\n}\n", "import type { AdXensorApi } from './api.js';\nimport type { Tracker } from './tracker.js';\nimport type { SlotOptions } from './types.js';\nimport { getDevice, resolveSize } from './device.js';\nimport { renderAd } from './renderer.js';\n\nconst VIEW_THRESHOLD = 0.5; // 50% visible\nconst VIEW_DURATION = 1000; // 1 second held in view = fires view event\n\nexport class AdSlot {\n private readonly slotId: string;\n private observer: IntersectionObserver | null = null;\n private viewTimer: ReturnType<typeof setTimeout> | null = null;\n\n constructor(\n private readonly el: HTMLElement,\n private readonly api: AdXensorApi,\n private readonly tracker: Tracker,\n private readonly siteId: string,\n private readonly sessionId: string,\n private readonly options: SlotOptions = {},\n ) {\n // AdSense-style: data-ad-slot. Falls back to element id or a random id.\n this.slotId =\n options.slotId ??\n el.dataset.adSlot ?? // data-ad-slot=\"banner-top\"\n (el.id || `adx-${Math.random().toString(36).slice(2, 9)}`);\n }\n\n // \u2500\u2500\u2500 Load entry point \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n load(lazy = true): void {\n if (this.el.dataset.adxState) return; // idempotent \u2014 already in-flight or done\n\n if (lazy && typeof IntersectionObserver !== 'undefined') {\n this.watchForEntry();\n } else {\n void this.fetch();\n }\n }\n\n // \u2500\u2500\u2500 Lazy entry: wait until element is near the viewport \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchForEntry(): void {\n const io = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n io.disconnect();\n void this.fetch();\n }\n },\n { rootMargin: '200px', threshold: 0 },\n );\n io.observe(this.el);\n }\n\n // \u2500\u2500\u2500 Fetch ad from API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private async fetch(): Promise<void> {\n if (this.el.dataset.adxState) return; // double-check after async gap\n this.setState('loading');\n\n try {\n // AdSense-style: data-ad-format. Falls back to options.format then 'auto'.\n const size = resolveSize(\n this.options.format ?? this.el.dataset.adFormat ?? 'auto',\n this.el,\n );\n const [w, h] = size.split('x').map(Number);\n\n // Reserve exact dimensions to prevent layout shift (CLS)\n if (w && h) {\n this.el.style.width = `${w}px`;\n this.el.style.maxWidth = '100%';\n this.el.style.minHeight = `${h}px`;\n }\n\n const ad = await this.api.serveAd({\n siteId: this.siteId,\n slotId: this.slotId,\n size,\n device: getDevice(),\n url: location.href,\n referrer: document.referrer,\n // Normalise \"fr-FR\" \u2192 \"fr\"; server resolves country from IP separately\n language: (navigator.language ?? 'fr').split('-')[0].toLowerCase(),\n screenWidth: screen.width,\n sessionId: this.sessionId,\n });\n\n if (!ad) {\n this.setState('empty');\n this.el.style.display = 'none';\n return;\n }\n\n // ads_core provides the full click URL directly (handles redirect internally)\n renderAd(this.el, ad, ad.clickHref);\n this.setState('filled');\n\n // Click tracking \u2014 passive: true because we never call preventDefault\n this.el.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('[data-adx-click]')) {\n this.tracker.click(ad.adId, this.slotId, ad.clickToken);\n }\n }, { passive: true });\n\n // Impression fires immediately after render (once per adId \u00D7 session)\n this.tracker.impression(ad.adId, this.slotId, ad.impressionToken);\n\n // View fires after \u226550% visible for \u22651s (IAB standard)\n this.watchVisibility(ad.adId, this.slotId);\n\n } catch {\n this.setState('error');\n this.el.style.display = 'none';\n }\n }\n\n // \u2500\u2500\u2500 Viewability (IAB MRC standard) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchVisibility(adId: string, slotId: string): void {\n if (typeof IntersectionObserver === 'undefined') {\n this.tracker.view(adId, slotId); // fallback: fire immediately\n return;\n }\n\n this.observer = new IntersectionObserver(\n ([entry]) => {\n if (!entry) return;\n\n if (entry.intersectionRatio >= VIEW_THRESHOLD) {\n if (this.viewTimer === null) {\n this.viewTimer = setTimeout(() => {\n this.tracker.view(adId, slotId);\n this.disconnect(); // stop observing once view is counted\n }, VIEW_DURATION);\n }\n } else {\n // Left viewport before 1s \u2014 reset timer\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n },\n { threshold: [VIEW_THRESHOLD] },\n );\n\n this.observer.observe(this.el);\n }\n\n // \u2500\u2500\u2500 State machine \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private setState(state: 'loading' | 'filled' | 'empty' | 'error'): void {\n this.el.dataset.adxState = state;\n }\n\n // \u2500\u2500\u2500 Cleanup \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n disconnect(): void {\n this.observer?.disconnect();\n this.observer = null;\n if (this.viewTimer !== null) {\n clearTimeout(this.viewTimer);\n this.viewTimer = null;\n }\n }\n}\n", "import { AdXensorApi } from './api.js';\nimport { Tracker } from './tracker.js';\nimport { AdSlot } from './AdSlot.js';\nimport { getSessionId } from './session.js';\nimport type { AdXensorConfig, SlotOptions } from './types.js';\n\n/** ads_core public base URL \u2014 must include /v1 suffix */\nconst DEFAULT_API_URL = 'https://core.adxensor.com/v1';\n\n/** Selector for unfilled <ins class=\"adxensor\"> slots */\nconst INS_SELECTOR = 'ins.adxensor:not([data-adx-state])';\n\nexport class AdXensor {\n /** One global instance per page (like AdSense). */\n private static instance: AdXensor | null = null;\n\n private readonly api: AdXensorApi;\n private readonly tracker: Tracker;\n private readonly sessionId: string;\n private readonly config: AdXensorConfig;\n private initialized = false;\n private domObserver: MutationObserver | null = null;\n\n constructor(config: AdXensorConfig) {\n this.config = config;\n this.sessionId = getSessionId();\n this.api = new AdXensorApi(\n config.apiUrl ?? DEFAULT_API_URL,\n config.apiKey,\n );\n this.tracker = new Tracker(this.api, config.siteId, this.sessionId);\n }\n\n // \u2500\u2500\u2500 Singleton helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n static getInstance(config: AdXensorConfig): AdXensor {\n if (!AdXensor.instance) AdXensor.instance = new AdXensor(config);\n return AdXensor.instance;\n }\n\n static reset(): void {\n AdXensor.instance?.destroy();\n AdXensor.instance = null;\n }\n\n // \u2500\u2500\u2500 Public API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n /**\n * Idempotent init \u2014 safe to call multiple times.\n * Fires pageview, fills existing slots, watches DOM for new ones.\n */\n init(): this {\n if (this.initialized) return this;\n this.initialized = true;\n this.tracker.pageview();\n this.api.ping(this.config.siteId);\n this.fillAll();\n this.watchDom();\n return this;\n }\n\n /** Fill every unfilled <ins class=\"adxensor\"> currently in the DOM. */\n fillAll(): void {\n document.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n }\n\n /**\n * AdSense-style push \u2014 fills the next unfilled slot in DOM order.\n * Use alongside <script>(window.adxensor = window.adxensor || []).push({})</script>\n */\n push(options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(INS_SELECTOR);\n if (el) this.fill(el, options);\n }\n\n /**\n * Fill a specific element.\n * No-op if the element is already loading/filled/empty/error (idempotent).\n */\n fill(el: HTMLElement, options: SlotOptions = {}): void {\n if (el.dataset.adxState) return; // state machine guard\n\n // Enforce display:block \u2014 required like AdSense\n if (el.style.display === 'none') el.style.display = 'block';\n\n const slot = new AdSlot(\n el,\n this.api,\n this.tracker,\n this.config.siteId,\n this.sessionId,\n options,\n );\n\n const lazy = options.lazy ?? this.config.lazyLoad ?? true;\n slot.load(lazy);\n }\n\n /**\n * Programmatic slot fill by CSS selector.\n * Logs a warning in debug mode if the element is not found.\n */\n defineSlot(selector: string, options: SlotOptions = {}): void {\n const el = document.querySelector<HTMLElement>(selector);\n if (!el) {\n if (this.config.debug) console.warn(`[AdXensor] defineSlot: \"${selector}\" not found`);\n return;\n }\n this.fill(el, options);\n }\n\n // \u2500\u2500\u2500 DOM watcher (SPA support) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n private watchDom(): void {\n if (typeof MutationObserver === 'undefined') return;\n\n this.domObserver = new MutationObserver((mutations) => {\n for (const { addedNodes } of mutations) {\n addedNodes.forEach((node) => {\n if (!(node instanceof HTMLElement)) return;\n if (node.matches('ins.adxensor') && !node.dataset.adxState) {\n this.fill(node);\n }\n node.querySelectorAll<HTMLElement>(INS_SELECTOR).forEach((el) => this.fill(el));\n });\n }\n });\n\n this.domObserver.observe(document.body, { childList: true, subtree: true });\n }\n\n destroy(): void {\n this.domObserver?.disconnect();\n this.domObserver = null;\n this.initialized = false;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,5 +6,6 @@ export declare class AdXensorApi {
6
6
  apiKey?: string | undefined);
7
7
  private headers;
8
8
  serveAd(req: ServeRequest): Promise<AdResponse | null>;
9
+ ping(siteId: string): void;
9
10
  sendEvent(path: string, payload: AdEventPayload | PageviewPayload): void;
10
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adxensor/publisher-sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "AdXensor publisher SDK — serve ads, track impressions, clicks, views and pageviews directly from any website or web app.",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -53,6 +53,7 @@ export class AdXensor {
53
53
  if (this.initialized) return this;
54
54
  this.initialized = true;
55
55
  this.tracker.pageview();
56
+ this.api.ping(this.config.siteId);
56
57
  this.fillAll();
57
58
  this.watchDom();
58
59
  return this;
package/src/core/api.ts CHANGED
@@ -61,6 +61,16 @@ export class AdXensorApi {
61
61
  }
62
62
  }
63
63
 
64
+ // ─── Integration ping — called once on SDK init ───────────────────────────
65
+ ping(siteId: string): void {
66
+ fetch(`${this.baseUrl}/ping`, {
67
+ method: 'POST',
68
+ headers: this.headers(),
69
+ body: JSON.stringify({ siteId }),
70
+ keepalive: true,
71
+ }).catch(() => { /* silent */ });
72
+ }
73
+
64
74
  // ─── Fire-and-forget event (uses sendBeacon when available) ───────────────
65
75
  sendEvent(path: string, payload: AdEventPayload | PageviewPayload): void {
66
76
  const url = `${this.baseUrl}/events/${path}`;