@hientran0208/ads-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/README.md +53 -0
- package/dist/ads-client-BVlLFVQU.d.cts +352 -0
- package/dist/ads-client-BVlLFVQU.d.ts +352 -0
- package/dist/ads-sa.global.js +3 -0
- package/dist/ads-sa.global.js.map +1 -0
- package/dist/chunk-GA4VEFSM.js +900 -0
- package/dist/chunk-GA4VEFSM.js.map +1 -0
- package/dist/index.cjs +1012 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +256 -0
- package/dist/index.d.ts +256 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +960 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +50 -0
- package/dist/react.d.ts +50 -0
- package/dist/react.js +63 -0
- package/dist/react.js.map +1 -0
- package/package.json +79 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { P as ProviderHandle, E as EngineEvent, a as EventBus, C as ConsentGate, V as VendorConsentId, T as TagInjector, R as RenderProvider, b as TagVendor, A as AdsClient, c as PlacementSource } from './ads-client-BVlLFVQU.js';
|
|
2
|
+
export { d as AdapterSlotRegistry, e as AdsClientConfig, f as AdsEngine, g as AdsEngineDeps, h as AssembledEngine, B as BOOT_SPINE, i as BootStep, j as ConsentSnapshot, k as EngineEventType, I as InjectOptions, L as LazyObserver, l as ProviderLifecycleEvent, m as ProviderRegistry, n as RefreshOptions, o as RequestOptions, p as VendorContext, q as VendorKind, W as WireOptions, r as assembleEngine, s as assembleEngineForPlacement, t as createAdapterSlotRegistry, u as createAdsClient, v as createAdsEngine, w as createAllowAllConsentGate, x as createEventBus, y as createLazyObserver, z as createProviderRegistry, D as createTagInjector } from './ads-client-BVlLFVQU.js';
|
|
3
|
+
import { SlotFormat, PlanSlot, ConsentModeSignals, ConsentConfig, Manifest, PlanContext, DynamicParams } from '@hientran0208/ads-manifest';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Capability lookup table: how each format is requested. Pure routing — it asks the
|
|
7
|
+
* registry to resolve and classifies the format; it NEVER calls googletag.
|
|
8
|
+
*
|
|
9
|
+
* - display/native → div path, eager/lazy, rides the batched request.
|
|
10
|
+
* - anchor/side-rail/oop → no-div path, no sizes; ride the batch.
|
|
11
|
+
* - interstitial → no-div, on-demand display, NOT in the eager batch, never refresh.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface DispatchTraits {
|
|
15
|
+
/** Has a div in the DOM (display/native) vs out-of-page. */
|
|
16
|
+
usesDiv: boolean;
|
|
17
|
+
/** Eligible for the batched eager request (Single Request Architecture). */
|
|
18
|
+
ridesEagerBatch: boolean;
|
|
19
|
+
/** On-demand display only (interstitial). */
|
|
20
|
+
onDemandOnly: boolean;
|
|
21
|
+
/** May be refreshed in-page. */
|
|
22
|
+
refreshable: boolean;
|
|
23
|
+
}
|
|
24
|
+
declare function dispatchTraits(format: SlotFormat): DispatchTraits;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Epoch-keyed slot lifecycle state machine. The epoch key prevents a stale render
|
|
28
|
+
* callback (fired after an SPA nav) from reporting to the wrong/new slot
|
|
29
|
+
* (sdk-sample high-risk). Illegal transitions are rejected.
|
|
30
|
+
*
|
|
31
|
+
* registered → gated → requested → rendered → (refreshed → requested) | destroyed
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
type SlotState = "registered" | "gated" | "requested" | "rendered" | "destroyed";
|
|
35
|
+
interface SlotRecord {
|
|
36
|
+
slot: PlanSlot;
|
|
37
|
+
handle: ProviderHandle | null;
|
|
38
|
+
state: SlotState;
|
|
39
|
+
/** Monotonic epoch; bumped on new page view. Stale callbacks are ignored. */
|
|
40
|
+
epoch: number;
|
|
41
|
+
}
|
|
42
|
+
interface SlotLifecycle {
|
|
43
|
+
register(slot: PlanSlot, epoch: number): SlotRecord;
|
|
44
|
+
get(uid: string): SlotRecord | undefined;
|
|
45
|
+
all(): SlotRecord[];
|
|
46
|
+
/** Attempt a transition; returns false (no-op) if illegal. */
|
|
47
|
+
transition(uid: string, to: SlotState): boolean;
|
|
48
|
+
attachHandle(uid: string, handle: ProviderHandle | null): void;
|
|
49
|
+
/** True if the uid exists AND its epoch matches (callback not stale). */
|
|
50
|
+
isCurrent(uid: string, epoch: number): boolean;
|
|
51
|
+
destroyAll(): void;
|
|
52
|
+
}
|
|
53
|
+
declare function createSlotLifecycle(): SlotLifecycle;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Telemetry seam — subscribes to the event bus and forwards normalized events to a
|
|
57
|
+
* sink. No-op in v1 (the sink does nothing); the seam exists so a reporting backend
|
|
58
|
+
* (P3) can subscribe without engine changes.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
type TelemetrySink = (ev: EngineEvent) => void;
|
|
62
|
+
/** Wire a sink to the bus. Returns an unsubscribe for all event types. */
|
|
63
|
+
declare function attachTelemetry(bus: EventBus, sink?: TelemetrySink): () => void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Isolates ALL `gtag('consent', …)` interaction (Google Consent Mode v2). Two jobs:
|
|
67
|
+
* 1. `setDefaults` — conservative defaults set SYNCHRONOUSLY before any vendor or
|
|
68
|
+
* gpt.js load. No CMP read required, so the Approach C fast-path never stalls on
|
|
69
|
+
* an async/absent `__tcfapi` (red-team #4).
|
|
70
|
+
* 2. `update` — called asynchronously once the CMP signal is read.
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
type GtagFn = (...args: unknown[]) => void;
|
|
74
|
+
interface GtagHost {
|
|
75
|
+
dataLayer?: unknown[];
|
|
76
|
+
gtag?: GtagFn;
|
|
77
|
+
}
|
|
78
|
+
interface ConsentMode {
|
|
79
|
+
setDefaults(signals: ConsentModeSignals): void;
|
|
80
|
+
update(signals: ConsentModeSignals): void;
|
|
81
|
+
defaultsSet(): boolean;
|
|
82
|
+
}
|
|
83
|
+
declare function createConsentMode(host?: GtagHost): ConsentMode;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* THE deliverable (red-team #3): the explicit IAB TCF v2.2 purpose →
|
|
87
|
+
* Consent-Mode-v2 signal mapping. TCF purposes do NOT map 1:1 to the four signals.
|
|
88
|
+
* This table is spec-driven and CMP-agnostic — built to the standard, smoke-tested
|
|
89
|
+
* against Cookiebot in P8, with the `__tcfapi` stub authoritative for acceptance.
|
|
90
|
+
*
|
|
91
|
+
* TCF v2.2 purposes (subset relevant to Google Consent Mode):
|
|
92
|
+
* P1 Store and/or access information on a device → storage
|
|
93
|
+
* P2 Use limited data to select advertising
|
|
94
|
+
* P3 Create profiles for personalised advertising → ad personalization
|
|
95
|
+
* P4 Use profiles to select personalised advertising → ad personalization
|
|
96
|
+
* P7 Measure advertising performance → ad user data (measurement)
|
|
97
|
+
* P8 Measure content performance → analytics
|
|
98
|
+
*
|
|
99
|
+
* Google Consent Mode v2 signals:
|
|
100
|
+
* ad_storage ← P1 (device storage for ads)
|
|
101
|
+
* analytics_storage ← P1 (device storage for analytics)
|
|
102
|
+
* ad_user_data ← P1 AND P7 (sending user data for ad measurement)
|
|
103
|
+
* ad_personalization ← P3 AND P4 (personalised advertising)
|
|
104
|
+
*/
|
|
105
|
+
|
|
106
|
+
/** Per-purpose consent booleans as exposed by `__tcfapi` (`tcData.purpose.consents`). */
|
|
107
|
+
type PurposeConsents = Record<number, boolean>;
|
|
108
|
+
/**
|
|
109
|
+
* Map TCF purpose consents to the four Consent Mode v2 signals. Conservative:
|
|
110
|
+
* an absent/false purpose yields `denied`.
|
|
111
|
+
*/
|
|
112
|
+
declare function purposesToSignals(consents: PurposeConsents): ConsentModeSignals;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Isolates ALL `__tcfapi` interaction (IAB TCF). Mockable. Subscribes via
|
|
116
|
+
* `addEventListener`, surfaces purpose consents, and falls back to conservative
|
|
117
|
+
* deny if the CMP never responds within the timeout. v1 reads the boolean purpose
|
|
118
|
+
* signals `__tcfapi` exposes — NO raw TC-string parsing (staged to P3).
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/** Minimal shape of the TCF `tcData` payload we consume. */
|
|
122
|
+
interface TcData {
|
|
123
|
+
eventStatus?: "tcloaded" | "cmpuishown" | "useractioncomplete" | string;
|
|
124
|
+
gdprApplies?: boolean;
|
|
125
|
+
purpose?: {
|
|
126
|
+
consents?: PurposeConsents;
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
type TcfApi = (command: "addEventListener" | "removeEventListener" | "getTCData", version: number, callback: (tcData: TcData, success: boolean) => void, listenerId?: number) => void;
|
|
130
|
+
interface TcfHost {
|
|
131
|
+
__tcfapi?: TcfApi;
|
|
132
|
+
}
|
|
133
|
+
interface TcfBridge {
|
|
134
|
+
/** Subscribe to CMP consent changes. Fires for each `useractioncomplete`/`tcloaded`. */
|
|
135
|
+
onConsent(cb: (consents: PurposeConsents, applies: boolean) => void): void;
|
|
136
|
+
/** True if a `__tcfapi` is present on the page. */
|
|
137
|
+
present(): boolean;
|
|
138
|
+
}
|
|
139
|
+
interface TcfBridgeOptions {
|
|
140
|
+
/** Ms to wait for the first CMP event before invoking the timeout fallback. */
|
|
141
|
+
timeoutMs?: number;
|
|
142
|
+
/** Called if the CMP never responds (keep conservative deny). */
|
|
143
|
+
onTimeout?: () => void;
|
|
144
|
+
setTimeoutFn?: typeof setTimeout;
|
|
145
|
+
}
|
|
146
|
+
declare function createTcfBridge(host?: TcfHost, opts?: TcfBridgeOptions): TcfBridge;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The real `ConsentGate` (P2 contract). Orchestrates: synchronous conservative
|
|
150
|
+
* Consent Mode defaults → async CMP read via the TCF bridge → `gtag consent update`
|
|
151
|
+
* → per-vendor decision via the matrix → notify subscribers so the engine arms
|
|
152
|
+
* previously-gated vendors AND re-requests already-defined GPT slots.
|
|
153
|
+
*
|
|
154
|
+
* Require client CMP — no bundled banner (locked decision; P3 seam).
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
interface ConsentGateDeps {
|
|
158
|
+
config?: ConsentConfig;
|
|
159
|
+
bus?: EventBus;
|
|
160
|
+
consentMode?: ConsentMode;
|
|
161
|
+
tcfBridge?: TcfBridge;
|
|
162
|
+
}
|
|
163
|
+
declare function createConsentGate(deps?: ConsentGateDeps): ConsentGate;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Pure signals → per-vendor allow/deny decision. Unit-tested without any globals.
|
|
167
|
+
* One snapshot gates BOTH paths: GPT (render) and GA4/Pixel (tag) all derive from
|
|
168
|
+
* the same Consent Mode signals.
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* v1 decision rules (conservative):
|
|
173
|
+
* - gpt → needs `ad_storage` (serve personalised/standard ads).
|
|
174
|
+
* - ga4 → needs `analytics_storage`.
|
|
175
|
+
* - meta-pixel → needs `ad_storage` (sets ad cookies / user data).
|
|
176
|
+
*
|
|
177
|
+
* Restricted/limited-ads mode (serving non-personalised under denied storage) is a
|
|
178
|
+
* P3 refinement; v1 gates the request outright when storage is denied.
|
|
179
|
+
*/
|
|
180
|
+
declare function vendorAllowed(vendor: VendorConsentId, signals: ConsentModeSignals): boolean;
|
|
181
|
+
/** Build a per-vendor decision map for the known v1 vendors. */
|
|
182
|
+
declare function vendorDecisionMap(signals: ConsentModeSignals): Record<"gpt" | "ga4" | "meta-pixel", boolean>;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* GPT render-path provider — implements the neutral `RenderProvider` contract. Every
|
|
186
|
+
* googletag call goes through `cmd.push` (gpt.js may not be loaded yet). Slots are
|
|
187
|
+
* null-guarded. PPID is set before the request; targeting/privacy via the Config API.
|
|
188
|
+
* gpt.js itself loads through the single TagInjector (allowlisted host).
|
|
189
|
+
*/
|
|
190
|
+
|
|
191
|
+
interface GptProviderDeps {
|
|
192
|
+
bus: EventBus;
|
|
193
|
+
injector?: TagInjector;
|
|
194
|
+
/** Allowlisted hosts (from plan.allowedTagHosts) used to load gpt.js. */
|
|
195
|
+
allowedHosts?: string[];
|
|
196
|
+
win?: Window;
|
|
197
|
+
}
|
|
198
|
+
declare function createGptProvider(deps: GptProviderDeps): RenderProvider;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* GA4 tag-path vendor (`kind:'analytics'`). Injects gtag.js via the single
|
|
202
|
+
* TagInjector (allowlisted host) and calls `gtag('config', id)`. Consent Mode
|
|
203
|
+
* defaults are set by Phase 4 BEFORE this loads (post-boot timing) — GA4 honors
|
|
204
|
+
* them; we never fire before defaults exist.
|
|
205
|
+
*
|
|
206
|
+
* (Dir named `tag-path/` rather than `vendor/` per the repo's scout-ignore filter;
|
|
207
|
+
* role is identical — the tag-path vendors from phase-03.)
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
declare function createGa4Vendor(win?: Window): TagVendor;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Meta Pixel tag-path vendor (`kind:'analytics'`). Injects fbevents.js via the
|
|
214
|
+
* single TagInjector (allowlisted host), `fbq('init', pixelId)` + `fbq('track', …)`.
|
|
215
|
+
* Each track emits a NORMALIZED event on the engine bus so a future CAPI forwarder
|
|
216
|
+
* can subscribe (brainstorm §5 seam) — client-side Pixel only captures ~50-60%
|
|
217
|
+
* under ATT; CAPI is P3-scope, no rework needed here.
|
|
218
|
+
*
|
|
219
|
+
* (Dir named `tag-path/` rather than `vendor/` per the repo's scout-ignore filter.)
|
|
220
|
+
*/
|
|
221
|
+
|
|
222
|
+
declare function createMetaPixelVendor(win?: Window): TagVendor;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Script-tag (IIFE) surface. Exposes `window.AdsSDK` with a `cmd.push(...)`
|
|
226
|
+
* stub-queue: calls made before boot are queued, then drained in order once the
|
|
227
|
+
* client initializes. Auto-boot reads an inlined manifest + `data-*` intent markup.
|
|
228
|
+
*/
|
|
229
|
+
|
|
230
|
+
interface AdsSdkInitConfig {
|
|
231
|
+
/** Static fast-path: inlined manifest. */
|
|
232
|
+
manifest?: Manifest;
|
|
233
|
+
/** Approach-C path: fetch the plan from the public `/placement` endpoint. */
|
|
234
|
+
placement?: PlacementSource;
|
|
235
|
+
consent?: ConsentConfig;
|
|
236
|
+
context?: Partial<PlanContext>;
|
|
237
|
+
}
|
|
238
|
+
interface AdsSdkGlobal {
|
|
239
|
+
cmd: {
|
|
240
|
+
push: (fn: (client: AdsClient) => void) => void;
|
|
241
|
+
};
|
|
242
|
+
init(config: AdsSdkInitConfig): Promise<void>;
|
|
243
|
+
client?: AdsClient;
|
|
244
|
+
}
|
|
245
|
+
/** Parse `data-*` intent attributes from the booting <script> element. */
|
|
246
|
+
declare function parseDataIntent(el: Element | null): {
|
|
247
|
+
dynamic: DynamicParams;
|
|
248
|
+
pageUrl?: string;
|
|
249
|
+
};
|
|
250
|
+
/**
|
|
251
|
+
* Install `window.AdsSDK`. Idempotent — a second install reuses the existing global
|
|
252
|
+
* (the engine's own page guard still prevents a second boot).
|
|
253
|
+
*/
|
|
254
|
+
declare function installAdsSdk(win?: Window): AdsSdkGlobal;
|
|
255
|
+
|
|
256
|
+
export { AdsClient, type AdsSdkGlobal, type AdsSdkInitConfig, ConsentGate, type ConsentGateDeps, type ConsentMode, type DispatchTraits, EngineEvent, EventBus, type GptProviderDeps, PlacementSource, ProviderHandle, type PurposeConsents, RenderProvider, type SlotLifecycle, type SlotRecord, type SlotState, TagInjector, TagVendor, type TcData, type TcfApi, type TcfBridge, type TelemetrySink, VendorConsentId, attachTelemetry, createConsentGate, createConsentMode, createGa4Vendor, createGptProvider, createMetaPixelVendor, createSlotLifecycle, createTcfBridge, dispatchTraits, installAdsSdk, parseDataIntent, purposesToSignals, vendorAllowed, vendorDecisionMap };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createAdsClient } from './chunk-GA4VEFSM.js';
|
|
2
|
+
export { BOOT_SPINE, assembleEngine, assembleEngineForPlacement, createAdapterSlotRegistry, createAdsClient, createAdsEngine, createAllowAllConsentGate, createConsentGate, createConsentMode, createEventBus, createGa4Vendor, createGptProvider, createLazyObserver, createMetaPixelVendor, createProviderRegistry, createSlotLifecycle, createTagInjector, createTcfBridge, dispatchTraits, purposesToSignals, vendorAllowed, vendorDecisionMap } from './chunk-GA4VEFSM.js';
|
|
3
|
+
|
|
4
|
+
// src/core/telemetry-sink.ts
|
|
5
|
+
var noopSink = () => {
|
|
6
|
+
};
|
|
7
|
+
function attachTelemetry(bus, sink = noopSink) {
|
|
8
|
+
const types = [
|
|
9
|
+
"slot:registered",
|
|
10
|
+
"slot:requested",
|
|
11
|
+
"slot:rendered",
|
|
12
|
+
"slot:empty",
|
|
13
|
+
"slot:destroyed",
|
|
14
|
+
"consent:changed",
|
|
15
|
+
"tag:injected",
|
|
16
|
+
"vendor:event",
|
|
17
|
+
"error",
|
|
18
|
+
"warning"
|
|
19
|
+
];
|
|
20
|
+
const unsubs = types.map((t) => bus.on(t, (ev) => sink(ev)));
|
|
21
|
+
return () => unsubs.forEach((u) => u());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/wrappers/iife-boot.ts
|
|
25
|
+
function parseDataIntent(el) {
|
|
26
|
+
const dynamic = {};
|
|
27
|
+
if (!el) return { dynamic };
|
|
28
|
+
const get = (name) => el.getAttribute(name);
|
|
29
|
+
const targeting = get("data-targeting");
|
|
30
|
+
if (targeting) {
|
|
31
|
+
try {
|
|
32
|
+
dynamic.targeting = JSON.parse(targeting);
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const ppid = get("data-ppid");
|
|
37
|
+
if (ppid) dynamic.ppid = ppid;
|
|
38
|
+
const pageUrl = get("data-page-url");
|
|
39
|
+
if (pageUrl) dynamic.pageUrl = pageUrl;
|
|
40
|
+
const privacy = get("data-privacy");
|
|
41
|
+
if (privacy) {
|
|
42
|
+
try {
|
|
43
|
+
dynamic.privacy = JSON.parse(privacy);
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const cc = get("data-change-correlator");
|
|
48
|
+
if (cc != null) dynamic.changeCorrelator = cc === "true";
|
|
49
|
+
return { dynamic, ...pageUrl ? { pageUrl } : {} };
|
|
50
|
+
}
|
|
51
|
+
function installAdsSdk(win = window) {
|
|
52
|
+
const existing = win.AdsSDK;
|
|
53
|
+
if (existing && existing.client) return existing;
|
|
54
|
+
const preStubbed = existing?.cmd;
|
|
55
|
+
const queued = Array.isArray(preStubbed) ? [...preStubbed] : [];
|
|
56
|
+
const api = {
|
|
57
|
+
cmd: {
|
|
58
|
+
push(fn) {
|
|
59
|
+
if (api.client) fn(api.client);
|
|
60
|
+
else queued.push(fn);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async init(config) {
|
|
64
|
+
const scriptEl = win.document?.currentScript ?? win.document?.querySelector("script[data-ads-sdk]");
|
|
65
|
+
const intent = parseDataIntent(scriptEl);
|
|
66
|
+
const context = {
|
|
67
|
+
...config.context ?? {},
|
|
68
|
+
dynamic: { ...config.context?.dynamic ?? {}, ...intent.dynamic }
|
|
69
|
+
};
|
|
70
|
+
const client = createAdsClient({
|
|
71
|
+
...config.manifest !== void 0 ? { manifest: config.manifest } : {},
|
|
72
|
+
...config.placement ? { placement: config.placement } : {},
|
|
73
|
+
...config.consent ? { consent: config.consent } : {},
|
|
74
|
+
context,
|
|
75
|
+
wire: { win }
|
|
76
|
+
});
|
|
77
|
+
await client.init();
|
|
78
|
+
api.client = client;
|
|
79
|
+
for (const fn of queued.splice(0)) fn(client);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
win.AdsSDK = api;
|
|
83
|
+
return api;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { attachTelemetry, installAdsSdk, parseDataIntent };
|
|
87
|
+
//# sourceMappingURL=index.js.map
|
|
88
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/telemetry-sink.ts","../src/wrappers/iife-boot.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Telemetry seam — subscribes to the event bus and forwards normalized events to a\n * sink. No-op in v1 (the sink does nothing); the seam exists so a reporting backend\n * (P3) can subscribe without engine changes.\n */\n\nimport type { EngineEvent, EventBus } from \"./event-bus.js\";\n\nexport type TelemetrySink = (ev: EngineEvent) => void;\n\nconst noopSink: TelemetrySink = () => {};\n\n/** Wire a sink to the bus. Returns an unsubscribe for all event types. */\nexport function attachTelemetry(bus: EventBus, sink: TelemetrySink = noopSink): () => void {\n const types: EngineEvent[\"type\"][] = [\n \"slot:registered\",\n \"slot:requested\",\n \"slot:rendered\",\n \"slot:empty\",\n \"slot:destroyed\",\n \"consent:changed\",\n \"tag:injected\",\n \"vendor:event\",\n \"error\",\n \"warning\",\n ];\n const unsubs = types.map((t) => bus.on(t, (ev) => sink(ev as EngineEvent)));\n return () => unsubs.forEach((u) => u());\n}\n","/**\n * Script-tag (IIFE) surface. Exposes `window.AdsSDK` with a `cmd.push(...)`\n * stub-queue: calls made before boot are queued, then drained in order once the\n * client initializes. Auto-boot reads an inlined manifest + `data-*` intent markup.\n */\n\nimport type { ConsentConfig, DynamicParams, Manifest, PlanContext } from \"@hientran0208/ads-manifest\";\nimport { createAdsClient, type AdsClient, type PlacementSource } from \"./ads-client.js\";\n\nexport interface AdsSdkInitConfig {\n /** Static fast-path: inlined manifest. */\n manifest?: Manifest;\n /** Approach-C path: fetch the plan from the public `/placement` endpoint. */\n placement?: PlacementSource;\n consent?: ConsentConfig;\n context?: Partial<PlanContext>;\n}\n\nexport interface AdsSdkGlobal {\n cmd: { push: (fn: (client: AdsClient) => void) => void };\n init(config: AdsSdkInitConfig): Promise<void>;\n client?: AdsClient;\n}\n\n/** Parse `data-*` intent attributes from the booting <script> element. */\nexport function parseDataIntent(el: Element | null): { dynamic: DynamicParams; pageUrl?: string } {\n const dynamic: DynamicParams = {};\n if (!el) return { dynamic };\n const get = (name: string) => el.getAttribute(name);\n\n const targeting = get(\"data-targeting\");\n if (targeting) {\n try {\n dynamic.targeting = JSON.parse(targeting);\n } catch {\n /* loose: ignore malformed targeting */\n }\n }\n const ppid = get(\"data-ppid\");\n if (ppid) dynamic.ppid = ppid;\n const pageUrl = get(\"data-page-url\");\n if (pageUrl) dynamic.pageUrl = pageUrl;\n const privacy = get(\"data-privacy\");\n if (privacy) {\n try {\n dynamic.privacy = JSON.parse(privacy);\n } catch {\n /* ignore */\n }\n }\n const cc = get(\"data-change-correlator\");\n if (cc != null) dynamic.changeCorrelator = cc === \"true\";\n\n return { dynamic, ...(pageUrl ? { pageUrl } : {}) };\n}\n\n/**\n * Install `window.AdsSDK`. Idempotent — a second install reuses the existing global\n * (the engine's own page guard still prevents a second boot).\n */\nexport function installAdsSdk(win: Window = window): AdsSdkGlobal {\n const existing = (win as unknown as { AdsSDK?: AdsSdkGlobal | { cmd?: unknown } }).AdsSDK;\n if (existing && (existing as AdsSdkGlobal).client) return existing as AdsSdkGlobal;\n\n // A page may pre-stub `window.AdsSDK = { cmd: [] }` (array) and push commands\n // before this IIFE loads; capture them to drain after init, in order.\n const preStubbed = existing?.cmd;\n const queued: Array<(client: AdsClient) => void> = Array.isArray(preStubbed)\n ? [...(preStubbed as Array<(c: AdsClient) => void>)]\n : [];\n\n const api: AdsSdkGlobal = {\n cmd: {\n push(fn) {\n if (api.client) fn(api.client);\n else queued.push(fn);\n },\n },\n async init(config) {\n const scriptEl = win.document?.currentScript ?? win.document?.querySelector(\"script[data-ads-sdk]\");\n const intent = parseDataIntent(scriptEl);\n const context: Partial<PlanContext> = {\n ...(config.context ?? {}),\n dynamic: { ...(config.context?.dynamic ?? {}), ...intent.dynamic },\n };\n const client = createAdsClient({\n ...(config.manifest !== undefined ? { manifest: config.manifest } : {}),\n ...(config.placement ? { placement: config.placement } : {}),\n ...(config.consent ? { consent: config.consent } : {}),\n context,\n wire: { win },\n });\n await client.init();\n api.client = client;\n // Drain any pre-init queued commands in order.\n for (const fn of queued.splice(0)) fn(client);\n },\n };\n\n (win as unknown as { AdsSDK?: AdsSdkGlobal }).AdsSDK = api;\n return api;\n}\n"]}
|