@optable/web-sdk 0.44.6 → 0.48.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 +62 -19
- package/browser/dist/sdk.js +1 -1
- package/lib/dist/addons/gpt.js +35 -12
- package/lib/dist/addons/prebid/analytics.d.ts +106 -0
- package/lib/dist/addons/prebid/analytics.js +474 -0
- package/lib/dist/addons/prototypes/analytics.d.ts +1 -0
- package/lib/dist/addons/prototypes/analytics.js +19 -6
- package/lib/dist/addons/try-identify.js +1 -1
- package/lib/dist/build.json +1 -1
- package/lib/dist/config.d.ts +4 -1
- package/lib/dist/core/context.d.ts +32 -0
- package/lib/dist/core/context.js +146 -0
- package/lib/dist/edge/profile.d.ts +1 -1
- package/lib/dist/edge/profile.js +2 -4
- package/lib/dist/edge/targeting.js +3 -0
- package/lib/dist/edge/witness.d.ts +5 -2
- package/lib/dist/edge/witness.js +4 -1
- package/lib/dist/sdk.d.ts +7 -2
- package/lib/dist/sdk.js +17 -5
- package/package.json +9 -6
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type OptableSDK from "../../sdk";
|
|
2
|
+
declare global {
|
|
3
|
+
interface Window {
|
|
4
|
+
pbjs?: any;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
interface OptablePrebidAnalyticsConfig {
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
analytics?: boolean;
|
|
10
|
+
tenant?: string;
|
|
11
|
+
bidWinTimeout?: number;
|
|
12
|
+
samplingVolume?: "session" | "event";
|
|
13
|
+
samplingSeed?: string;
|
|
14
|
+
samplingRate?: number;
|
|
15
|
+
samplingRateFn?: () => boolean;
|
|
16
|
+
}
|
|
17
|
+
declare class OptablePrebidAnalytics {
|
|
18
|
+
private readonly optableInstance;
|
|
19
|
+
private config;
|
|
20
|
+
readonly isInitialized: boolean;
|
|
21
|
+
private readonly labelStyle;
|
|
22
|
+
private readonly maxAuctionDataSize;
|
|
23
|
+
private auctions;
|
|
24
|
+
private prebidInstance;
|
|
25
|
+
/**
|
|
26
|
+
* Create a new OptablePrebidAnalytics instance.
|
|
27
|
+
* @param optableInstance - An initialized Optable SDK instance that exposes a `witness()` method.
|
|
28
|
+
* @param config - Optional configuration for sampling, debug and analytics behavior.
|
|
29
|
+
*/
|
|
30
|
+
constructor(optableInstance: OptableSDK, config?: OptablePrebidAnalyticsConfig);
|
|
31
|
+
/**
|
|
32
|
+
* Log messages to the console when debugging is enabled.
|
|
33
|
+
* @param args - Values to log.
|
|
34
|
+
* @returns void
|
|
35
|
+
*/
|
|
36
|
+
log(...args: unknown[]): void;
|
|
37
|
+
/**
|
|
38
|
+
* Determine whether the current event/session should be sampled according to
|
|
39
|
+
* the configured sampling rate, seed or function.
|
|
40
|
+
* @returns true if the event should be sampled and analytics calls may proceed.
|
|
41
|
+
*/
|
|
42
|
+
shouldSample(): boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Send an event to the Witness API when analytics are enabled and sampling passes.
|
|
45
|
+
* @param eventName - The name of the event to send (e.g. "optable.prebid.auction").
|
|
46
|
+
* @param properties - An object of event properties to include in the payload.
|
|
47
|
+
* @returns A small result object indicating whether the call was disabled or sent.
|
|
48
|
+
*/
|
|
49
|
+
sendToWitnessAPI(eventName: string, properties?: Record<string, any>): Promise<{
|
|
50
|
+
disabled: boolean;
|
|
51
|
+
eventName: string;
|
|
52
|
+
properties: Record<string, any>;
|
|
53
|
+
}>;
|
|
54
|
+
/**
|
|
55
|
+
* Attach listeners to a Prebid.js instance and process any missed events.
|
|
56
|
+
* This will replay past `auctionEnd` and `bidWon` events and then register live handlers.
|
|
57
|
+
* @param pbjs - The Prebid.js global instance (or equivalent) to hook into.
|
|
58
|
+
* @returns void
|
|
59
|
+
*/
|
|
60
|
+
setHooks(pbjs: any): void;
|
|
61
|
+
/**
|
|
62
|
+
* Hook into Prebid.js by attaching event hooks either immediately or by
|
|
63
|
+
* queueing callbacks when `pbjs.onEvent` is not available yet.
|
|
64
|
+
* @param prebidInstance - Optional Prebid.js instance to use (defaults to `window.pbjs`).
|
|
65
|
+
* @returns true when a hook has been registered, false when Prebid is not present.
|
|
66
|
+
*/
|
|
67
|
+
hookIntoPrebid(prebidInstance?: any): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Process a Prebid `auctionEnd` event: build an internal representation of
|
|
70
|
+
* requests, merge in received bids and schedule a delayed Witness API call
|
|
71
|
+
* (to allow `bidWon` to be received) or mark as missed.
|
|
72
|
+
* @param event - The raw Prebid auctionEnd event object.
|
|
73
|
+
* @param missed - True when the event was previously emitted (missed replay).
|
|
74
|
+
* @returns void
|
|
75
|
+
*/
|
|
76
|
+
trackAuctionEnd(event: any, missed?: boolean): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Handle a Prebid `bidWon` event by finalizing the matching auction, clearing
|
|
79
|
+
* the pending timeout and sending the combined payload to Witness.
|
|
80
|
+
* @param event - The raw Prebid bidWon event object.
|
|
81
|
+
* @param missed - True when the event was previously emitted (missed replay).
|
|
82
|
+
* @returns void
|
|
83
|
+
*/
|
|
84
|
+
trackBidWon(event: any, missed?: boolean): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Clean up old auctions to prevent memory leaks.
|
|
87
|
+
* Removes the oldest auction when the internal store grows past the configured size.
|
|
88
|
+
* @returns void
|
|
89
|
+
*/
|
|
90
|
+
cleanupOldAuctions(): void;
|
|
91
|
+
/**
|
|
92
|
+
* Clear all stored analytics data (useful for tests).
|
|
93
|
+
* @returns void
|
|
94
|
+
*/
|
|
95
|
+
clearData(): void;
|
|
96
|
+
/**
|
|
97
|
+
* Convert internal auction state and optional bidWon event into a Witness payload.
|
|
98
|
+
* This collects matcher/source metadata, bid counts and optional custom analytics.
|
|
99
|
+
* @param auctionEndEvent - The `auctionEnd` event object from Prebid.js.
|
|
100
|
+
* @param bidWonEvent - Optional `bidWon` event when a winning bid exists.
|
|
101
|
+
* @param missed - True when the original events were already emitted (replayed).
|
|
102
|
+
* @returns A payload object compatible with the Witness API.
|
|
103
|
+
*/
|
|
104
|
+
toWitness(auctionEndEvent: any, bidWonEvent: any | null, missed?: boolean): Promise<Record<string, any>>;
|
|
105
|
+
}
|
|
106
|
+
export default OptablePrebidAnalytics;
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import * as Bowser from "bowser";
|
|
11
|
+
const STATUS = {
|
|
12
|
+
REQUESTED: "REQUESTED",
|
|
13
|
+
RECEIVED: "RECEIVED",
|
|
14
|
+
NO_BID: "NO_BID",
|
|
15
|
+
TIMEOUT: "TIMEOUT",
|
|
16
|
+
};
|
|
17
|
+
const SESSION_SAMPLE_KEY = "optable:prebid:analytics:sample-number";
|
|
18
|
+
class OptablePrebidAnalytics {
|
|
19
|
+
/**
|
|
20
|
+
* Create a new OptablePrebidAnalytics instance.
|
|
21
|
+
* @param optableInstance - An initialized Optable SDK instance that exposes a `witness()` method.
|
|
22
|
+
* @param config - Optional configuration for sampling, debug and analytics behavior.
|
|
23
|
+
*/
|
|
24
|
+
constructor(optableInstance, config = { samplingRate: 1, samplingVolume: "event", bidWinTimeout: 10000 }) {
|
|
25
|
+
var _a, _b, _c, _d;
|
|
26
|
+
this.optableInstance = optableInstance;
|
|
27
|
+
this.config = config;
|
|
28
|
+
this.isInitialized = false;
|
|
29
|
+
this.labelStyle = "color: white; background-color: #9198dc; padding: 2px 4px; border-radius: 2px;";
|
|
30
|
+
this.maxAuctionDataSize = 50;
|
|
31
|
+
this.auctions = new Map();
|
|
32
|
+
if (!optableInstance || typeof optableInstance.witness !== "function") {
|
|
33
|
+
throw new Error("OptablePrebidAnalytics requires a valid optable instance with witness() method");
|
|
34
|
+
}
|
|
35
|
+
this.config.debug = (_a = config.debug) !== null && _a !== void 0 ? _a : false;
|
|
36
|
+
this.config.bidWinTimeout = (_b = config.bidWinTimeout) !== null && _b !== void 0 ? _b : 10000;
|
|
37
|
+
this.config.samplingRate = (_c = config.samplingRate) !== null && _c !== void 0 ? _c : 1;
|
|
38
|
+
this.config.samplingVolume = (_d = config.samplingVolume) !== null && _d !== void 0 ? _d : "event";
|
|
39
|
+
if (this.config.samplingVolume === "session") {
|
|
40
|
+
sessionStorage.setItem(SESSION_SAMPLE_KEY, Math.random().toFixed(2));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
sessionStorage.removeItem(SESSION_SAMPLE_KEY);
|
|
44
|
+
}
|
|
45
|
+
sessionStorage.optableSessionDepth = (Number(sessionStorage === null || sessionStorage === void 0 ? void 0 : sessionStorage.optableSessionDepth) || 0) + 1;
|
|
46
|
+
this.isInitialized = true;
|
|
47
|
+
// Store auction data
|
|
48
|
+
this.maxAuctionDataSize = 50;
|
|
49
|
+
this.log("OptablePrebidAnalytics initialized");
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Log messages to the console when debugging is enabled.
|
|
53
|
+
* @param args - Values to log.
|
|
54
|
+
* @returns void
|
|
55
|
+
*/
|
|
56
|
+
log(...args) {
|
|
57
|
+
if (this.config.debug) {
|
|
58
|
+
console.log("%cOptable%c [OptablePrebidAnalytics]", this.labelStyle, "color: inherit;", ...args);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Determine whether the current event/session should be sampled according to
|
|
63
|
+
* the configured sampling rate, seed or function.
|
|
64
|
+
* @returns true if the event should be sampled and analytics calls may proceed.
|
|
65
|
+
*/
|
|
66
|
+
shouldSample() {
|
|
67
|
+
if (this.config.samplingRate <= 0)
|
|
68
|
+
return false;
|
|
69
|
+
if (this.config.samplingRate >= 1)
|
|
70
|
+
return true;
|
|
71
|
+
if (this.config.samplingRateFn) {
|
|
72
|
+
return this.config.samplingRateFn();
|
|
73
|
+
}
|
|
74
|
+
if (this.config.samplingVolume === "session") {
|
|
75
|
+
const samplingNumber = Number(sessionStorage.getItem(SESSION_SAMPLE_KEY) || "1");
|
|
76
|
+
return samplingNumber < this.config.samplingRate;
|
|
77
|
+
}
|
|
78
|
+
// Optional: deterministic sampling by seed (e.g., user ID)
|
|
79
|
+
if (this.config.samplingSeed) {
|
|
80
|
+
const hash = [...this.config.samplingSeed].reduce((acc, c) => acc + c.charCodeAt(0), 0);
|
|
81
|
+
const normalized = (hash % 10000) / 10000;
|
|
82
|
+
return normalized < this.config.samplingRate;
|
|
83
|
+
}
|
|
84
|
+
// Random sampling
|
|
85
|
+
return Math.random() < this.config.samplingRate;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Send an event to the Witness API when analytics are enabled and sampling passes.
|
|
89
|
+
* @param eventName - The name of the event to send (e.g. "optable.prebid.auction").
|
|
90
|
+
* @param properties - An object of event properties to include in the payload.
|
|
91
|
+
* @returns A small result object indicating whether the call was disabled or sent.
|
|
92
|
+
*/
|
|
93
|
+
sendToWitnessAPI(eventName_1) {
|
|
94
|
+
return __awaiter(this, arguments, void 0, function* (eventName, properties = {}) {
|
|
95
|
+
if (!this.config.analytics) {
|
|
96
|
+
this.log("Witness API calls disabled - would send:", eventName, properties);
|
|
97
|
+
return { disabled: true, eventName, properties };
|
|
98
|
+
}
|
|
99
|
+
if (!this.shouldSample()) {
|
|
100
|
+
this.log("Event not sampled - skipping Witness API call for:", eventName, properties);
|
|
101
|
+
return { disabled: true, eventName, properties };
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
yield this.optableInstance.witness(eventName, properties);
|
|
105
|
+
this.log("Sending to Witness API:", eventName, properties);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.log("Error sending to Witness API:", eventName, properties, error);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
return { disabled: false, eventName, properties };
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Attach listeners to a Prebid.js instance and process any missed events.
|
|
116
|
+
* This will replay past `auctionEnd` and `bidWon` events and then register live handlers.
|
|
117
|
+
* @param pbjs - The Prebid.js global instance (or equivalent) to hook into.
|
|
118
|
+
* @returns void
|
|
119
|
+
*/
|
|
120
|
+
setHooks(pbjs) {
|
|
121
|
+
this.log("Processing missed auctionEnd");
|
|
122
|
+
pbjs.getEvents().forEach((event) => {
|
|
123
|
+
if (event.eventType === "auctionEnd") {
|
|
124
|
+
this.log("auction missed");
|
|
125
|
+
this.trackAuctionEnd(event.args, true);
|
|
126
|
+
}
|
|
127
|
+
if (event.eventType === "bidWon") {
|
|
128
|
+
this.log("bid won missed");
|
|
129
|
+
this.trackBidWon(event.args, true);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
this.log("Hooking into Prebid.js events");
|
|
133
|
+
pbjs.onEvent("auctionEnd", (event) => {
|
|
134
|
+
this.log("auctionEnd event received");
|
|
135
|
+
this.trackAuctionEnd(event);
|
|
136
|
+
});
|
|
137
|
+
pbjs.onEvent("bidWon", (event) => {
|
|
138
|
+
this.log("bidWon event received");
|
|
139
|
+
this.trackBidWon(event);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Hook into Prebid.js by attaching event hooks either immediately or by
|
|
144
|
+
* queueing callbacks when `pbjs.onEvent` is not available yet.
|
|
145
|
+
* @param prebidInstance - Optional Prebid.js instance to use (defaults to `window.pbjs`).
|
|
146
|
+
* @returns true when a hook has been registered, false when Prebid is not present.
|
|
147
|
+
*/
|
|
148
|
+
hookIntoPrebid(prebidInstance = window.pbjs) {
|
|
149
|
+
const pbjs = prebidInstance;
|
|
150
|
+
this.prebidInstance = pbjs;
|
|
151
|
+
if (typeof pbjs === "undefined") {
|
|
152
|
+
this.log("Prebid.js not found");
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
if (typeof pbjs.onEvent !== "function") {
|
|
156
|
+
pbjs.que = pbjs.que || [];
|
|
157
|
+
pbjs.que.push(() => this.setHooks(pbjs));
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
this.setHooks(pbjs);
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Process a Prebid `auctionEnd` event: build an internal representation of
|
|
166
|
+
* requests, merge in received bids and schedule a delayed Witness API call
|
|
167
|
+
* (to allow `bidWon` to be received) or mark as missed.
|
|
168
|
+
* @param event - The raw Prebid auctionEnd event object.
|
|
169
|
+
* @param missed - True when the event was previously emitted (missed replay).
|
|
170
|
+
* @returns void
|
|
171
|
+
*/
|
|
172
|
+
trackAuctionEnd(event_1) {
|
|
173
|
+
return __awaiter(this, arguments, void 0, function* (event, missed = false) {
|
|
174
|
+
const { auctionId, timeout, bidderRequests = [], bidsReceived = [], noBids = [], timeoutBids = [] } = event;
|
|
175
|
+
this.log(`Processing auction ${auctionId} with ${bidderRequests.length} bidder requests`);
|
|
176
|
+
window.optable = window.optable || {};
|
|
177
|
+
window.optable.pageAuctionsCount = (Number(window.optable.pageAuctionsCount) || 0) + 1;
|
|
178
|
+
// Build auction object with bidder requests and EID flags
|
|
179
|
+
const auction = {
|
|
180
|
+
auctionId,
|
|
181
|
+
timeout,
|
|
182
|
+
bidderRequests: bidderRequests.map((br) => {
|
|
183
|
+
var _a, _b, _c, _d;
|
|
184
|
+
const { bidderCode, bidderRequestId, bids = [] } = br;
|
|
185
|
+
const domain = (_b = (_a = br.ortb2.site) === null || _a === void 0 ? void 0 : _a.domain) !== null && _b !== void 0 ? _b : "unknown";
|
|
186
|
+
const eids = (_d = (_c = br.ortb2.user) === null || _c === void 0 ? void 0 : _c.eids) !== null && _d !== void 0 ? _d : [];
|
|
187
|
+
// Optable EIDs
|
|
188
|
+
const optableEIDS = eids.filter((e) => e.inserter === "optable.co");
|
|
189
|
+
const optableMatchers = [...new Set(optableEIDS.map((e) => e.matcher).filter(Boolean))];
|
|
190
|
+
const optableSources = [...new Set(optableEIDS.map((e) => e.source).filter(Boolean))];
|
|
191
|
+
return {
|
|
192
|
+
bidderCode,
|
|
193
|
+
bidderRequestId,
|
|
194
|
+
domain,
|
|
195
|
+
hasOEids: optableEIDS.length > 0,
|
|
196
|
+
optableMatchers,
|
|
197
|
+
optableSources,
|
|
198
|
+
status: STATUS.REQUESTED,
|
|
199
|
+
bids: bids.map((b) => {
|
|
200
|
+
var _a, _b, _c, _d;
|
|
201
|
+
return ({
|
|
202
|
+
bidId: b.bidId,
|
|
203
|
+
bidderRequestId,
|
|
204
|
+
adUnitCode: b.adUnitCode,
|
|
205
|
+
adUnitId: b.adUnitId,
|
|
206
|
+
transactionId: b.transactionId,
|
|
207
|
+
src: b.src,
|
|
208
|
+
floorMin: (_a = b.floorData) === null || _a === void 0 ? void 0 : _a.floorMin,
|
|
209
|
+
splitTestAssignment: (_d = (_c = (_b = b.ortb2Imp) === null || _b === void 0 ? void 0 : _b.ext) === null || _c === void 0 ? void 0 : _c.optable) === null || _d === void 0 ? void 0 : _d.splitTestAssignment,
|
|
210
|
+
status: STATUS.REQUESTED,
|
|
211
|
+
});
|
|
212
|
+
}),
|
|
213
|
+
};
|
|
214
|
+
}),
|
|
215
|
+
};
|
|
216
|
+
// Build lookup tables for 1:many relationship
|
|
217
|
+
const requestIndex = {};
|
|
218
|
+
const bidIndex = {};
|
|
219
|
+
const bidToRequest = {};
|
|
220
|
+
auction.bidderRequests.forEach((br) => {
|
|
221
|
+
requestIndex[br.bidderRequestId] = br;
|
|
222
|
+
br.bids.forEach((bid) => {
|
|
223
|
+
bidIndex[bid.bidId] = bid;
|
|
224
|
+
bidToRequest[bid.bidId] = br;
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
// Merge in bidsReceived → update individual bids as RECEIVED
|
|
228
|
+
bidsReceived.forEach((b) => {
|
|
229
|
+
var _a, _b, _c, _d, _e, _f;
|
|
230
|
+
const bidId = b.requestId;
|
|
231
|
+
const br = bidToRequest[bidId];
|
|
232
|
+
if (!br) {
|
|
233
|
+
this.log(`No bidderRequest found for bidId=${bidId}`);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
// Find the specific bid to update
|
|
237
|
+
let bidObj = bidIndex[bidId];
|
|
238
|
+
if (bidObj) {
|
|
239
|
+
// Update existing bid
|
|
240
|
+
Object.assign(bidObj, {
|
|
241
|
+
status: STATUS.RECEIVED,
|
|
242
|
+
cpm: b.cpm,
|
|
243
|
+
size: `${b.width}x${b.height}`,
|
|
244
|
+
currency: b.currency,
|
|
245
|
+
splitTestAssignment: (_c = (_b = (_a = b.ortb2Imp) === null || _a === void 0 ? void 0 : _a.ext) === null || _b === void 0 ? void 0 : _b.optable) === null || _c === void 0 ? void 0 : _c.splitTestAssignment,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Create new bid object for this response
|
|
250
|
+
bidObj = {
|
|
251
|
+
bidId,
|
|
252
|
+
bidderRequestId: br.bidderRequestId,
|
|
253
|
+
adUnitCode: b.adUnitCode,
|
|
254
|
+
adUnitId: b.adUnitId,
|
|
255
|
+
transactionId: b.transactionId,
|
|
256
|
+
src: b.src,
|
|
257
|
+
cpm: b.cpm,
|
|
258
|
+
size: `${b.width}x${b.height}`,
|
|
259
|
+
currency: b.currency,
|
|
260
|
+
status: STATUS.RECEIVED,
|
|
261
|
+
splitTestAssignment: (_f = (_e = (_d = b.ortb2Imp) === null || _d === void 0 ? void 0 : _d.ext) === null || _e === void 0 ? void 0 : _e.optable) === null || _f === void 0 ? void 0 : _f.splitTestAssignment,
|
|
262
|
+
};
|
|
263
|
+
br.bids.push(bidObj);
|
|
264
|
+
bidIndex[bidId] = bidObj;
|
|
265
|
+
bidToRequest[bidId] = br;
|
|
266
|
+
}
|
|
267
|
+
// Update bidder request status to RECEIVED if any bid was received
|
|
268
|
+
if (br.status === STATUS.REQUESTED) {
|
|
269
|
+
br.status = STATUS.RECEIVED;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
// Handle noBids → mark the entire request as NO_BID
|
|
273
|
+
noBids.forEach((nb) => {
|
|
274
|
+
const br = requestIndex[nb.bidderRequestId];
|
|
275
|
+
if (!br)
|
|
276
|
+
return;
|
|
277
|
+
br.status = STATUS.NO_BID;
|
|
278
|
+
// Mark all bids in this request as NO_BID
|
|
279
|
+
br.bids.forEach((bid) => {
|
|
280
|
+
bid.status = STATUS.NO_BID;
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
// Handle timeoutBids → mark the entire request as TIMEOUT
|
|
284
|
+
timeoutBids.forEach((tb) => {
|
|
285
|
+
const br = requestIndex[tb.bidderRequestId];
|
|
286
|
+
if (!br)
|
|
287
|
+
return;
|
|
288
|
+
br.status = STATUS.TIMEOUT;
|
|
289
|
+
// Mark all bids in this request as TIMEOUT
|
|
290
|
+
br.bids.forEach((bid) => {
|
|
291
|
+
bid.status = STATUS.TIMEOUT;
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
const createdAt = new Date();
|
|
295
|
+
const auctionEndTimeoutId = setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const payload = yield this.toWitness(event, null, missed);
|
|
297
|
+
payload["auctionEndAt"] = createdAt.toISOString();
|
|
298
|
+
payload["bidWonAt"] = null;
|
|
299
|
+
payload["optableLoaded"] = !missed;
|
|
300
|
+
this.sendToWitnessAPI("optable.prebid.auction", payload);
|
|
301
|
+
}), this.config.bidWinTimeout);
|
|
302
|
+
// Store the auction data
|
|
303
|
+
this.auctions.set(auctionId, { auctionEnd: event, createdAt, missed, auctionEndTimeoutId });
|
|
304
|
+
// Clean up old auctions
|
|
305
|
+
this.cleanupOldAuctions();
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Handle a Prebid `bidWon` event by finalizing the matching auction, clearing
|
|
310
|
+
* the pending timeout and sending the combined payload to Witness.
|
|
311
|
+
* @param event - The raw Prebid bidWon event object.
|
|
312
|
+
* @param missed - True when the event was previously emitted (missed replay).
|
|
313
|
+
* @returns void
|
|
314
|
+
*/
|
|
315
|
+
trackBidWon(event_1) {
|
|
316
|
+
return __awaiter(this, arguments, void 0, function* (event, missed = false) {
|
|
317
|
+
const filteredEvent = {
|
|
318
|
+
auctionId: event.auctionId,
|
|
319
|
+
bidderCode: event.bidderCode,
|
|
320
|
+
bidId: event.requestId,
|
|
321
|
+
tenant: this.config.tenant,
|
|
322
|
+
missed,
|
|
323
|
+
};
|
|
324
|
+
this.log("bidWon filtered event", filteredEvent);
|
|
325
|
+
const auction = this.auctions.get(event.auctionId);
|
|
326
|
+
if (!auction) {
|
|
327
|
+
this.log("Missing 'auctionEnd' event. Skipping.");
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (auction.auctionEndTimeoutId) {
|
|
331
|
+
clearTimeout(auction.auctionEndTimeoutId);
|
|
332
|
+
}
|
|
333
|
+
const payload = yield this.toWitness(auction.auctionEnd, event, missed);
|
|
334
|
+
payload["auctionEndAt"] = auction.createdAt.toISOString();
|
|
335
|
+
payload["bidWonAt"] = new Date().toISOString();
|
|
336
|
+
payload["optableLoaded"] = !missed;
|
|
337
|
+
this.sendToWitnessAPI("optable.prebid.auction", payload);
|
|
338
|
+
this.auctions.delete(event.auctionId);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Clean up old auctions to prevent memory leaks.
|
|
343
|
+
* Removes the oldest auction when the internal store grows past the configured size.
|
|
344
|
+
* @returns void
|
|
345
|
+
*/
|
|
346
|
+
cleanupOldAuctions() {
|
|
347
|
+
const auctionIds = [...this.auctions.keys()];
|
|
348
|
+
if (auctionIds.length > this.maxAuctionDataSize) {
|
|
349
|
+
const oldestAuctionId = auctionIds[0];
|
|
350
|
+
this.auctions.delete(oldestAuctionId);
|
|
351
|
+
this.log(`Cleaned up old auction: ${oldestAuctionId}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Clear all stored analytics data (useful for tests).
|
|
356
|
+
* @returns void
|
|
357
|
+
*/
|
|
358
|
+
clearData() {
|
|
359
|
+
this.auctions.clear();
|
|
360
|
+
this.log("All analytics data cleared");
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Convert internal auction state and optional bidWon event into a Witness payload.
|
|
364
|
+
* This collects matcher/source metadata, bid counts and optional custom analytics.
|
|
365
|
+
* @param auctionEndEvent - The `auctionEnd` event object from Prebid.js.
|
|
366
|
+
* @param bidWonEvent - Optional `bidWon` event when a winning bid exists.
|
|
367
|
+
* @param missed - True when the original events were already emitted (replayed).
|
|
368
|
+
* @returns A payload object compatible with the Witness API.
|
|
369
|
+
*/
|
|
370
|
+
toWitness(auctionEndEvent_1, bidWonEvent_1) {
|
|
371
|
+
return __awaiter(this, arguments, void 0, function* (auctionEndEvent, bidWonEvent, missed = false) {
|
|
372
|
+
var _a, _b, _c;
|
|
373
|
+
const { auctionId, bidderRequests = [], bidsReceived = [], noBids = [], timeoutBids = [] } = auctionEndEvent;
|
|
374
|
+
const oMatchersSet = new Set();
|
|
375
|
+
const oSourcesSet = new Set();
|
|
376
|
+
let adUnitCode = "unknown";
|
|
377
|
+
let totalBids = 0;
|
|
378
|
+
let device = null;
|
|
379
|
+
// Process bidder requests
|
|
380
|
+
const requests = bidderRequests.map((br) => {
|
|
381
|
+
var _a, _b, _c, _d;
|
|
382
|
+
const { bidderCode, bidderRequestId, bids = [] } = br;
|
|
383
|
+
const domain = (_b = (_a = br.ortb2.site) === null || _a === void 0 ? void 0 : _a.domain) !== null && _b !== void 0 ? _b : "unknown";
|
|
384
|
+
const eids = (_d = (_c = br.ortb2.user) === null || _c === void 0 ? void 0 : _c.eids) !== null && _d !== void 0 ? _d : [];
|
|
385
|
+
// Optable EIDs
|
|
386
|
+
const optableEIDS = eids.filter((e) => e.inserter === "optable.co");
|
|
387
|
+
const optableMatchers = [...new Set(optableEIDS.map((e) => e.matcher).filter(Boolean))];
|
|
388
|
+
const optableSources = [...new Set(optableEIDS.map((e) => e.source).filter(Boolean))];
|
|
389
|
+
device = br.ortb2.device;
|
|
390
|
+
return {
|
|
391
|
+
bidderCode,
|
|
392
|
+
bidderRequestId,
|
|
393
|
+
domain,
|
|
394
|
+
optableTargetingDone: optableEIDS.length > 0,
|
|
395
|
+
optableMatchers,
|
|
396
|
+
optableSources,
|
|
397
|
+
status: STATUS.REQUESTED,
|
|
398
|
+
bids: bids.map((b) => {
|
|
399
|
+
var _a, _b, _c, _d;
|
|
400
|
+
return ({
|
|
401
|
+
bidId: b.bidId,
|
|
402
|
+
bidderRequestId,
|
|
403
|
+
adUnitCode: b.adUnitCode,
|
|
404
|
+
adUnitId: b.adUnitId,
|
|
405
|
+
transactionId: b.transactionId,
|
|
406
|
+
src: b.src,
|
|
407
|
+
floorMin: (_a = b.floorData) === null || _a === void 0 ? void 0 : _a.floorMin,
|
|
408
|
+
splitTestAssignment: (_d = (_c = (_b = b.ortb2Imp) === null || _b === void 0 ? void 0 : _b.ext) === null || _c === void 0 ? void 0 : _c.optable) === null || _d === void 0 ? void 0 : _d.splitTestAssignment,
|
|
409
|
+
status: STATUS.REQUESTED,
|
|
410
|
+
});
|
|
411
|
+
}),
|
|
412
|
+
};
|
|
413
|
+
});
|
|
414
|
+
// Merge splitTestAssignment from bidsReceived into the requests
|
|
415
|
+
const bidsReceivedMap = new Map(bidsReceived.map((b) => [b.requestId, b]));
|
|
416
|
+
requests.forEach((request) => {
|
|
417
|
+
request.bids.forEach((bid) => {
|
|
418
|
+
var _a, _b, _c;
|
|
419
|
+
const bidReceived = bidsReceivedMap.get(bid.bidId);
|
|
420
|
+
if ((_c = (_b = (_a = bidReceived === null || bidReceived === void 0 ? void 0 : bidReceived.ortb2Imp) === null || _a === void 0 ? void 0 : _a.ext) === null || _b === void 0 ? void 0 : _b.optable) === null || _c === void 0 ? void 0 : _c.splitTestAssignment) {
|
|
421
|
+
bid.splitTestAssignment = bidReceived.ortb2Imp.ext.optable.splitTestAssignment;
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
const witnessData = {
|
|
426
|
+
bidderRequests: requests.map((br) => {
|
|
427
|
+
br.optableMatchers.forEach((m) => oMatchersSet.add(m));
|
|
428
|
+
br.optableSources.forEach((s) => oSourcesSet.add(s));
|
|
429
|
+
return br;
|
|
430
|
+
}),
|
|
431
|
+
auctionId,
|
|
432
|
+
adUnitCode,
|
|
433
|
+
totalRequests: bidderRequests.length,
|
|
434
|
+
optableSampling: this.config.samplingRate || 1,
|
|
435
|
+
optableTargetingDone: oMatchersSet.size || oSourcesSet.size,
|
|
436
|
+
optableMatchers: Array.from(oMatchersSet),
|
|
437
|
+
optableSources: Array.from(oSourcesSet),
|
|
438
|
+
bidWon: bidWonEvent
|
|
439
|
+
? {
|
|
440
|
+
message: bidWonEvent.bidderCode +
|
|
441
|
+
" won the ad server auction for ad unit " +
|
|
442
|
+
bidWonEvent.adUnitCode +
|
|
443
|
+
" at " +
|
|
444
|
+
bidWonEvent.cpm +
|
|
445
|
+
" CPM",
|
|
446
|
+
bidderCode: bidWonEvent.bidderCode,
|
|
447
|
+
adUnitCode: bidWonEvent.adUnitCode,
|
|
448
|
+
cpm: bidWonEvent.cpm,
|
|
449
|
+
}
|
|
450
|
+
: null,
|
|
451
|
+
missed,
|
|
452
|
+
url: `${window.location.hostname}${window.location.pathname}`,
|
|
453
|
+
tenant: this.config.tenant,
|
|
454
|
+
// eslint-disable-next-line no-undef
|
|
455
|
+
optableWrapperVersion: SDK_WRAPPER_VERSION || "unknown",
|
|
456
|
+
userAgent: Bowser.parse(window.navigator.userAgent),
|
|
457
|
+
device,
|
|
458
|
+
prebidjsVersion: ((_a = this.prebidInstance) === null || _a === void 0 ? void 0 : _a.version) || "unknown",
|
|
459
|
+
sessionDepth: (sessionStorage === null || sessionStorage === void 0 ? void 0 : sessionStorage.optableSessionDepth) || 1,
|
|
460
|
+
pageAuctionsCount: ((_b = window.optable) === null || _b === void 0 ? void 0 : _b.pageAuctionsCount) || 1,
|
|
461
|
+
};
|
|
462
|
+
// Log summary with bid counts
|
|
463
|
+
this.log(`Auction ${auctionId} processed: ${bidderRequests.length} requests, ${totalBids} total bids, ${bidsReceived.length} received, ${noBids.length} no-bids, ${timeoutBids.length} timeouts`);
|
|
464
|
+
if ((_c = window.optable) === null || _c === void 0 ? void 0 : _c.customAnalytics) {
|
|
465
|
+
yield window.optable.customAnalytics().then((response) => {
|
|
466
|
+
this.log(`Adding custom data to payload ${JSON.stringify(response)}`);
|
|
467
|
+
Object.assign(witnessData, response);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
return witnessData;
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
export default OptablePrebidAnalytics;
|
|
@@ -25,6 +25,7 @@ declare class OptablePrebidAnalytics {
|
|
|
25
25
|
* Hook into Prebid.js events
|
|
26
26
|
*/
|
|
27
27
|
hookIntoPrebid(prebidInstance?: any): boolean;
|
|
28
|
+
prebidInstance: any;
|
|
28
29
|
trackAuctionEnd(event: any, missed: any): Promise<void>;
|
|
29
30
|
trackBidWon(event: any, missed: any): void;
|
|
30
31
|
/**
|