@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.
- package/dist/cdn/tag.js +1 -0
- package/dist/npm/cjs/index.cjs +507 -0
- package/dist/npm/cjs/index.cjs.map +7 -0
- package/dist/npm/esm/index.js +487 -0
- package/dist/npm/esm/index.js.map +7 -0
- package/dist/npm/types/core/AdSlot.d.ts +21 -0
- package/dist/npm/types/core/AdXensor.d.ts +38 -0
- package/dist/npm/types/core/api.d.ts +10 -0
- package/dist/npm/types/core/device.d.ts +7 -0
- package/dist/npm/types/core/renderer.d.ts +10 -0
- package/dist/npm/types/core/session.d.ts +6 -0
- package/dist/npm/types/core/tracker.d.ts +12 -0
- package/dist/npm/types/core/types.d.ts +87 -0
- package/dist/npm/types/embed.d.ts +32 -0
- package/dist/npm/types/index.d.ts +9 -0
- package/package.json +36 -0
- package/src/core/AdSlot.ts +169 -0
- package/src/core/AdXensor.ts +136 -0
- package/src/core/api.ts +80 -0
- package/src/core/device.ts +31 -0
- package/src/core/renderer.ts +40 -0
- package/src/core/session.ts +38 -0
- package/src/core/tracker.ts +81 -0
- package/src/core/types.ts +112 -0
- package/src/embed.ts +96 -0
- package/src/index.ts +24 -0
|
@@ -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';
|