@adxensor/publisher-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ import { hasFired, markFired } from './session.js';
2
+ import { getDevice } from './device.js';
3
+ import type { AdXensorApi } from './api.js';
4
+
5
+ /**
6
+ * Normalise navigator.language (e.g. "fr-FR", "en-US") to a 2-char
7
+ * ISO 639-1 code ("fr", "en"). Country is resolved server-side from IP.
8
+ */
9
+ function getLang(): string {
10
+ return (navigator.language ?? 'fr').split('-')[0].toLowerCase();
11
+ }
12
+
13
+ export class Tracker {
14
+ constructor(
15
+ private readonly api: AdXensorApi,
16
+ private readonly siteId: string,
17
+ private readonly sessionId: string,
18
+ ) {}
19
+
20
+ // ─── Base context — attached to every event ────────────────────────────────
21
+ private base() {
22
+ return {
23
+ siteId: this.siteId,
24
+ url: location.href,
25
+ device: getDevice(),
26
+ sessionId: this.sessionId,
27
+ language: getLang(), // "fr" | "en" | "pt" | …
28
+ screenWidth: screen.width, // for device analytics breakdowns
29
+ ts: Date.now(),
30
+ };
31
+ }
32
+
33
+ // ─── Pageview: once per page load ─────────────────────────────────────────
34
+ pageview(): void {
35
+ this.api.sendEvent('pageview', {
36
+ ...this.base(),
37
+ referrer: document.referrer,
38
+ });
39
+ }
40
+
41
+ // ─── Impression: once per adId × session ──────────────────────────────────
42
+ impression(adId: string, slotId: string, impressionToken: string): void {
43
+ const key = `imp:${adId}:${this.sessionId}`;
44
+ if (hasFired(key)) return;
45
+ markFired(key);
46
+ this.api.sendEvent('impression', {
47
+ ...this.base(),
48
+ adId,
49
+ slotId,
50
+ impressionToken,
51
+ });
52
+ }
53
+
54
+ // ─── Click: deduplicated within 500 ms window per ad ─────────────────────
55
+ click(adId: string, slotId: string, clickToken: string): void {
56
+ const tsKey = `clk_ts:${adId}`;
57
+ try {
58
+ const last = parseInt(sessionStorage.getItem(tsKey) || '0', 10);
59
+ if (Date.now() - last < 500) return; // double-click guard
60
+ sessionStorage.setItem(tsKey, String(Date.now()));
61
+ } catch { /* ignore */ }
62
+ this.api.sendEvent('click', {
63
+ ...this.base(),
64
+ adId,
65
+ slotId,
66
+ clickToken,
67
+ });
68
+ }
69
+
70
+ // ─── View: once per adId × session (50% visible for ≥1 s) ────────────────
71
+ view(adId: string, slotId: string): void {
72
+ const key = `view:${adId}:${this.sessionId}`;
73
+ if (hasFired(key)) return;
74
+ markFired(key);
75
+ this.api.sendEvent('view', {
76
+ ...this.base(),
77
+ adId,
78
+ slotId,
79
+ });
80
+ }
81
+ }
@@ -0,0 +1,112 @@
1
+ // ─── Ad formats ───────────────────────────────────────────────────────────────
2
+ export type AdFormat =
3
+ | '728x90' // leaderboard
4
+ | '970x90' // super leaderboard
5
+ | '970x250' // billboard
6
+ | '300x250' // medium rectangle
7
+ | '300x600' // half page
8
+ | '160x600' // wide skyscraper
9
+ | '320x50' // mobile banner
10
+ | '320x100' // large mobile banner
11
+ | '468x60' // full banner
12
+ | 'auto'
13
+ | (string & {}); // allow arbitrary "WxH" strings without losing union hints
14
+
15
+ /** @deprecated Use AdFormat */
16
+ export type AdSize = AdFormat;
17
+
18
+ export type DeviceType = 'mobile' | 'tablet' | 'desktop';
19
+
20
+ /** State machine written on the <ins> element via data-adx-state */
21
+ export type SlotState = 'loading' | 'filled' | 'empty' | 'error';
22
+
23
+ // ─── SDK configuration ────────────────────────────────────────────────────────
24
+ export interface AdXensorConfig {
25
+ /** Publisher site ID — required */
26
+ siteId: string;
27
+ /** Optional API key for authenticated publishers */
28
+ apiKey?: string;
29
+ /** Override the default API base URL */
30
+ apiUrl?: string;
31
+ /** Log debug info to console (default: false) */
32
+ debug?: boolean;
33
+ /** Lazy-load ads when they approach the viewport (default: true) */
34
+ lazyLoad?: boolean;
35
+ }
36
+
37
+ // ─── Per-slot overrides ───────────────────────────────────────────────────────
38
+ export interface SlotOptions {
39
+ /** Named slot identifier (overrides data-ad-slot attribute) */
40
+ slotId?: string;
41
+ /** Ad format / size (overrides data-ad-format attribute) */
42
+ format?: AdFormat;
43
+ /** Override lazy loading for this slot only */
44
+ lazy?: boolean;
45
+ }
46
+
47
+ // ─── Ad serve request ─────────────────────────────────────────────────────────
48
+ export interface ServeRequest {
49
+ siteId: string;
50
+ slotId: string;
51
+ size: string;
52
+ device: DeviceType;
53
+ url: string;
54
+ referrer: string;
55
+ /**
56
+ * BCP-47 tag from navigator.language, normalised to 2-char ISO 639-1.
57
+ * e.g. "fr", "en", "es" — used for language targeting by ads_core.
58
+ */
59
+ language: string;
60
+ screenWidth: number;
61
+ sessionId: string;
62
+ }
63
+
64
+ export interface AdResponse {
65
+ adId: string;
66
+ campaignId: string;
67
+ type: 'image' | 'html';
68
+ imageUrl?: string;
69
+ htmlContent?: string;
70
+ /**
71
+ * Full click URL returned by ads_core (e.g. https://ads.adxensor.com/v1/click/TOKEN).
72
+ * Use this directly as the <a> href — ads_core handles the redirect internally.
73
+ */
74
+ clickHref: string;
75
+ width: number;
76
+ height: number;
77
+ altText: string;
78
+ /** Event ID used for POST /v1/events/impression */
79
+ impressionToken: string;
80
+ /** Event ID used for POST /v1/events/click */
81
+ clickToken: string;
82
+ }
83
+
84
+ // ─── Event payloads ───────────────────────────────────────────────────────────
85
+ /**
86
+ * Base fields sent with EVERY event (impression, click, view, pageview).
87
+ * Country is resolved server-side from IP (geoip-lite) and NOT sent by the SDK.
88
+ */
89
+ interface BaseEventPayload {
90
+ siteId: string;
91
+ sessionId: string;
92
+ url: string;
93
+ device: DeviceType;
94
+ /**
95
+ * Normalised 2-char ISO 639-1 language code from navigator.language.
96
+ * e.g. "fr", "en", "es", "pt"
97
+ */
98
+ language: string;
99
+ screenWidth: number;
100
+ ts: number;
101
+ }
102
+
103
+ export interface PageviewPayload extends BaseEventPayload {
104
+ referrer: string;
105
+ }
106
+
107
+ export interface AdEventPayload extends BaseEventPayload {
108
+ adId: string;
109
+ slotId: string;
110
+ impressionToken?: string;
111
+ clickToken?: string;
112
+ }
package/src/embed.ts ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * CDN entry point — loaded via <script> tag (IIFE bundle).
3
+ *
4
+ * ── Minimal snippet (auto-fill all <ins class="adxensor"> on load) ───────────
5
+ *
6
+ * <script async
7
+ * src="https://cdn.adxensor.com/tag.js"
8
+ * data-ad-client="pub-XXXXXXXX">
9
+ * </script>
10
+ *
11
+ * <ins class="adxensor"
12
+ * style="display:block"
13
+ * data-ad-slot="header-banner"
14
+ * data-ad-format="728x90">
15
+ * </ins>
16
+ *
17
+ * ── AdSense-style push (fill one slot at a time) ─────────────────────────────
18
+ *
19
+ * <ins class="adxensor" style="display:block"
20
+ * data-ad-slot="sidebar" data-ad-format="300x250"></ins>
21
+ * <script>
22
+ * (window.adxensor = window.adxensor || []).push({});
23
+ * </script>
24
+ *
25
+ * ── Pre-load queue (before script is ready) ──────────────────────────────────
26
+ *
27
+ * <script>
28
+ * window.adxensor = window.adxensor || [];
29
+ * window.adxensor.push({});
30
+ * </script>
31
+ */
32
+
33
+ import { AdXensor } from './core/AdXensor.js';
34
+ import type { SlotOptions } from './core/types.js';
35
+
36
+ type QueueEntry = [keyof AdXensor, ...unknown[]] | Record<string, unknown>;
37
+
38
+ // Expose constructor for advanced users
39
+ (window as any).AdXensor = AdXensor;
40
+
41
+ function bootstrap(): void {
42
+ const script =
43
+ (document.currentScript as HTMLScriptElement | null) ??
44
+ document.querySelector<HTMLScriptElement>('script[data-ad-client]');
45
+
46
+ if (!script) {
47
+ console.error('[AdXensor] Cannot find script tag with data-ad-client');
48
+ return;
49
+ }
50
+
51
+ const siteId = script.dataset.adClient;
52
+ if (!siteId) {
53
+ console.error('[AdXensor] data-ad-client is required');
54
+ return;
55
+ }
56
+
57
+ const adx = AdXensor.getInstance({
58
+ siteId,
59
+ apiKey: script.dataset.apiKey,
60
+ apiUrl: script.dataset.apiUrl,
61
+ debug: script.dataset.debug !== undefined,
62
+ lazyLoad: script.dataset.lazyLoad !== 'false', // lazy by default
63
+ });
64
+
65
+ // Drain pre-load queue
66
+ const queue: QueueEntry[] = Array.isArray((window as any).adxensor)
67
+ ? (window as any).adxensor
68
+ : [];
69
+
70
+ for (const entry of queue) {
71
+ if (Array.isArray(entry)) {
72
+ const [method, ...args] = entry;
73
+ if (method === 'push') adx.push(args[0] as SlotOptions ?? {});
74
+ } else {
75
+ // plain push({}) call
76
+ adx.push(entry as SlotOptions);
77
+ }
78
+ }
79
+
80
+ // Replace the array with a live proxy so future .push() calls work
81
+ (window as any).adxensor = {
82
+ push(options: SlotOptions = {}) {
83
+ adx.push(options);
84
+ },
85
+ };
86
+
87
+ adx.init();
88
+
89
+ if (script.dataset.debug !== undefined) (window as any)._adx = adx;
90
+ }
91
+
92
+ if (document.readyState === 'loading') {
93
+ document.addEventListener('DOMContentLoaded', bootstrap);
94
+ } else {
95
+ bootstrap();
96
+ }
package/src/index.ts ADDED
@@ -0,0 +1,24 @@
1
+ // ─── Main class (primary entrypoint) ──────────────────────────────────────────
2
+ export { AdXensor } from './core/AdXensor.js';
3
+
4
+ // ─── Lower-level building blocks (advanced / custom integrations) ─────────────
5
+ export { AdXensorApi } from './core/api.js';
6
+ export { Tracker } from './core/tracker.js';
7
+ export { AdSlot } from './core/AdSlot.js';
8
+ export { renderAd } from './core/renderer.js';
9
+ export { getDevice, resolveSize } from './core/device.js';
10
+ export { getSessionId, hasFired, markFired } from './core/session.js';
11
+
12
+ // ─── Types ────────────────────────────────────────────────────────────────────
13
+ export type {
14
+ AdXensorConfig,
15
+ SlotOptions,
16
+ SlotState,
17
+ AdFormat,
18
+ AdSize, // deprecated alias for AdFormat — kept for backwards compat
19
+ DeviceType,
20
+ AdResponse,
21
+ ServeRequest,
22
+ AdEventPayload,
23
+ PageviewPayload,
24
+ } from './core/types.js';