@bidkernel/analytics 0.16.0 → 0.17.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/index.mjs CHANGED
@@ -511,28 +511,6 @@ var TraceEventBatch = {
511
511
  reader.skip(tag & 7);
512
512
  }
513
513
  return message;
514
- },
515
- create(base) {
516
- return TraceEventBatch.fromPartial(base ?? {});
517
- },
518
- fromPartial(object) {
519
- const message = createBaseTraceEventBatch();
520
- message.propertyId = object.propertyId ?? "";
521
- message.pageviewId = object.pageviewId ?? "";
522
- message.sessionId = object.sessionId ?? "";
523
- message.pageUrl = object.pageUrl ?? "";
524
- message.country = object.country ?? "";
525
- message.region = object.region ?? "";
526
- message.deviceType = object.deviceType ?? "";
527
- message.userId = object.userId ?? "";
528
- message.domain = object.domain ?? "";
529
- message.events = object.events?.map((e) => TraceEvent.fromPartial(e)) || [];
530
- message.browser = object.browser ?? "";
531
- message.deviceManufacturer = object.deviceManufacturer ?? "";
532
- message.deviceModel = object.deviceModel ?? "";
533
- message.city = object.city ?? "";
534
- message.postalCode = object.postalCode ?? "";
535
- return message;
536
514
  }
537
515
  };
538
516
  function createBaseTraceEvent() {
@@ -697,33 +675,6 @@ var TraceEvent = {
697
675
  reader.skip(tag & 7);
698
676
  }
699
677
  return message;
700
- },
701
- create(base) {
702
- return TraceEvent.fromPartial(base ?? {});
703
- },
704
- fromPartial(object) {
705
- const message = createBaseTraceEvent();
706
- message.timestampMs = object.timestampMs ?? 0;
707
- message.type = object.type ?? 0;
708
- message.eventName = object.eventName ?? "";
709
- message.auctionId = object.auctionId ?? "";
710
- message.transactionId = object.transactionId ?? "";
711
- message.adUnitCode = object.adUnitCode ?? "";
712
- message.bid = object.bid !== void 0 && object.bid !== null ? BidTrace.fromPartial(object.bid) : void 0;
713
- message.namespace = object.namespace ?? "";
714
- message.eventId = object.eventId ?? "";
715
- message.metadata = globalThis.Object.entries(object.metadata ?? {}).reduce(
716
- (acc, [key, value]) => {
717
- if (value !== void 0) {
718
- acc[key] = globalThis.String(value);
719
- }
720
- return acc;
721
- },
722
- {}
723
- );
724
- message.viewableDurationMs = object.viewableDurationMs ?? void 0;
725
- message.bidderOutcomes = object.bidderOutcomes?.map((e) => BidderOutcomeEntry.fromPartial(e)) || [];
726
- return message;
727
678
  }
728
679
  };
729
680
  function createBaseTraceEvent_MetadataEntry() {
@@ -767,15 +718,6 @@ var TraceEvent_MetadataEntry = {
767
718
  reader.skip(tag & 7);
768
719
  }
769
720
  return message;
770
- },
771
- create(base) {
772
- return TraceEvent_MetadataEntry.fromPartial(base ?? {});
773
- },
774
- fromPartial(object) {
775
- const message = createBaseTraceEvent_MetadataEntry();
776
- message.key = object.key ?? "";
777
- message.value = object.value ?? "";
778
- return message;
779
721
  }
780
722
  };
781
723
  function createBaseBidderOutcomeEntry() {
@@ -849,18 +791,6 @@ var BidderOutcomeEntry = {
849
791
  reader.skip(tag & 7);
850
792
  }
851
793
  return message;
852
- },
853
- create(base) {
854
- return BidderOutcomeEntry.fromPartial(base ?? {});
855
- },
856
- fromPartial(object) {
857
- const message = createBaseBidderOutcomeEntry();
858
- message.bidder = object.bidder ?? "";
859
- message.adUnitCode = object.adUnitCode ?? "";
860
- message.mediaType = object.mediaType ?? "";
861
- message.outcome = object.outcome ?? 0;
862
- message.latencyMs = object.latencyMs ?? 0;
863
- return message;
864
794
  }
865
795
  };
866
796
  function createBaseBidTrace() {
@@ -995,23 +925,6 @@ var BidTrace = {
995
925
  reader.skip(tag & 7);
996
926
  }
997
927
  return message;
998
- },
999
- create(base) {
1000
- return BidTrace.fromPartial(base ?? {});
1001
- },
1002
- fromPartial(object) {
1003
- const message = createBaseBidTrace();
1004
- message.bidder = object.bidder ?? "";
1005
- message.cpm = object.cpm ?? 0;
1006
- message.currency = object.currency ?? "";
1007
- message.width = object.width ?? 0;
1008
- message.height = object.height ?? 0;
1009
- message.dealId = object.dealId ?? "";
1010
- message.mediaType = object.mediaType ?? "";
1011
- message.latencyMs = object.latencyMs ?? 0;
1012
- message.advertiserDomain = object.advertiserDomain ?? "";
1013
- message.creativeId = object.creativeId ?? "";
1014
- return message;
1015
928
  }
1016
929
  };
1017
930
 
@@ -1125,17 +1038,24 @@ function sameEndpointOrigin(a, b) {
1125
1038
  return false;
1126
1039
  }
1127
1040
  }
1128
- function extendSession() {
1129
- const now = Date.now();
1130
- inMemorySessionTs = now;
1131
- if (!storageAllowed) return;
1041
+ var SESSION_PERSIST_INTERVAL_MS = 60 * 1e3;
1042
+ var persistedSessionTs = 0;
1043
+ function persistSessionTs(now) {
1132
1044
  try {
1133
1045
  if (typeof localStorage !== "undefined") {
1134
1046
  localStorage.setItem(SESSION_TS_KEY, now.toString());
1047
+ persistedSessionTs = now;
1135
1048
  }
1136
1049
  } catch {
1137
1050
  }
1138
1051
  }
1052
+ function extendSession() {
1053
+ const now = Date.now();
1054
+ inMemorySessionTs = now;
1055
+ if (!storageAllowed) return;
1056
+ if (now - persistedSessionTs < SESSION_PERSIST_INTERVAL_MS) return;
1057
+ persistSessionTs(now);
1058
+ }
1139
1059
  function getOrCreateSessionId() {
1140
1060
  const now = Date.now();
1141
1061
  const memoryFresh = !!inMemorySessionId && !!inMemorySessionTs && now - inMemorySessionTs <= THIRTY_MINUTES_MS;
@@ -1145,14 +1065,14 @@ function getOrCreateSessionId() {
1145
1065
  const tsStr = localStorage.getItem(SESSION_TS_KEY);
1146
1066
  const storedTs = tsStr ? parseInt(tsStr, 10) || 0 : 0;
1147
1067
  if (storedId && storedTs && now - storedTs <= THIRTY_MINUTES_MS) {
1148
- localStorage.setItem(SESSION_TS_KEY, now.toString());
1068
+ persistSessionTs(now);
1149
1069
  inMemorySessionId = storedId;
1150
1070
  inMemorySessionTs = now;
1151
1071
  return storedId;
1152
1072
  }
1153
1073
  const newId = memoryFresh ? inMemorySessionId : generateUUID();
1154
1074
  localStorage.setItem(SESSION_KEY, newId);
1155
- localStorage.setItem(SESSION_TS_KEY, now.toString());
1075
+ persistSessionTs(now);
1156
1076
  inMemorySessionId = newId;
1157
1077
  inMemorySessionTs = now;
1158
1078
  return newId;
@@ -1262,537 +1182,281 @@ function parseBidDimensions(bid) {
1262
1182
  height: Number.isFinite(bid?.height) ? bid.height : 0
1263
1183
  };
1264
1184
  }
1265
- var adsLoaderListenerMap = /* @__PURE__ */ new WeakMap();
1266
- function hookAdsManagerLoadedEvent(loadedEventCtor) {
1267
- if (!loadedEventCtor) return;
1268
- try {
1269
- const loadedProto = loadedEventCtor.prototype;
1270
- if (loadedProto && typeof loadedProto.getAdsManager === "function" && !loadedProto.getAdsManager.__bidkernelHooked) {
1271
- const originalGetAdsManager = loadedProto.getAdsManager;
1272
- const hookedGetAdsManager = function(...args) {
1273
- const adsManager = originalGetAdsManager.apply(this, args);
1274
- if (adsManager) {
1275
- for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1276
- try {
1277
- instance.attachImaAdsManager(adsManager);
1278
- } catch {
1279
- }
1280
- }
1281
- }
1282
- return adsManager;
1283
- };
1284
- hookedGetAdsManager.__bidkernelHooked = true;
1285
- loadedProto.getAdsManager = hookedGetAdsManager;
1286
- }
1287
- } catch {
1185
+ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1186
+ static activeInstances = /* @__PURE__ */ new Set();
1187
+ static getActiveInstances() {
1188
+ return _BidkernelPrebidAnalytics.activeInstances;
1288
1189
  }
1289
- }
1290
- function hookAdsManagerPrototype(adsManagerCtorOrProto) {
1291
- if (!adsManagerCtorOrProto) return;
1292
- try {
1293
- const proto = adsManagerCtorOrProto.prototype || adsManagerCtorOrProto;
1294
- if (proto && typeof proto.init === "function" && !proto.init.__bidkernelHooked) {
1295
- const origInit = proto.init;
1296
- const hookedInit = function(...args) {
1297
- for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1298
- try {
1299
- instance.attachImaAdsManager(this);
1300
- } catch {
1301
- }
1302
- }
1303
- return origInit.apply(this, args);
1304
- };
1305
- hookedInit.__bidkernelHooked = true;
1306
- proto.init = hookedInit;
1190
+ /**
1191
+ * Allows or forbids localStorage writes for every instance on the page.
1192
+ * Collection and delivery are unaffected: only the persisted session id,
1193
+ * its timestamp and exit batches are gated.
1194
+ */
1195
+ static setStorageAllowed(allowed) {
1196
+ storageAllowed = allowed;
1197
+ persistedSessionTs = 0;
1198
+ }
1199
+ static isStorageAllowed() {
1200
+ return storageAllowed;
1201
+ }
1202
+ config;
1203
+ queue = [];
1204
+ errorCount = 0;
1205
+ flushTimer = null;
1206
+ boundFlushBeacon;
1207
+ boundVisibilityChange;
1208
+ isEnabled = false;
1209
+ boundPbjsHandlers = [];
1210
+ // Captured at enable()/navigate() so events queued before an SPA route
1211
+ // change flush with the page they occurred on, not the new URL.
1212
+ pageUrl = "";
1213
+ deduper = new PrebidEventDeduper();
1214
+ consecutiveSendFailures = 0;
1215
+ nextSendAllowedAt = 0;
1216
+ replayedEventCount = 0;
1217
+ revenueFlushTimer = null;
1218
+ // localStorage keys of exit batches this instance persisted, so a page that
1219
+ // survives its own pagehide/hidden (bfcache restore, tab re-focus) can
1220
+ // remove them instead of leaving them for a duplicate resend.
1221
+ persistedBatchKeys = [];
1222
+ persistedCleanupTimer = null;
1223
+ // Viewability & refresh tracking
1224
+ intersectionObserver = null;
1225
+ slotViewabilityRecords = /* @__PURE__ */ new Map();
1226
+ elementToSlotId = /* @__PURE__ */ new Map();
1227
+ slotRefreshIndices = /* @__PURE__ */ new Map();
1228
+ slotLastAuctionIds = /* @__PURE__ */ new Map();
1229
+ pendingThresholdListeners = /* @__PURE__ */ new Map();
1230
+ pendingViewableListeners = /* @__PURE__ */ new Map();
1231
+ // Element a subscriber named for a slot (a sticky bar or interstitial that
1232
+ // renders outside the slot element); wins over document.getElementById.
1233
+ slotElements = /* @__PURE__ */ new Map();
1234
+ clickDetachCleanups = /* @__PURE__ */ new Set();
1235
+ lastClickAt = /* @__PURE__ */ new Map();
1236
+ // Impression deduplication per slot and refresh cycle (strictly 1 impression per cycle)
1237
+ slotEmittedImpressionKeys = /* @__PURE__ */ new Set();
1238
+ // Winning bid cache from bidWon to marry with subsequent render triggers (adRenderSucceeded, video, etc.)
1239
+ cachedWinningBids = /* @__PURE__ */ new Map();
1240
+ // Active video player attachment cleanup routines (registered by ./video)
1241
+ videoDetachCleanups = /* @__PURE__ */ new Set();
1242
+ // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1243
+ auctionBidderOutcomes = /* @__PURE__ */ new Map();
1244
+ logger;
1245
+ constructor(config) {
1246
+ if (config.storageAllowed !== void 0) {
1247
+ _BidkernelPrebidAnalytics.setStorageAllowed(config.storageAllowed);
1307
1248
  }
1308
- if (proto && typeof proto.start === "function" && !proto.start.__bidkernelHooked) {
1309
- const origStart = proto.start;
1310
- const hookedStart = function(...args) {
1311
- for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1312
- try {
1313
- instance.attachImaAdsManager(this);
1314
- } catch {
1315
- }
1316
- }
1317
- return origStart.apply(this, args);
1318
- };
1319
- hookedStart.__bidkernelHooked = true;
1320
- proto.start = hookedStart;
1249
+ const requestedEndpoint = config.endpoint || "";
1250
+ const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
1251
+ this.config = {
1252
+ endpoint,
1253
+ propertyId: config.propertyId || "",
1254
+ pageviewId: config.pageviewId || generateUUID(),
1255
+ sessionId: config.sessionId || getOrCreateSessionId(),
1256
+ userId: config.userId || "",
1257
+ deviceType: config.deviceType || "desktop",
1258
+ country: config.country || "",
1259
+ region: config.region || "",
1260
+ auctionEnabled: config.auctionEnabled ?? true,
1261
+ warningsEnabled: config.warningsEnabled ?? true,
1262
+ errorsEnabled: config.errorsEnabled ?? true,
1263
+ viewabilityEnabled: config.viewabilityEnabled ?? true,
1264
+ storageAllowed: config.storageAllowed ?? true,
1265
+ logLevel: config.logLevel || "INFO",
1266
+ pbjsGlobalName: config.pbjsGlobalName || "pbjs",
1267
+ attachPbjsListeners: config.attachPbjsListeners ?? true,
1268
+ revenueFlushDelayMs: Number.isFinite(config.revenueFlushDelayMs) && config.revenueFlushDelayMs >= 0 ? config.revenueFlushDelayMs : REVENUE_FLUSH_DELAY_MS
1269
+ };
1270
+ this.logger = createLogger({
1271
+ prefix: "[bidkernel][prebid-analytics]",
1272
+ logLevel: this.config.logLevel
1273
+ });
1274
+ if (requestedEndpoint && !endpoint) {
1275
+ this.log(
1276
+ "ERROR",
1277
+ `Rejected untrusted analytics endpoint ${requestedEndpoint}: must be an absolute https:// URL without embedded credentials. Events will not be transmitted.`
1278
+ );
1321
1279
  }
1322
- } catch {
1280
+ this.boundFlushBeacon = () => {
1281
+ this.flushAllSlotsTimeInView();
1282
+ this.flushBeacon();
1283
+ };
1284
+ this.boundVisibilityChange = () => this.handleVisibilityChange();
1323
1285
  }
1324
- }
1325
- function hookAdsLoader(adsLoaderCtor) {
1326
- if (!adsLoaderCtor) return;
1327
- try {
1328
- const proto = adsLoaderCtor.prototype;
1329
- if (!proto) return;
1330
- if (typeof proto.addEventListener === "function" && !proto.addEventListener.__bidkernelHooked) {
1331
- const origAddEventListener = proto.addEventListener;
1332
- const hookedAddEventListener = function(type, listener, ...rest) {
1333
- if ((type === "adsManagerLoaded" || type === (this?.AdsManagerLoadedEvent?.Type?.ADS_MANAGER_LOADED || "adsManagerLoaded")) && typeof listener === "function") {
1334
- let wrappedListener = adsLoaderListenerMap.get(listener);
1335
- if (!wrappedListener) {
1336
- wrappedListener = function(event) {
1337
- if (event && typeof event.getAdsManager === "function" && !event.getAdsManager.__bidkernelHooked) {
1338
- const origGetAdsManager = event.getAdsManager;
1339
- event.getAdsManager = function(...args) {
1340
- const adsManager = origGetAdsManager.apply(this, args);
1341
- if (adsManager) {
1342
- for (const sdk of BidkernelPrebidAnalytics.getActiveInstances()) {
1343
- try {
1344
- sdk.attachImaAdsManager(adsManager);
1345
- } catch {
1346
- }
1347
- }
1348
- }
1349
- return adsManager;
1350
- };
1351
- event.getAdsManager.__bidkernelHooked = true;
1352
- }
1353
- return listener.apply(this, arguments);
1354
- };
1355
- adsLoaderListenerMap.set(listener, wrappedListener);
1286
+ enable() {
1287
+ if (this.isEnabled) return;
1288
+ this.isEnabled = true;
1289
+ _BidkernelPrebidAnalytics.activeInstances.add(this);
1290
+ this.logger.setLevel(this.config.logLevel);
1291
+ if (!this.config.endpoint) {
1292
+ this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
1293
+ }
1294
+ if (this.config.viewabilityEnabled) {
1295
+ if (typeof IntersectionObserver !== "undefined") {
1296
+ this.intersectionObserver = new IntersectionObserver(this.handleIntersection.bind(this), {
1297
+ threshold: [0.5]
1298
+ });
1299
+ for (const record of this.slotViewabilityRecords.values()) {
1300
+ if (record.element) {
1301
+ this.intersectionObserver.observe(record.element);
1356
1302
  }
1357
- return origAddEventListener.call(this, type, wrappedListener, ...rest);
1358
1303
  }
1359
- return origAddEventListener.call(this, type, listener, ...rest);
1360
- };
1361
- hookedAddEventListener.__bidkernelHooked = true;
1362
- proto.addEventListener = hookedAddEventListener;
1363
- }
1364
- if (typeof proto.removeEventListener === "function" && !proto.removeEventListener.__bidkernelHooked) {
1365
- const origRemoveEventListener = proto.removeEventListener;
1366
- const hookedRemoveEventListener = function(type, listener, ...rest) {
1367
- const wrapped = typeof listener === "function" ? adsLoaderListenerMap.get(listener) : void 0;
1368
- return origRemoveEventListener.call(this, type, wrapped || listener, ...rest);
1369
- };
1370
- hookedRemoveEventListener.__bidkernelHooked = true;
1371
- proto.removeEventListener = hookedRemoveEventListener;
1304
+ }
1372
1305
  }
1373
- } catch {
1374
- }
1375
- }
1376
- function hookImaPrototype(ima) {
1377
- if (!ima || typeof ima !== "object") return;
1378
- try {
1379
- if (ima.AdsManagerLoadedEvent) {
1380
- hookAdsManagerLoadedEvent(ima.AdsManagerLoadedEvent);
1306
+ if (this.config.attachPbjsListeners) {
1307
+ this.attachPbjs();
1381
1308
  }
1382
- const loadedDesc = Object.getOwnPropertyDescriptor(ima, "AdsManagerLoadedEvent");
1383
- if (!loadedDesc || loadedDesc.configurable) {
1384
- let _loadedEvent = ima.AdsManagerLoadedEvent;
1385
- try {
1386
- Object.defineProperty(ima, "AdsManagerLoadedEvent", {
1387
- configurable: true,
1388
- enumerable: true,
1389
- get() {
1390
- return _loadedEvent;
1391
- },
1392
- set(val) {
1393
- _loadedEvent = val;
1394
- hookAdsManagerLoadedEvent(val);
1395
- }
1396
- });
1397
- } catch {
1309
+ if (typeof window !== "undefined") {
1310
+ if (!this.pageUrl) {
1311
+ try {
1312
+ const u = new URL(window.location.href);
1313
+ this.pageUrl = u.origin + u.pathname;
1314
+ } catch {
1315
+ this.pageUrl = window.location.href;
1316
+ }
1317
+ }
1318
+ window.addEventListener("pagehide", this.boundFlushBeacon);
1319
+ if (typeof document !== "undefined") {
1320
+ document.addEventListener("visibilitychange", this.boundVisibilityChange);
1398
1321
  }
1322
+ this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
1323
+ this.resendPersistedBatches();
1399
1324
  }
1400
- if (ima.AdsLoader) {
1401
- hookAdsLoader(ima.AdsLoader);
1325
+ }
1326
+ /**
1327
+ * Binds the Prebid event handlers on the configured global and replays
1328
+ * pbjs.getEvents() history. enable() calls this when attachPbjsListeners is
1329
+ * true. An integration that loads Prebid lazily constructs the instance with
1330
+ * attachPbjsListeners=false and calls this once pbjs exists; disable() (and
1331
+ * therefore navigate()) drops the handlers, so call it again after either.
1332
+ * Idempotent. Returns true when handlers are bound after the call, false
1333
+ * when the instance is disabled or pbjs.onEvent is not available yet.
1334
+ */
1335
+ attachPbjs() {
1336
+ if (!this.isEnabled) return false;
1337
+ if (this.boundPbjsHandlers.length > 0) return true;
1338
+ const win = typeof window !== "undefined" ? window : {};
1339
+ const pbjs = win[this.config.pbjsGlobalName] || {};
1340
+ if (typeof pbjs.onEvent !== "function") {
1341
+ this.log("WARN", "pbjs.onEvent is not defined. Prebid analytics will not function.");
1342
+ return false;
1402
1343
  }
1403
- const loaderDesc = Object.getOwnPropertyDescriptor(ima, "AdsLoader");
1404
- if (!loaderDesc || loaderDesc.configurable) {
1405
- let _loader = ima.AdsLoader;
1344
+ this.log("DEBUG", "Attaching event listeners to pbjs");
1345
+ const handlerMap = {
1346
+ auctionInit: this.handleAuctionInit.bind(this),
1347
+ auctionEnd: this.handleAuctionEnd.bind(this),
1348
+ bidRequested: this.handleBidRequested.bind(this),
1349
+ bidResponse: this.handleBidResponse.bind(this),
1350
+ bidTimeout: this.handleBidTimeout.bind(this),
1351
+ bidWon: this.handleBidWon.bind(this),
1352
+ noBid: this.handleNoBid.bind(this),
1353
+ adRenderFailed: this.handleAdRenderFailed.bind(this),
1354
+ adRenderSucceeded: this.handleAdRenderSucceeded.bind(this)
1355
+ };
1356
+ for (const [name, fn] of Object.entries(handlerMap)) {
1357
+ pbjs.onEvent(name, fn);
1358
+ this.boundPbjsHandlers.push({ event: name, handler: fn });
1359
+ }
1360
+ if (typeof pbjs.getEvents === "function") {
1406
1361
  try {
1407
- Object.defineProperty(ima, "AdsLoader", {
1408
- configurable: true,
1409
- enumerable: true,
1410
- get() {
1411
- return _loader;
1412
- },
1413
- set(val) {
1414
- _loader = val;
1415
- hookAdsLoader(val);
1362
+ const pastEvents = pbjs.getEvents();
1363
+ if (Array.isArray(pastEvents)) {
1364
+ const newPastEvents = pastEvents.slice(this.replayedEventCount);
1365
+ this.replayedEventCount = pastEvents.length;
1366
+ if (newPastEvents.length > 0) {
1367
+ this.log(
1368
+ "DEBUG",
1369
+ `Replaying ${newPastEvents.length} historical events from pbjs.getEvents()`
1370
+ );
1371
+ for (const ev of newPastEvents) {
1372
+ if (!ev) continue;
1373
+ const eventType = ev.eventType || ev.event || ev.name;
1374
+ const args = ev.args !== void 0 ? ev.args : ev.data !== void 0 ? ev.data : ev;
1375
+ const handler = handlerMap[eventType];
1376
+ if (handler) {
1377
+ handler(args);
1378
+ }
1379
+ }
1416
1380
  }
1417
- });
1418
- } catch {
1381
+ }
1382
+ } catch (e) {
1383
+ this.log("WARN", "Failed to replay historical events from pbjs.getEvents()", e);
1419
1384
  }
1420
1385
  }
1421
- if (ima.AdsManager) {
1422
- hookAdsManagerPrototype(ima.AdsManager);
1386
+ return true;
1387
+ }
1388
+ disable() {
1389
+ if (!this.isEnabled) return;
1390
+ this.flushAllSlotsTimeInView();
1391
+ this.isEnabled = false;
1392
+ _BidkernelPrebidAnalytics.activeInstances.delete(this);
1393
+ for (const record of this.slotViewabilityRecords.values()) {
1394
+ if (record.dwellTimer) {
1395
+ clearTimeout(record.dwellTimer);
1396
+ record.dwellTimer = null;
1397
+ record.dwellStartedAt = null;
1398
+ }
1399
+ for (const l of record.thresholdListeners) {
1400
+ if (l.timer) {
1401
+ clearTimeout(l.timer);
1402
+ l.timer = null;
1403
+ }
1404
+ }
1423
1405
  }
1424
- const mgrDesc = Object.getOwnPropertyDescriptor(ima, "AdsManager");
1425
- if (!mgrDesc || mgrDesc.configurable) {
1426
- let _mgr = ima.AdsManager;
1406
+ if (this.intersectionObserver) {
1407
+ this.intersectionObserver.disconnect();
1408
+ this.intersectionObserver = null;
1409
+ }
1410
+ this.slotViewabilityRecords.clear();
1411
+ this.elementToSlotId.clear();
1412
+ for (const cleanup of this.videoDetachCleanups) {
1427
1413
  try {
1428
- Object.defineProperty(ima, "AdsManager", {
1429
- configurable: true,
1430
- enumerable: true,
1431
- get() {
1432
- return _mgr;
1433
- },
1434
- set(val) {
1435
- _mgr = val;
1436
- hookAdsManagerPrototype(val);
1437
- }
1438
- });
1414
+ cleanup();
1439
1415
  } catch {
1440
1416
  }
1441
1417
  }
1442
- } catch {
1443
- }
1444
- }
1445
- function initImaInterception() {
1446
- if (typeof window === "undefined") return;
1447
- const win = window;
1448
- if (win.google?.ima) {
1449
- hookImaPrototype(win.google.ima);
1450
- }
1451
- if (win.google && typeof win.google === "object") {
1452
- const imaDesc = Object.getOwnPropertyDescriptor(win.google, "ima");
1453
- if (!imaDesc || imaDesc.configurable) {
1454
- let _ima = win.google.ima;
1418
+ this.videoDetachCleanups.clear();
1419
+ for (const cleanup of Array.from(this.clickDetachCleanups)) {
1455
1420
  try {
1456
- Object.defineProperty(win.google, "ima", {
1457
- configurable: true,
1458
- enumerable: true,
1459
- get() {
1460
- return _ima;
1461
- },
1462
- set(val) {
1463
- _ima = val;
1464
- hookImaPrototype(val);
1465
- }
1466
- });
1421
+ cleanup();
1467
1422
  } catch {
1468
1423
  }
1469
1424
  }
1470
- }
1471
- const googleDesc = Object.getOwnPropertyDescriptor(win, "google");
1472
- if (!googleDesc || googleDesc.configurable) {
1473
- let _google = win.google;
1474
- try {
1475
- Object.defineProperty(win, "google", {
1476
- configurable: true,
1477
- enumerable: true,
1478
- get() {
1479
- return _google;
1480
- },
1481
- set(val) {
1482
- _google = val;
1483
- if (_google && typeof _google === "object") {
1484
- if (_google.ima) {
1485
- hookImaPrototype(_google.ima);
1486
- }
1487
- const iDesc = Object.getOwnPropertyDescriptor(_google, "ima");
1488
- if (!iDesc || iDesc.configurable) {
1489
- let _i = _google.ima;
1490
- try {
1491
- Object.defineProperty(_google, "ima", {
1492
- configurable: true,
1493
- enumerable: true,
1494
- get() {
1495
- return _i;
1496
- },
1497
- set(iv) {
1498
- _i = iv;
1499
- hookImaPrototype(iv);
1500
- }
1501
- });
1502
- } catch {
1503
- }
1504
- }
1505
- }
1425
+ this.clickDetachCleanups.clear();
1426
+ this.slotElements.clear();
1427
+ this.cachedWinningBids.clear();
1428
+ this.slotEmittedImpressionKeys.clear();
1429
+ this.slotLastAuctionIds.clear();
1430
+ this.auctionBidderOutcomes.clear();
1431
+ if (this.flushTimer) {
1432
+ clearInterval(this.flushTimer);
1433
+ this.flushTimer = null;
1434
+ }
1435
+ if (this.revenueFlushTimer) {
1436
+ clearTimeout(this.revenueFlushTimer);
1437
+ this.revenueFlushTimer = null;
1438
+ }
1439
+ if (this.persistedCleanupTimer) {
1440
+ clearTimeout(this.persistedCleanupTimer);
1441
+ this.persistedCleanupTimer = null;
1442
+ }
1443
+ if (typeof window !== "undefined") {
1444
+ window.removeEventListener("pagehide", this.boundFlushBeacon);
1445
+ if (typeof document !== "undefined") {
1446
+ document.removeEventListener("visibilitychange", this.boundVisibilityChange);
1447
+ }
1448
+ }
1449
+ if (this.boundPbjsHandlers.length > 0) {
1450
+ const win = typeof window !== "undefined" ? window : {};
1451
+ const pbjs = win[this.config.pbjsGlobalName] || {};
1452
+ if (typeof pbjs.offEvent === "function") {
1453
+ for (const { event, handler } of this.boundPbjsHandlers) {
1454
+ pbjs.offEvent(event, handler);
1506
1455
  }
1507
- });
1508
- } catch {
1456
+ }
1457
+ this.boundPbjsHandlers = [];
1509
1458
  }
1510
- }
1511
- }
1512
- if (typeof window !== "undefined") {
1513
- initImaInterception();
1514
- }
1515
- var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1516
- static activeInstances = /* @__PURE__ */ new Set();
1517
- static getActiveInstances() {
1518
- return _BidkernelPrebidAnalytics.activeInstances;
1519
- }
1520
- static initImaInterception() {
1521
- initImaInterception();
1522
- }
1523
- /**
1524
- * Allows or forbids localStorage writes for every instance on the page.
1525
- * Collection and delivery are unaffected: only the persisted session id,
1526
- * its timestamp and exit batches are gated.
1527
- */
1528
- static setStorageAllowed(allowed) {
1529
- storageAllowed = allowed;
1530
- }
1531
- static isStorageAllowed() {
1532
- return storageAllowed;
1533
- }
1534
- config;
1535
- queue = [];
1536
- errorCount = 0;
1537
- flushTimer = null;
1538
- boundFlushBeacon;
1539
- boundVisibilityChange;
1540
- isEnabled = false;
1541
- boundPbjsHandlers = [];
1542
- // Captured at enable()/navigate() so events queued before an SPA route
1543
- // change flush with the page they occurred on, not the new URL.
1544
- pageUrl = "";
1545
- deduper = new PrebidEventDeduper();
1546
- consecutiveSendFailures = 0;
1547
- nextSendAllowedAt = 0;
1548
- replayedEventCount = 0;
1549
- revenueFlushTimer = null;
1550
- // localStorage keys of exit batches this instance persisted, so a page that
1551
- // survives its own pagehide/hidden (bfcache restore, tab re-focus) can
1552
- // remove them instead of leaving them for a duplicate resend.
1553
- persistedBatchKeys = [];
1554
- persistedCleanupTimer = null;
1555
- // Viewability & refresh tracking
1556
- intersectionObserver = null;
1557
- slotViewabilityRecords = /* @__PURE__ */ new Map();
1558
- elementToSlotId = /* @__PURE__ */ new Map();
1559
- slotRefreshIndices = /* @__PURE__ */ new Map();
1560
- slotLastAuctionIds = /* @__PURE__ */ new Map();
1561
- pendingThresholdListeners = /* @__PURE__ */ new Map();
1562
- pendingViewableListeners = /* @__PURE__ */ new Map();
1563
- // Element a subscriber named for a slot (a sticky bar or interstitial that
1564
- // renders outside the slot element); wins over document.getElementById.
1565
- slotElements = /* @__PURE__ */ new Map();
1566
- clickDetachCleanups = /* @__PURE__ */ new Set();
1567
- lastClickAt = /* @__PURE__ */ new Map();
1568
- // Impression deduplication per slot and refresh cycle (strictly 1 impression per cycle)
1569
- slotEmittedImpressionKeys = /* @__PURE__ */ new Set();
1570
- // Winning bid cache from bidWon to marry with subsequent render triggers (adRenderSucceeded, video, etc.)
1571
- cachedWinningBids = /* @__PURE__ */ new Map();
1572
- // Active video player attachment cleanup routines
1573
- videoDetachCleanups = /* @__PURE__ */ new Set();
1574
- // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1575
- auctionBidderOutcomes = /* @__PURE__ */ new Map();
1576
- logger;
1577
- constructor(config) {
1578
- if (config.storageAllowed !== void 0) {
1579
- _BidkernelPrebidAnalytics.setStorageAllowed(config.storageAllowed);
1580
- }
1581
- const requestedEndpoint = config.endpoint || "";
1582
- const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
1583
- this.config = {
1584
- endpoint,
1585
- propertyId: config.propertyId || "",
1586
- pageviewId: config.pageviewId || generateUUID(),
1587
- sessionId: config.sessionId || getOrCreateSessionId(),
1588
- userId: config.userId || "",
1589
- deviceType: config.deviceType || "desktop",
1590
- country: config.country || "",
1591
- region: config.region || "",
1592
- auctionEnabled: config.auctionEnabled ?? true,
1593
- warningsEnabled: config.warningsEnabled ?? true,
1594
- errorsEnabled: config.errorsEnabled ?? true,
1595
- viewabilityEnabled: config.viewabilityEnabled ?? true,
1596
- storageAllowed: config.storageAllowed ?? true,
1597
- logLevel: config.logLevel || "INFO",
1598
- pbjsGlobalName: config.pbjsGlobalName || "pbjs",
1599
- attachPbjsListeners: config.attachPbjsListeners ?? true,
1600
- revenueFlushDelayMs: Number.isFinite(config.revenueFlushDelayMs) && config.revenueFlushDelayMs >= 0 ? config.revenueFlushDelayMs : REVENUE_FLUSH_DELAY_MS
1601
- };
1602
- this.logger = createLogger({
1603
- prefix: "[bidkernel][prebid-analytics]",
1604
- logLevel: this.config.logLevel
1605
- });
1606
- if (requestedEndpoint && !endpoint) {
1607
- this.log(
1608
- "ERROR",
1609
- `Rejected untrusted analytics endpoint ${requestedEndpoint}: must be an absolute https:// URL without embedded credentials. Events will not be transmitted.`
1610
- );
1611
- }
1612
- this.boundFlushBeacon = () => {
1613
- this.flushAllSlotsTimeInView();
1614
- this.flushBeacon();
1615
- };
1616
- this.boundVisibilityChange = () => this.handleVisibilityChange();
1617
- }
1618
- enable() {
1619
- if (this.isEnabled) return;
1620
- this.isEnabled = true;
1621
- _BidkernelPrebidAnalytics.activeInstances.add(this);
1622
- this.logger.setLevel(this.config.logLevel);
1623
- if (!this.config.endpoint) {
1624
- this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
1625
- }
1626
- if (this.config.viewabilityEnabled) {
1627
- if (typeof IntersectionObserver !== "undefined") {
1628
- this.intersectionObserver = new IntersectionObserver(this.handleIntersection.bind(this), {
1629
- threshold: [0.5]
1630
- });
1631
- for (const record of this.slotViewabilityRecords.values()) {
1632
- if (record.element) {
1633
- this.intersectionObserver.observe(record.element);
1634
- }
1635
- }
1636
- }
1637
- }
1638
- if (this.config.attachPbjsListeners) {
1639
- this.attachPbjs();
1640
- }
1641
- if (typeof window !== "undefined") {
1642
- if (!this.pageUrl) {
1643
- try {
1644
- const u = new URL(window.location.href);
1645
- this.pageUrl = u.origin + u.pathname;
1646
- } catch {
1647
- this.pageUrl = window.location.href;
1648
- }
1649
- }
1650
- window.addEventListener("pagehide", this.boundFlushBeacon);
1651
- if (typeof document !== "undefined") {
1652
- document.addEventListener("visibilitychange", this.boundVisibilityChange);
1653
- }
1654
- this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
1655
- this.resendPersistedBatches();
1656
- initImaInterception();
1657
- if (window.google?.ima) {
1658
- hookImaPrototype(window.google.ima);
1659
- }
1660
- }
1661
- }
1662
- /**
1663
- * Binds the Prebid event handlers on the configured global and replays
1664
- * pbjs.getEvents() history. enable() calls this when attachPbjsListeners is
1665
- * true. An integration that loads Prebid lazily constructs the instance with
1666
- * attachPbjsListeners=false and calls this once pbjs exists; disable() (and
1667
- * therefore navigate()) drops the handlers, so call it again after either.
1668
- * Idempotent. Returns true when handlers are bound after the call, false
1669
- * when the instance is disabled or pbjs.onEvent is not available yet.
1670
- */
1671
- attachPbjs() {
1672
- if (!this.isEnabled) return false;
1673
- if (this.boundPbjsHandlers.length > 0) return true;
1674
- const win = typeof window !== "undefined" ? window : {};
1675
- const pbjs = win[this.config.pbjsGlobalName] || {};
1676
- if (typeof pbjs.onEvent !== "function") {
1677
- this.log("WARN", "pbjs.onEvent is not defined. Prebid analytics will not function.");
1678
- return false;
1679
- }
1680
- this.log("DEBUG", "Attaching event listeners to pbjs");
1681
- const handlerMap = {
1682
- auctionInit: this.handleAuctionInit.bind(this),
1683
- auctionEnd: this.handleAuctionEnd.bind(this),
1684
- bidRequested: this.handleBidRequested.bind(this),
1685
- bidResponse: this.handleBidResponse.bind(this),
1686
- bidTimeout: this.handleBidTimeout.bind(this),
1687
- bidWon: this.handleBidWon.bind(this),
1688
- noBid: this.handleNoBid.bind(this),
1689
- adRenderFailed: this.handleAdRenderFailed.bind(this),
1690
- adRenderSucceeded: this.handleAdRenderSucceeded.bind(this)
1691
- };
1692
- for (const [name, fn] of Object.entries(handlerMap)) {
1693
- pbjs.onEvent(name, fn);
1694
- this.boundPbjsHandlers.push({ event: name, handler: fn });
1695
- }
1696
- if (typeof pbjs.getEvents === "function") {
1697
- try {
1698
- const pastEvents = pbjs.getEvents();
1699
- if (Array.isArray(pastEvents)) {
1700
- const newPastEvents = pastEvents.slice(this.replayedEventCount);
1701
- this.replayedEventCount = pastEvents.length;
1702
- if (newPastEvents.length > 0) {
1703
- this.log(
1704
- "DEBUG",
1705
- `Replaying ${newPastEvents.length} historical events from pbjs.getEvents()`
1706
- );
1707
- for (const ev of newPastEvents) {
1708
- if (!ev) continue;
1709
- const eventType = ev.eventType || ev.event || ev.name;
1710
- const args = ev.args !== void 0 ? ev.args : ev.data !== void 0 ? ev.data : ev;
1711
- const handler = handlerMap[eventType];
1712
- if (handler) {
1713
- handler(args);
1714
- }
1715
- }
1716
- }
1717
- }
1718
- } catch (e) {
1719
- this.log("WARN", "Failed to replay historical events from pbjs.getEvents()", e);
1720
- }
1721
- }
1722
- return true;
1723
- }
1724
- disable() {
1725
- if (!this.isEnabled) return;
1726
- this.flushAllSlotsTimeInView();
1727
- this.isEnabled = false;
1728
- _BidkernelPrebidAnalytics.activeInstances.delete(this);
1729
- for (const record of this.slotViewabilityRecords.values()) {
1730
- if (record.dwellTimer) {
1731
- clearTimeout(record.dwellTimer);
1732
- record.dwellTimer = null;
1733
- record.dwellStartedAt = null;
1734
- }
1735
- for (const l of record.thresholdListeners) {
1736
- if (l.timer) {
1737
- clearTimeout(l.timer);
1738
- l.timer = null;
1739
- }
1740
- }
1741
- }
1742
- if (this.intersectionObserver) {
1743
- this.intersectionObserver.disconnect();
1744
- this.intersectionObserver = null;
1745
- }
1746
- this.slotViewabilityRecords.clear();
1747
- this.elementToSlotId.clear();
1748
- for (const cleanup of this.videoDetachCleanups) {
1749
- try {
1750
- cleanup();
1751
- } catch {
1752
- }
1753
- }
1754
- this.videoDetachCleanups.clear();
1755
- for (const cleanup of Array.from(this.clickDetachCleanups)) {
1756
- try {
1757
- cleanup();
1758
- } catch {
1759
- }
1760
- }
1761
- this.clickDetachCleanups.clear();
1762
- this.slotElements.clear();
1763
- this.cachedWinningBids.clear();
1764
- this.slotEmittedImpressionKeys.clear();
1765
- this.slotLastAuctionIds.clear();
1766
- this.auctionBidderOutcomes.clear();
1767
- if (this.flushTimer) {
1768
- clearInterval(this.flushTimer);
1769
- this.flushTimer = null;
1770
- }
1771
- if (this.revenueFlushTimer) {
1772
- clearTimeout(this.revenueFlushTimer);
1773
- this.revenueFlushTimer = null;
1774
- }
1775
- if (this.persistedCleanupTimer) {
1776
- clearTimeout(this.persistedCleanupTimer);
1777
- this.persistedCleanupTimer = null;
1778
- }
1779
- if (typeof window !== "undefined") {
1780
- window.removeEventListener("pagehide", this.boundFlushBeacon);
1781
- if (typeof document !== "undefined") {
1782
- document.removeEventListener("visibilitychange", this.boundVisibilityChange);
1783
- }
1784
- }
1785
- if (this.boundPbjsHandlers.length > 0) {
1786
- const win = typeof window !== "undefined" ? window : {};
1787
- const pbjs = win[this.config.pbjsGlobalName] || {};
1788
- if (typeof pbjs.offEvent === "function") {
1789
- for (const { event, handler } of this.boundPbjsHandlers) {
1790
- pbjs.offEvent(event, handler);
1791
- }
1792
- }
1793
- this.boundPbjsHandlers = [];
1794
- }
1795
- this.flush();
1459
+ this.flush();
1796
1460
  }
1797
1461
  handleVisibilityChange() {
1798
1462
  if (typeof document === "undefined") return;
@@ -2315,72 +1979,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2315
1979
  this.cachedWinningBids.delete(oldest.value);
2316
1980
  }
2317
1981
  }
2318
- /**
2319
- * Searches cached winning bids for an entry matching the given filters (creativeId,
2320
- * adId, or mediaType), falling back to the most recent winning video bid.
2321
- */
2322
- findCachedWinningBid(filter) {
2323
- const now = Date.now();
2324
- const isConsumed = (entry) => {
2325
- return this.hasImpressionEmitted(
2326
- entry.adUnitCode,
2327
- void 0,
2328
- entry.auctionId,
2329
- entry.bidTrace.creativeId
2330
- );
2331
- };
2332
- if (filter?.creativeId || filter?.adId) {
2333
- for (const entry of Array.from(this.cachedWinningBids.values())) {
2334
- if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
2335
- if (filter.creativeId && (entry.bidTrace.creativeId === filter.creativeId || entry.rawBid?.creativeId === filter.creativeId) || filter.adId && (entry.rawBid?.adId === filter.adId || entry.adUnitCode === filter.adId)) {
2336
- return entry;
2337
- }
2338
- }
2339
- }
2340
- let bestUnconsumedMatch;
2341
- if (filter?.mediaType) {
2342
- for (const entry of Array.from(this.cachedWinningBids.values())) {
2343
- if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
2344
- const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
2345
- if (isEntryVideo) {
2346
- if (!isConsumed(entry)) {
2347
- if (!bestUnconsumedMatch || entry.timestamp < bestUnconsumedMatch.timestamp) {
2348
- bestUnconsumedMatch = entry;
2349
- }
2350
- }
2351
- }
2352
- }
2353
- if (bestUnconsumedMatch) return bestUnconsumedMatch;
2354
- let bestFallbackMatch;
2355
- for (const entry of Array.from(this.cachedWinningBids.values())) {
2356
- if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
2357
- const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
2358
- if (isEntryVideo) {
2359
- if (!bestFallbackMatch || entry.timestamp > bestFallbackMatch.timestamp) {
2360
- bestFallbackMatch = entry;
2361
- }
2362
- }
2363
- }
2364
- if (bestFallbackMatch) return bestFallbackMatch;
2365
- }
2366
- for (const entry of Array.from(this.cachedWinningBids.values())) {
2367
- if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
2368
- if (!isConsumed(entry)) {
2369
- if (!bestUnconsumedMatch || entry.timestamp > bestUnconsumedMatch.timestamp) {
2370
- bestUnconsumedMatch = entry;
2371
- }
2372
- }
2373
- }
2374
- if (bestUnconsumedMatch) return bestUnconsumedMatch;
2375
- let bestRecent;
2376
- for (const entry of Array.from(this.cachedWinningBids.values())) {
2377
- if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
2378
- if (!bestRecent || entry.timestamp > bestRecent.timestamp) {
2379
- bestRecent = entry;
2380
- }
2381
- }
2382
- return bestRecent;
2383
- }
2384
1982
  /** Records a dedup key, evicting oldest-first at the cap. */
2385
1983
  addImpressionKey(key) {
2386
1984
  if (this.slotEmittedImpressionKeys.has(key)) return;
@@ -2505,424 +2103,28 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2505
2103
  "DEBUG",
2506
2104
  `Impression already emitted for slot ${resolvedSlotId || adUnitCode} in cycle ${currentRefreshIndex}`
2507
2105
  );
2508
- return false;
2509
- }
2510
- if (resolvedSlotId)
2511
- this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId);
2512
- if (adUnitCode && adUnitCode !== resolvedSlotId)
2513
- this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
2514
- this.enqueue(TraceEventType.IMPRESSION, "impression", {
2515
- auctionId,
2516
- transactionId,
2517
- adUnitCode,
2518
- bid: bidTrace,
2519
- metadata: {
2520
- ...options?.metadata,
2521
- refresh_index: String(currentRefreshIndex)
2522
- }
2523
- });
2524
- if (rec) {
2525
- if (!rec.bidPayload && bidTrace) rec.bidPayload = bidTrace;
2526
- if (!rec.auctionId && auctionId) rec.auctionId = auctionId;
2527
- if (!rec.transactionId && transactionId) rec.transactionId = transactionId;
2528
- }
2529
- return true;
2530
- }
2531
- /**
2532
- * Bridges Google IMA SDK AdsManager events to Bidkernel analytics.
2533
- * Defers IMPRESSION emission until AdEvent.STARTED (or IMPRESSION),
2534
- * tracks milestones (FIRST_QUARTILE, MIDPOINT, THIRD_QUARTILE, COMPLETE),
2535
- * and handles AD_ERROR.
2536
- */
2537
- attachImaAdsManager(adsManager, options) {
2538
- if (!adsManager || typeof adsManager.addEventListener !== "function") {
2539
- this.log("WARN", "Invalid AdsManager passed to attachImaAdsManager");
2540
- return () => {
2541
- };
2542
- }
2543
- if (!adsManager.__bidkernelAttachedInstances) {
2544
- try {
2545
- Object.defineProperty(adsManager, "__bidkernelAttachedInstances", {
2546
- value: /* @__PURE__ */ new Map(),
2547
- configurable: true,
2548
- writable: true
2549
- });
2550
- } catch {
2551
- adsManager.__bidkernelAttachedInstances = /* @__PURE__ */ new Map();
2552
- }
2553
- }
2554
- const attached = adsManager.__bidkernelAttachedInstances;
2555
- const previous = attached.get(this);
2556
- if (previous) {
2557
- if (!options) return () => {
2558
- };
2559
- previous();
2560
- }
2561
- const slotId = options?.slotId || options?.adUnitCode || "video";
2562
- const adUnitCode = options?.adUnitCode || slotId;
2563
- const auctionId = options?.auctionId || "";
2564
- const transactionId = options?.transactionId || "";
2565
- const getWinningBid = (adData) => {
2566
- let cached = (auctionId ? this.getCachedBid(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? this.getCachedBid(`${auctionId}:${slotId}`) : void 0) || (adUnitCode !== "video" ? this.getCachedBid(adUnitCode) : void 0) || (slotId !== adUnitCode && slotId !== "video" ? this.getCachedBid(slotId) : void 0);
2567
- if (!cached) {
2568
- cached = this.findCachedWinningBid({
2569
- creativeId: adData?.creativeId,
2570
- adId: adData?.adId,
2571
- mediaType: "video"
2572
- });
2573
- }
2574
- return cached;
2575
- };
2576
- let adStarted = false;
2577
- const onAdStartedOrImpression = (event) => {
2578
- adStarted = true;
2579
- this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
2580
- const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
2581
- const adData = {};
2582
- if (ad) {
2583
- adData.creativeId = (typeof ad.getCreativeId === "function" ? ad.getCreativeId() : ad.creativeId) || "";
2584
- adData.adId = (typeof ad.getAdId === "function" ? ad.getAdId() : ad.id) || "";
2585
- adData.title = (typeof ad.getTitle === "function" ? ad.getTitle() : ad.title) || "";
2586
- adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
2587
- adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
2588
- }
2589
- const cached = getWinningBid(adData);
2590
- const resolvedSlotId = options?.slotId && options.slotId !== "video" ? options.slotId : options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || options?.slotId || options?.adUnitCode || "video";
2591
- const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || resolvedSlotId;
2592
- const resolvedAuctionId = auctionId || cached?.auctionId || "";
2593
- const resolvedTransactionId = transactionId || cached?.transactionId || "";
2594
- const mergedBid = {
2595
- ...cached?.rawBid || options?.bid,
2596
- mediaType: "video",
2597
- creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
2598
- };
2599
- const emitted = this.recordImpression(resolvedSlotId, {
2600
- adUnitCode: resolvedAdUnitCode,
2601
- auctionId: resolvedAuctionId,
2602
- transactionId: resolvedTransactionId,
2603
- mediaType: "video",
2604
- bid: mergedBid,
2605
- metadata: {
2606
- ...options?.metadata,
2607
- media_type: "video",
2608
- ...adData.title ? { ad_title: adData.title } : {},
2609
- ...Number.isFinite(adData.duration) ? { ad_duration_s: String(adData.duration) } : {}
2610
- }
2611
- });
2612
- if (emitted && options?.onImpression) {
2613
- try {
2614
- options.onImpression(resolvedSlotId, { ad, bid: mergedBid });
2615
- } catch (e) {
2616
- this.log("ERROR", "Error in onImpression callback", e);
2617
- }
2618
- }
2619
- };
2620
- const onMilestone = (milestone) => {
2621
- this.log("DEBUG", `IMA AdEvent milestone: ${milestone}`);
2622
- if (options?.onMilestone) {
2623
- try {
2624
- options.onMilestone(milestone, slotId);
2625
- } catch (e) {
2626
- this.log("ERROR", "Error in onMilestone callback", e);
2627
- }
2628
- }
2629
- };
2630
- const onAdClick = (event) => {
2631
- this.log("DEBUG", "IMA AdEvent.CLICK received", event);
2632
- const cached = getWinningBid();
2633
- const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
2634
- this.enqueue(TraceEventType.CLICK, "click", {
2635
- auctionId: auctionId || cached?.auctionId,
2636
- transactionId: transactionId || cached?.transactionId,
2637
- adUnitCode: resolvedAdUnitCode,
2638
- bid: cached?.bidTrace || options?.bid,
2639
- metadata: {
2640
- ...options?.metadata,
2641
- media_type: "video"
2642
- }
2643
- });
2644
- };
2645
- const onAdError = (event) => {
2646
- this.log("WARN", "IMA AdErrorEvent received", event);
2647
- const err = typeof event?.getError === "function" ? event.getError() : event?.error || event;
2648
- const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
2649
- const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
2650
- const cached = getWinningBid();
2651
- const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
2652
- this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
2653
- auctionId: auctionId || cached?.auctionId,
2654
- transactionId: transactionId || cached?.transactionId,
2655
- adUnitCode: resolvedAdUnitCode,
2656
- bid: cached?.bidTrace || options?.bid,
2657
- metadata: {
2658
- ...options?.metadata,
2659
- reason: adStarted ? "ima_playback_error" : "ima_ad_error",
2660
- playback_phase: adStarted ? "post_start" : "pre_start",
2661
- message: msg,
2662
- ...code ? { ima_error_code: String(code) } : {}
2663
- },
2664
- error: typeof err === "object" ? err : new Error(msg)
2665
- });
2666
- if (options?.onError) {
2667
- try {
2668
- options.onError(err);
2669
- } catch (e) {
2670
- this.log("ERROR", "Error in onError callback", e);
2671
- }
2672
- }
2673
- };
2674
- const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
2675
- const adEventType = googleIma?.AdEvent?.Type || {};
2676
- const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
2677
- const rawListeners = [
2678
- { type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
2679
- { type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
2680
- {
2681
- type: adEventType.FIRST_QUARTILE || "firstQuartile",
2682
- handler: () => onMilestone("firstQuartile")
2683
- },
2684
- { type: adEventType.MIDPOINT || "midpoint", handler: () => onMilestone("midpoint") },
2685
- {
2686
- type: adEventType.THIRD_QUARTILE || "thirdQuartile",
2687
- handler: () => onMilestone("thirdQuartile")
2688
- },
2689
- { type: adEventType.COMPLETE || "complete", handler: () => onMilestone("complete") },
2690
- { type: adEventType.CLICK || "click", handler: onAdClick },
2691
- { type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
2692
- ];
2693
- const seenListenerTypes = /* @__PURE__ */ new Set();
2694
- const listeners = [];
2695
- for (const l of rawListeners) {
2696
- if (!seenListenerTypes.has(l.type)) {
2697
- seenListenerTypes.add(l.type);
2698
- listeners.push(l);
2699
- }
2700
- }
2701
- for (const { type, handler } of listeners) {
2702
- try {
2703
- adsManager.addEventListener(type, handler);
2704
- } catch {
2705
- }
2706
- }
2707
- const cleanup = () => {
2708
- attached.delete(this);
2709
- for (const { type, handler } of listeners) {
2710
- try {
2711
- adsManager.removeEventListener(type, handler);
2712
- } catch {
2713
- }
2714
- }
2715
- this.videoDetachCleanups.delete(cleanup);
2716
- };
2717
- attached.set(this, cleanup);
2718
- this.videoDetachCleanups.add(cleanup);
2719
- return cleanup;
2720
- }
2721
- /**
2722
- * Attaches render hooks and viewability tracking to a video player.
2723
- * Supports HTML5 <video> elements, container elements, or Google IMA AdsManager.
2724
- */
2725
- attachVideoPlayer(target, options) {
2726
- if (!target) {
2727
- this.log("WARN", "attachVideoPlayer called with null/undefined target");
2728
- return () => {
2729
- };
2730
- }
2731
- let el = null;
2732
- if (typeof target === "string") {
2733
- if (typeof document !== "undefined") {
2734
- try {
2735
- el = document.querySelector(target) || document.getElementById(target);
2736
- } catch {
2737
- el = document.getElementById(target);
2738
- }
2739
- }
2740
- } else if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
2741
- el = target;
2742
- } else if (target && typeof target === "object" && target.nodeType === 1) {
2743
- el = target;
2744
- } else if (
2745
- // Check if target is an IMA AdsManager
2746
- typeof target.addEventListener === "function" && (typeof target.init === "function" || typeof target.start === "function" || typeof target.getCuePoints === "function" || typeof target.getVolume === "function" || target.__imaAdsManager)
2747
- ) {
2748
- return this.attachImaAdsManager(target, options);
2749
- } else if (target && typeof target.addEventListener === "function") {
2750
- el = target;
2751
- }
2752
- const slotId = options?.slotId || options?.adUnitCode || (el ? el.id : "") || "video";
2753
- const adUnitCode = options?.adUnitCode || slotId;
2754
- const auctionId = options?.auctionId || "";
2755
- const transactionId = options?.transactionId || "";
2756
- const getWinningBid = () => {
2757
- return (auctionId ? this.getCachedBid(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? this.getCachedBid(`${auctionId}:${slotId}`) : void 0) || this.getCachedBid(adUnitCode) || (slotId !== adUnitCode ? this.getCachedBid(slotId) : void 0);
2758
- };
2759
- let videoEl = null;
2760
- if (el) {
2761
- if (el.tagName && el.tagName.toLowerCase() === "video") {
2762
- videoEl = el;
2763
- } else {
2764
- videoEl = el.querySelector("video");
2765
- }
2766
- }
2767
- const quartilesFired = {
2768
- q1: false,
2769
- q2: false,
2770
- q3: false,
2771
- q4: false
2772
- };
2773
- const triggerPlaybackImpression = (activeVideo) => {
2774
- const vEl = activeVideo || videoEl || (el?.querySelector ? el.querySelector("video") : null);
2775
- const cached = getWinningBid();
2776
- const mergedBid = {
2777
- ...cached?.rawBid || options?.bid,
2778
- mediaType: "video"
2779
- };
2780
- const emitted = this.recordImpression(slotId, {
2781
- adUnitCode,
2782
- auctionId: auctionId || cached?.auctionId,
2783
- transactionId: transactionId || cached?.transactionId,
2784
- mediaType: "video",
2785
- bid: mergedBid,
2786
- metadata: {
2787
- ...options?.metadata,
2788
- media_type: "video",
2789
- ...vEl && Number.isFinite(vEl.duration) ? { video_duration_s: String(Math.round(vEl.duration)) } : {}
2790
- }
2791
- });
2792
- if (emitted && options?.onImpression) {
2793
- try {
2794
- options.onImpression(slotId, { element: vEl || el, bid: mergedBid });
2795
- } catch (e) {
2796
- this.log("ERROR", "Error in onImpression callback", e);
2797
- }
2798
- }
2799
- };
2800
- const handlePlaying = (e) => {
2801
- this.log("DEBUG", "HTML5 video playing event received");
2802
- const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
2803
- triggerPlaybackImpression(targetVideo);
2804
- };
2805
- const handleTimeUpdate = (e) => {
2806
- const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
2807
- if (targetVideo && targetVideo.currentTime > 0) {
2808
- triggerPlaybackImpression(targetVideo);
2809
- if (Number.isFinite(targetVideo.duration) && targetVideo.duration > 0) {
2810
- const progress = targetVideo.currentTime / targetVideo.duration;
2811
- if (progress >= 0.25 && !quartilesFired.q1) {
2812
- quartilesFired.q1 = true;
2813
- options?.onMilestone?.("firstQuartile", slotId);
2814
- }
2815
- if (progress >= 0.5 && !quartilesFired.q2) {
2816
- quartilesFired.q2 = true;
2817
- options?.onMilestone?.("midpoint", slotId);
2818
- }
2819
- if (progress >= 0.75 && !quartilesFired.q3) {
2820
- quartilesFired.q3 = true;
2821
- options?.onMilestone?.("thirdQuartile", slotId);
2822
- }
2823
- }
2824
- }
2825
- };
2826
- const handleEnded = () => {
2827
- if (!quartilesFired.q4) {
2828
- quartilesFired.q4 = true;
2829
- options?.onMilestone?.("complete", slotId);
2830
- }
2831
- };
2832
- const handleError = (e) => {
2833
- const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
2834
- const err = targetVideo?.error || videoEl?.error;
2835
- const cached = getWinningBid();
2836
- const msg = err ? `HTML5 video error code ${err.code}: ${err.message}` : "Video playback error";
2837
- this.log("WARN", msg);
2838
- this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
2839
- auctionId: auctionId || cached?.auctionId,
2840
- transactionId: transactionId || cached?.transactionId,
2841
- adUnitCode,
2842
- bid: cached?.bidTrace || options?.bid,
2843
- metadata: {
2844
- ...options?.metadata,
2845
- reason: "html5_video_error",
2846
- message: msg,
2847
- ...err?.code ? { video_error_code: String(err.code) } : {}
2848
- },
2849
- error: err ? new Error(msg) : void 0
2850
- });
2851
- if (options?.onError) {
2852
- try {
2853
- options.onError(err);
2854
- } catch (e2) {
2855
- this.log("ERROR", "Error in onError callback", e2);
2856
- }
2857
- }
2858
- };
2859
- const attachListenersToVideo = (targetVideo) => {
2860
- targetVideo.addEventListener("playing", handlePlaying);
2861
- targetVideo.addEventListener("play", handlePlaying);
2862
- targetVideo.addEventListener("timeupdate", handleTimeUpdate);
2863
- targetVideo.addEventListener("ended", handleEnded);
2864
- targetVideo.addEventListener("error", handleError);
2865
- };
2866
- const removeListenersFromVideo = (targetVideo) => {
2867
- targetVideo.removeEventListener("playing", handlePlaying);
2868
- targetVideo.removeEventListener("play", handlePlaying);
2869
- targetVideo.removeEventListener("timeupdate", handleTimeUpdate);
2870
- targetVideo.removeEventListener("ended", handleEnded);
2871
- targetVideo.removeEventListener("error", handleError);
2872
- };
2873
- let mutationObserver = null;
2874
- if (el && el !== videoEl) {
2875
- el.addEventListener("playing", handlePlaying, true);
2876
- el.addEventListener("play", handlePlaying, true);
2877
- el.addEventListener("timeupdate", handleTimeUpdate, true);
2878
- el.addEventListener("ended", handleEnded, true);
2879
- el.addEventListener("error", handleError, true);
2880
- }
2881
- if (videoEl) {
2882
- attachListenersToVideo(videoEl);
2883
- }
2884
- if (el && el.tagName?.toLowerCase() !== "video" && typeof MutationObserver !== "undefined") {
2885
- mutationObserver = new MutationObserver(() => {
2886
- const found = el.querySelector("video");
2887
- if (found && found !== videoEl) {
2888
- if (videoEl) removeListenersFromVideo(videoEl);
2889
- videoEl = found;
2890
- if (videoEl) attachListenersToVideo(videoEl);
2891
- }
2892
- });
2893
- mutationObserver.observe(el, { childList: true, subtree: true });
2894
- }
2895
- const cachedInitial = getWinningBid();
2896
- if (options?.trackViewability !== false && el) {
2897
- this.observeSlot(el, slotId, {
2898
- adUnitCode,
2899
- auctionId: auctionId || cachedInitial?.auctionId,
2900
- transactionId: transactionId || cachedInitial?.transactionId,
2901
- mediaType: "video",
2902
- bid: cachedInitial?.bidTrace || options?.bid,
2903
- metadata: options?.metadata,
2904
- emitRefreshEvent: false
2905
- });
2906
- }
2907
- const cleanup = () => {
2908
- if (mutationObserver) {
2909
- mutationObserver.disconnect();
2910
- mutationObserver = null;
2911
- }
2912
- if (el && el !== videoEl) {
2913
- el.removeEventListener("playing", handlePlaying, true);
2914
- el.removeEventListener("play", handlePlaying, true);
2915
- el.removeEventListener("timeupdate", handleTimeUpdate, true);
2916
- el.removeEventListener("ended", handleEnded, true);
2917
- el.removeEventListener("error", handleError, true);
2918
- }
2919
- if (videoEl) {
2920
- removeListenersFromVideo(videoEl);
2106
+ return false;
2107
+ }
2108
+ if (resolvedSlotId)
2109
+ this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId);
2110
+ if (adUnitCode && adUnitCode !== resolvedSlotId)
2111
+ this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
2112
+ this.enqueue(TraceEventType.IMPRESSION, "impression", {
2113
+ auctionId,
2114
+ transactionId,
2115
+ adUnitCode,
2116
+ bid: bidTrace,
2117
+ metadata: {
2118
+ ...options?.metadata,
2119
+ refresh_index: String(currentRefreshIndex)
2921
2120
  }
2922
- this.videoDetachCleanups.delete(cleanup);
2923
- };
2924
- this.videoDetachCleanups.add(cleanup);
2925
- return cleanup;
2121
+ });
2122
+ if (rec) {
2123
+ if (!rec.bidPayload && bidTrace) rec.bidPayload = bidTrace;
2124
+ if (!rec.auctionId && auctionId) rec.auctionId = auctionId;
2125
+ if (!rec.transactionId && transactionId) rec.transactionId = transactionId;
2126
+ }
2127
+ return true;
2926
2128
  }
2927
2129
  onTimeInViewThreshold(slotId, thresholdMs, callback) {
2928
2130
  const listener = {
@@ -3306,523 +2508,1243 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
3306
2508
  if (resolvedAuctionId) {
3307
2509
  this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
3308
2510
  }
3309
- if (!this.slotRefreshIndices.has(adUnitCode)) {
3310
- this.slotRefreshIndices.set(adUnitCode, 0);
2511
+ if (!this.slotRefreshIndices.has(adUnitCode)) {
2512
+ this.slotRefreshIndices.set(adUnitCode, 0);
2513
+ }
2514
+ }
2515
+ const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
2516
+ if (!isVideo) {
2517
+ const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
2518
+ adUnitCode,
2519
+ currentRefreshIndex,
2520
+ resolvedAuctionId,
2521
+ bidTrace.creativeId
2522
+ ) : false;
2523
+ if (!alreadyEmitted) {
2524
+ if (adUnitCode) {
2525
+ this.markImpressionEmitted(
2526
+ adUnitCode,
2527
+ currentRefreshIndex,
2528
+ resolvedAuctionId,
2529
+ bidTrace.creativeId
2530
+ );
2531
+ }
2532
+ this.enqueue(TraceEventType.IMPRESSION, "impression", {
2533
+ auctionId: resolvedAuctionId,
2534
+ transactionId: resolvedTransactionId,
2535
+ adUnitCode,
2536
+ bid: bidTrace,
2537
+ metadata: { refresh_index: String(currentRefreshIndex) }
2538
+ });
2539
+ }
2540
+ } else {
2541
+ this.log(
2542
+ "DEBUG",
2543
+ `adRenderSucceeded received for video ad unit ${adUnitCode || "unknown"}: suppressing IMPRESSION until verified video lifecycle event`
2544
+ );
2545
+ }
2546
+ if (this.config.viewabilityEnabled && typeof document !== "undefined") {
2547
+ let el = adUnitCode && this.slotElements.get(adUnitCode) || null;
2548
+ const targetId = adUnitCode || data.adId || bid.adId;
2549
+ if (!el && targetId) {
2550
+ el = document.getElementById(targetId);
2551
+ }
2552
+ if (!el && typeof window !== "undefined" && window.googletag?.pubads) {
2553
+ try {
2554
+ const slots = window.googletag.pubads().getSlots();
2555
+ for (const slot of slots) {
2556
+ if (slot.getAdUnitPath?.() === adUnitCode || slot.getSlotElementId?.() === adUnitCode) {
2557
+ el = document.getElementById(slot.getSlotElementId());
2558
+ if (el) break;
2559
+ }
2560
+ }
2561
+ } catch {
2562
+ }
2563
+ }
2564
+ if (!el && targetId) {
2565
+ try {
2566
+ for (const candidate of Array.from(document.querySelectorAll("[data-ad-slot]"))) {
2567
+ if (candidate.getAttribute("data-ad-slot") === targetId) {
2568
+ el = candidate;
2569
+ break;
2570
+ }
2571
+ }
2572
+ } catch {
2573
+ }
2574
+ }
2575
+ if (el) {
2576
+ this.observeSlot(el, adUnitCode || targetId, {
2577
+ adUnitCode,
2578
+ auctionId: resolvedAuctionId,
2579
+ transactionId: resolvedTransactionId,
2580
+ mediaType: resolvedMediaType,
2581
+ bid: bidTrace,
2582
+ refreshIndex: currentRefreshIndex,
2583
+ emitRefreshEvent: false
2584
+ });
2585
+ }
2586
+ }
2587
+ }
2588
+ isDuplicate(eventName, data) {
2589
+ return this.deduper.isDuplicate(eventName, data);
2590
+ }
2591
+ enqueue(type, eventName, data, level) {
2592
+ if (!this.isEnabled) return;
2593
+ if (!this.shouldSample(type, level)) return;
2594
+ extendSession();
2595
+ const protoEvent = {
2596
+ eventId: generateUUID(),
2597
+ timestampMs: Date.now(),
2598
+ type,
2599
+ eventName,
2600
+ auctionId: data.auctionId || "",
2601
+ transactionId: data.transactionId || "",
2602
+ adUnitCode: data.adUnitCode || data.elementId || "",
2603
+ namespace: data.ns || data.namespace || ""
2604
+ };
2605
+ if (data.bid) {
2606
+ protoEvent.bid = {
2607
+ bidder: data.bid.bidder || "",
2608
+ cpm: Number.isFinite(data.bid.cpm) ? data.bid.cpm : 0,
2609
+ // No defaults here: handlers with a priced bid (bidResponse, bidWon,
2610
+ // impression) set currency/mediaType themselves. Defaulting for the
2611
+ // rest would stamp fake "USD"/"banner" on noBid and bidRequest rows.
2612
+ currency: data.bid.currency || "",
2613
+ width: Number.isFinite(data.bid.width) ? data.bid.width : 0,
2614
+ height: Number.isFinite(data.bid.height) ? data.bid.height : 0,
2615
+ dealId: data.bid.dealId || "",
2616
+ mediaType: data.bid.mediaType || "",
2617
+ latencyMs: Number.isFinite(data.bid.latencyMs) ? data.bid.latencyMs : 0,
2618
+ advertiserDomain: data.bid.advertiserDomain || "",
2619
+ creativeId: data.bid.creativeId || ""
2620
+ };
2621
+ }
2622
+ if (data.viewableDurationMs !== void 0) {
2623
+ protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
2624
+ }
2625
+ if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2626
+ protoEvent.bidderOutcomes = data.bidderOutcomes;
2627
+ }
2628
+ const metadata = { ...data.metadata };
2629
+ if (data.error !== void 0 && data.error !== null) {
2630
+ metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
2631
+ }
2632
+ if (data.gpid) {
2633
+ metadata.gpid = String(data.gpid);
2634
+ }
2635
+ if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
2636
+ metadata.refresh_index = String(data.refreshIndex);
2637
+ }
2638
+ const boundedMetadata = boundMetadata(metadata);
2639
+ if (Object.keys(boundedMetadata).length > 0) {
2640
+ protoEvent.metadata = boundedMetadata;
2641
+ }
2642
+ this.queue.push(protoEvent);
2643
+ if (this.queue.length > MAX_QUEUE_SIZE) {
2644
+ this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE);
2645
+ }
2646
+ if (this.queue.length >= BATCH_SIZE) {
2647
+ this.flush();
2648
+ } else if (REVENUE_FLUSH_TYPES.has(type) && this.revenueFlushTimer === null) {
2649
+ this.revenueFlushTimer = setTimeout(() => {
2650
+ this.revenueFlushTimer = null;
2651
+ this.flush();
2652
+ }, this.config.revenueFlushDelayMs);
2653
+ }
2654
+ }
2655
+ shouldSample(type, level) {
2656
+ if (type === TraceEventType.ERROR) {
2657
+ this.errorCount++;
2658
+ if (this.errorCount > MAX_ERRORS_PER_SESSION) return false;
2659
+ return this.config.errorsEnabled;
2660
+ }
2661
+ if (level === "WARN") {
2662
+ return this.config.warningsEnabled;
2663
+ }
2664
+ return this.config.auctionEnabled;
2665
+ }
2666
+ requeue(events) {
2667
+ if (!events.length) return;
2668
+ this.queue = events.concat(this.queue);
2669
+ if (this.queue.length > MAX_QUEUE_SIZE) {
2670
+ this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE);
2671
+ }
2672
+ }
2673
+ // Drains a batch (up to MAX_PAYLOAD_BYTES) from the queue.
2674
+ // Shared by flush() and flushBeacon().
2675
+ drainBatch() {
2676
+ if (this.queue.length === 0 || !this.config.endpoint) return null;
2677
+ const domain = typeof window !== "undefined" ? window.location.hostname : "";
2678
+ const buildBatch = (evts) => ({
2679
+ propertyId: this.config.propertyId,
2680
+ pageviewId: this.config.pageviewId,
2681
+ sessionId: this.config.sessionId,
2682
+ pageUrl: this.pageUrl,
2683
+ country: this.config.country,
2684
+ region: this.config.region,
2685
+ deviceType: this.config.deviceType,
2686
+ userId: this.config.userId,
2687
+ domain,
2688
+ events: evts
2689
+ });
2690
+ for (; ; ) {
2691
+ if (this.queue.length === 0) return null;
2692
+ let events = this.queue;
2693
+ let encoded = TraceEventBatch.encode(
2694
+ buildBatch(events)
2695
+ ).finish();
2696
+ while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
2697
+ events = events.slice(0, Math.floor(events.length / 2));
2698
+ encoded = TraceEventBatch.encode(
2699
+ buildBatch(events)
2700
+ ).finish();
2701
+ }
2702
+ if (encoded.byteLength > MAX_PAYLOAD_BYTES) {
2703
+ const [dropped] = this.queue.splice(0, 1);
2704
+ this.log(
2705
+ "WARN",
2706
+ `Dropping oversized event ${dropped?.eventName || dropped?.eventId} (${encoded.byteLength} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte cap on its own)`
2707
+ );
2708
+ continue;
2709
+ }
2710
+ this.queue = this.queue.slice(events.length);
2711
+ return {
2712
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
2713
+ // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
2714
+ // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
2715
+ encoded,
2716
+ events
2717
+ };
2718
+ }
2719
+ }
2720
+ sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
2721
+ const fetchOpts = {
2722
+ method: "POST",
2723
+ // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
2724
+ headers: { "Content-Type": SAFE_CONTENT_TYPE },
2725
+ body: payload.encoded
2726
+ };
2727
+ if (useKeepalive) {
2728
+ fetchOpts.keepalive = true;
2729
+ }
2730
+ fetch(payload.url, fetchOpts).then((res) => {
2731
+ if (!res.ok) {
2732
+ this.log("WARN", `Failed to send batch: HTTP ${res.status}`);
2733
+ if (res.status >= 400 && res.status < 500 && res.status !== 429 && res.status !== 408) {
2734
+ this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
2735
+ return;
2736
+ }
2737
+ if (!isReplay) this.handleSendFailure(payload.events);
2738
+ } else {
2739
+ if (!isReplay) {
2740
+ this.consecutiveSendFailures = 0;
2741
+ this.nextSendAllowedAt = 0;
2742
+ }
2743
+ if (onDelivered) onDelivered();
2744
+ }
2745
+ }).catch((err) => {
2746
+ this.log("ERROR", "Failed to send batch", err);
2747
+ if (!isReplay) this.handleSendFailure(payload.events);
2748
+ });
2749
+ const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2750
+ if (proc && typeof proc._tickCallback === "function") {
2751
+ try {
2752
+ proc._tickCallback();
2753
+ } catch {
3311
2754
  }
3312
2755
  }
3313
- const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
3314
- if (!isVideo) {
3315
- const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
3316
- adUnitCode,
3317
- currentRefreshIndex,
3318
- resolvedAuctionId,
3319
- bidTrace.creativeId
3320
- ) : false;
3321
- if (!alreadyEmitted) {
3322
- if (adUnitCode) {
3323
- this.markImpressionEmitted(
3324
- adUnitCode,
3325
- currentRefreshIndex,
3326
- resolvedAuctionId,
3327
- bidTrace.creativeId
3328
- );
3329
- }
3330
- this.enqueue(TraceEventType.IMPRESSION, "impression", {
3331
- auctionId: resolvedAuctionId,
3332
- transactionId: resolvedTransactionId,
3333
- adUnitCode,
3334
- bid: bidTrace,
3335
- metadata: { refresh_index: String(currentRefreshIndex) }
3336
- });
3337
- }
2756
+ }
2757
+ handleSendFailure(events) {
2758
+ this.consecutiveSendFailures++;
2759
+ const backoff = Math.min(
2760
+ FLUSH_INTERVAL_MS * 2 ** (this.consecutiveSendFailures - 1),
2761
+ MAX_SEND_BACKOFF_MS
2762
+ );
2763
+ this.nextSendAllowedAt = Date.now() + backoff;
2764
+ if (this.consecutiveSendFailures <= MAX_CONSECUTIVE_SEND_FAILURES) {
2765
+ this.requeue(events);
3338
2766
  } else {
3339
2767
  this.log(
3340
- "DEBUG",
3341
- `adRenderSucceeded received for video ad unit ${adUnitCode || "unknown"}: suppressing IMPRESSION until verified video lifecycle event`
2768
+ "WARN",
2769
+ `Dropping ${events.length} events after ${this.consecutiveSendFailures} consecutive send failures`
3342
2770
  );
3343
2771
  }
3344
- if (this.config.viewabilityEnabled && typeof document !== "undefined") {
3345
- let el = adUnitCode && this.slotElements.get(adUnitCode) || null;
3346
- const targetId = adUnitCode || data.adId || bid.adId;
3347
- if (!el && targetId) {
3348
- el = document.getElementById(targetId);
3349
- }
3350
- if (!el && typeof window !== "undefined" && window.googletag?.pubads) {
2772
+ }
2773
+ flush() {
2774
+ if (Date.now() < this.nextSendAllowedAt) return;
2775
+ let payload = this.drainBatch();
2776
+ while (payload) {
2777
+ this.sendFetch(payload, false);
2778
+ if (Date.now() < this.nextSendAllowedAt) break;
2779
+ payload = this.drainBatch();
2780
+ }
2781
+ }
2782
+ flushBeacon() {
2783
+ let payload = this.drainBatch();
2784
+ while (payload) {
2785
+ let sent = false;
2786
+ if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
3351
2787
  try {
3352
- const slots = window.googletag.pubads().getSlots();
3353
- for (const slot of slots) {
3354
- if (slot.getAdUnitPath?.() === adUnitCode || slot.getSlotElementId?.() === adUnitCode) {
3355
- el = document.getElementById(slot.getSlotElementId());
3356
- if (el) break;
3357
- }
3358
- }
2788
+ const blob = new Blob([payload.encoded], { type: SAFE_CONTENT_TYPE });
2789
+ sent = navigator.sendBeacon(payload.url, blob);
3359
2790
  } catch {
2791
+ sent = false;
3360
2792
  }
3361
2793
  }
3362
- if (!el && targetId) {
2794
+ if (!sent) {
2795
+ const persistKey = this.persistBatch(payload.encoded);
2796
+ this.sendFetch(payload, true, () => this.removePersistedBatch(persistKey));
2797
+ }
2798
+ payload = this.drainBatch();
2799
+ }
2800
+ this.schedulePersistedCleanup();
2801
+ }
2802
+ // --- Exit-batch persistence -----------------------------------------------
2803
+ // Key prefix for this instance's property. encodeURIComponent keeps the
2804
+ // property segment free of the "_" the timestamp suffix is parsed against,
2805
+ // so a property ID containing one cannot forge another property's prefix.
2806
+ pendingKeyPrefix() {
2807
+ return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
2808
+ }
2809
+ persistBatch(encoded) {
2810
+ if (!storageAllowed) return null;
2811
+ try {
2812
+ if (typeof localStorage === "undefined") return null;
2813
+ if (!this.config.propertyId) return null;
2814
+ let pendingCount = 0;
2815
+ for (let i = 0; i < localStorage.length; i++) {
2816
+ const k = localStorage.key(i);
2817
+ if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
2818
+ }
2819
+ if (pendingCount >= MAX_PENDING_BATCHES) return null;
2820
+ const key = `${this.pendingKeyPrefix()}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2821
+ localStorage.setItem(key, bytesToBase64(encoded));
2822
+ this.persistedBatchKeys.push(key);
2823
+ return key;
2824
+ } catch {
2825
+ return null;
2826
+ }
2827
+ }
2828
+ removePersistedBatch(key) {
2829
+ if (!key) return;
2830
+ try {
2831
+ if (typeof localStorage !== "undefined") localStorage.removeItem(key);
2832
+ } catch {
2833
+ }
2834
+ const idx = this.persistedBatchKeys.indexOf(key);
2835
+ if (idx !== -1) this.persistedBatchKeys.splice(idx, 1);
2836
+ }
2837
+ // A page that is still running PENDING_BATCH_CLEANUP_DELAY_MS after an exit
2838
+ // flush was never torn down, so its beacons have long since gone out; drop
2839
+ // the persisted copies rather than letting a later pageview resend them.
2840
+ schedulePersistedCleanup() {
2841
+ if (this.persistedBatchKeys.length === 0) return;
2842
+ if (this.persistedCleanupTimer) clearTimeout(this.persistedCleanupTimer);
2843
+ this.persistedCleanupTimer = setTimeout(() => {
2844
+ this.persistedCleanupTimer = null;
2845
+ for (const key of Array.from(this.persistedBatchKeys)) {
2846
+ this.removePersistedBatch(key);
2847
+ }
2848
+ }, PENDING_BATCH_CLEANUP_DELAY_MS);
2849
+ }
2850
+ // Resend batches a previous pageview persisted at exit but could not
2851
+ // confirm. Only this property's own keys are claimed: batches belonging to
2852
+ // another property sharing this origin are left for that property's next
2853
+ // pageview, since resending them here would file them under this property.
2854
+ // Keys are claimed (removed) before sending so concurrent tabs don't
2855
+ // double-send; the batch bytes carry their original pageview, session, and
2856
+ // event timestamps, so late rows attribute correctly.
2857
+ resendPersistedBatches() {
2858
+ try {
2859
+ if (typeof localStorage === "undefined" || !this.config.endpoint) return;
2860
+ if (!this.config.propertyId) return;
2861
+ const ownPrefix = this.pendingKeyPrefix();
2862
+ const keys = [];
2863
+ const legacyKeys = [];
2864
+ for (let i = 0; i < localStorage.length; i++) {
2865
+ const k = localStorage.key(i);
2866
+ if (!k || !k.startsWith(PENDING_BATCH_KEY_PREFIX)) continue;
2867
+ if (k.startsWith(ownPrefix)) {
2868
+ keys.push(k);
2869
+ } else if (isLegacyPendingKey(k)) {
2870
+ legacyKeys.push(k);
2871
+ }
2872
+ }
2873
+ for (const key of legacyKeys) {
3363
2874
  try {
3364
- for (const candidate of Array.from(document.querySelectorAll("[data-ad-slot]"))) {
3365
- if (candidate.getAttribute("data-ad-slot") === targetId) {
3366
- el = candidate;
3367
- break;
3368
- }
3369
- }
2875
+ localStorage.removeItem(key);
3370
2876
  } catch {
3371
2877
  }
3372
2878
  }
3373
- if (el) {
3374
- this.observeSlot(el, adUnitCode || targetId, {
3375
- adUnitCode,
3376
- auctionId: resolvedAuctionId,
3377
- transactionId: resolvedTransactionId,
3378
- mediaType: resolvedMediaType,
3379
- bid: bidTrace,
3380
- refreshIndex: currentRefreshIndex,
3381
- emitRefreshEvent: false
3382
- });
2879
+ for (const key of keys) {
2880
+ let value = null;
2881
+ try {
2882
+ value = localStorage.getItem(key);
2883
+ localStorage.removeItem(key);
2884
+ } catch {
2885
+ continue;
2886
+ }
2887
+ if (!value) continue;
2888
+ const ts = parseInt(key.slice(ownPrefix.length), 10);
2889
+ if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
2890
+ let encoded;
2891
+ try {
2892
+ encoded = base64ToBytes(value);
2893
+ } catch {
2894
+ continue;
2895
+ }
2896
+ if (encoded.byteLength === 0) continue;
2897
+ this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
2898
+ this.sendFetch(
2899
+ {
2900
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
2901
+ encoded,
2902
+ events: []
2903
+ },
2904
+ false,
2905
+ void 0,
2906
+ true
2907
+ );
3383
2908
  }
2909
+ } catch {
3384
2910
  }
3385
2911
  }
3386
- isDuplicate(eventName, data) {
3387
- return this.deduper.isDuplicate(eventName, data);
2912
+ log(level, msg, ...args) {
2913
+ this.logger.log(level, msg, ...args);
3388
2914
  }
3389
- enqueue(type, eventName, data, level) {
3390
- if (!this.isEnabled) return;
3391
- if (!this.shouldSample(type, level)) return;
3392
- extendSession();
3393
- const protoEvent = {
3394
- eventId: generateUUID(),
3395
- timestampMs: Date.now(),
3396
- type,
3397
- eventName,
3398
- auctionId: data.auctionId || "",
3399
- transactionId: data.transactionId || "",
3400
- adUnitCode: data.adUnitCode || data.elementId || "",
3401
- namespace: data.ns || data.namespace || ""
3402
- };
3403
- if (data.bid) {
3404
- protoEvent.bid = {
3405
- bidder: data.bid.bidder || "",
3406
- cpm: Number.isFinite(data.bid.cpm) ? data.bid.cpm : 0,
3407
- // No defaults here: handlers with a priced bid (bidResponse, bidWon,
3408
- // impression) set currency/mediaType themselves. Defaulting for the
3409
- // rest would stamp fake "USD"/"banner" on noBid and bidRequest rows.
3410
- currency: data.bid.currency || "",
3411
- width: Number.isFinite(data.bid.width) ? data.bid.width : 0,
3412
- height: Number.isFinite(data.bid.height) ? data.bid.height : 0,
3413
- dealId: data.bid.dealId || "",
3414
- mediaType: data.bid.mediaType || "",
3415
- latencyMs: Number.isFinite(data.bid.latencyMs) ? data.bid.latencyMs : 0,
3416
- advertiserDomain: data.bid.advertiserDomain || "",
3417
- creativeId: data.bid.creativeId || ""
3418
- };
2915
+ };
2916
+ var defaultLogger = createLogger({
2917
+ prefix: "[bidkernel]",
2918
+ logLevel: "INFO"
2919
+ });
2920
+ function extractAnalyticsOptions(config, allowProviderless = true) {
2921
+ if (!config) return null;
2922
+ if (Array.isArray(config)) {
2923
+ const matched = config.find((item) => item?.provider === "bidkernel") ?? (allowProviderless ? config.find(
2924
+ (item) => item && typeof item === "object" && item.provider === void 0
2925
+ ) : void 0);
2926
+ return matched ? extractAnalyticsOptions(matched, allowProviderless) : null;
2927
+ }
2928
+ if (typeof config === "object") {
2929
+ if (config.options && typeof config.options === "object") {
2930
+ return config.options;
3419
2931
  }
3420
- if (data.viewableDurationMs !== void 0) {
3421
- protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
2932
+ if (config.config && typeof config.config === "object") {
2933
+ return config.config;
3422
2934
  }
3423
- if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
3424
- protoEvent.bidderOutcomes = data.bidderOutcomes;
2935
+ if (config.propertyId || config.endpoint) {
2936
+ return config;
3425
2937
  }
3426
- const metadata = { ...data.metadata };
3427
- if (data.error !== void 0 && data.error !== null) {
3428
- metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
2938
+ }
2939
+ return null;
2940
+ }
2941
+ function getStashedConfig(win) {
2942
+ if (!win) return null;
2943
+ if (win._bidkernelAnalyticsConfig) {
2944
+ const extracted = extractAnalyticsOptions(win._bidkernelAnalyticsConfig);
2945
+ if (extracted) return extracted;
2946
+ }
2947
+ if (win.bidkernelPrebidAnalytics) {
2948
+ const bpa = win.bidkernelPrebidAnalytics;
2949
+ if (bpa.options) {
2950
+ const extracted = extractAnalyticsOptions(bpa.options);
2951
+ if (extracted) return extracted;
3429
2952
  }
3430
- if (data.gpid) {
3431
- metadata.gpid = String(data.gpid);
2953
+ if (bpa.config) {
2954
+ const extracted = extractAnalyticsOptions(bpa.config);
2955
+ if (extracted) return extracted;
3432
2956
  }
3433
- if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
3434
- metadata.refresh_index = String(data.refreshIndex);
2957
+ if (bpa.propertyId) {
2958
+ return extractAnalyticsOptions(bpa);
3435
2959
  }
3436
- const boundedMetadata = boundMetadata(metadata);
3437
- if (Object.keys(boundedMetadata).length > 0) {
3438
- protoEvent.metadata = boundedMetadata;
2960
+ }
2961
+ return null;
2962
+ }
2963
+ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2964
+ const win = typeof window !== "undefined" ? window : {};
2965
+ win[pbjsGlobalName] = win[pbjsGlobalName] || {};
2966
+ const pbjs = win[pbjsGlobalName];
2967
+ const enableInstance = (options) => {
2968
+ const previous = win._bidkernelPrebidAnalytics;
2969
+ const pinnedEndpoint = previous?.getEndpoint?.() || "";
2970
+ const merged = {
2971
+ pbjsGlobalName,
2972
+ ...defaults,
2973
+ ...options
2974
+ };
2975
+ if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
2976
+ defaultLogger.warn(
2977
+ `Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
2978
+ );
2979
+ merged.endpoint = pinnedEndpoint;
3439
2980
  }
3440
- this.queue.push(protoEvent);
3441
- if (this.queue.length > MAX_QUEUE_SIZE) {
3442
- this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE);
2981
+ if (previous) {
2982
+ try {
2983
+ previous.disable();
2984
+ } catch (e) {
2985
+ defaultLogger.warn("Failed to disable previous analytics instance:", e);
2986
+ }
3443
2987
  }
3444
- if (this.queue.length >= BATCH_SIZE) {
3445
- this.flush();
3446
- } else if (REVENUE_FLUSH_TYPES.has(type) && this.revenueFlushTimer === null) {
3447
- this.revenueFlushTimer = setTimeout(() => {
3448
- this.revenueFlushTimer = null;
3449
- this.flush();
3450
- }, this.config.revenueFlushDelayMs);
2988
+ const analytics = new BidkernelPrebidAnalytics(merged);
2989
+ analytics.enable();
2990
+ win._bidkernelPrebidAnalytics = analytics;
2991
+ return analytics;
2992
+ };
2993
+ const doRegister = () => {
2994
+ try {
2995
+ if (pbjs.adapterManager && typeof pbjs.adapterManager.registerAnalyticsAdapter === "function") {
2996
+ pbjs.adapterManager.registerAnalyticsAdapter({
2997
+ adapter: {
2998
+ enableAnalytics: (config) => {
2999
+ const options = extractAnalyticsOptions(config, !Array.isArray(config));
3000
+ if (options) {
3001
+ enableInstance(options);
3002
+ }
3003
+ },
3004
+ disableAnalytics: () => {
3005
+ if (win._bidkernelPrebidAnalytics) {
3006
+ win._bidkernelPrebidAnalytics.disable();
3007
+ }
3008
+ }
3009
+ },
3010
+ code: "bidkernel"
3011
+ });
3012
+ }
3013
+ const stashed = getStashedConfig(win);
3014
+ if (stashed && (stashed.propertyId || defaults?.propertyId)) {
3015
+ enableInstance(stashed);
3016
+ }
3017
+ } catch (e) {
3018
+ defaultLogger.warn("Failed to register standard Prebid analytics adapter:", e);
3451
3019
  }
3020
+ };
3021
+ if (pbjs.adapterManager && typeof pbjs.adapterManager.registerAnalyticsAdapter === "function") {
3022
+ doRegister();
3023
+ } else {
3024
+ pbjs.que = pbjs.que || [];
3025
+ pbjs.que.push(doRegister);
3452
3026
  }
3453
- shouldSample(type, level) {
3454
- if (type === TraceEventType.ERROR) {
3455
- this.errorCount++;
3456
- if (this.errorCount > MAX_ERRORS_PER_SESSION) return false;
3457
- return this.config.errorsEnabled;
3458
- }
3459
- if (level === "WARN") {
3460
- return this.config.warningsEnabled;
3027
+ }
3028
+
3029
+ // src/video.ts
3030
+ var internals = (analytics) => analytics;
3031
+ var adsLoaderListenerMap = /* @__PURE__ */ new WeakMap();
3032
+ function hookAdsManagerLoadedEvent(loadedEventCtor) {
3033
+ if (!loadedEventCtor) return;
3034
+ try {
3035
+ const loadedProto = loadedEventCtor.prototype;
3036
+ if (loadedProto && typeof loadedProto.getAdsManager === "function" && !loadedProto.getAdsManager.__bidkernelHooked) {
3037
+ const originalGetAdsManager = loadedProto.getAdsManager;
3038
+ const hookedGetAdsManager = function(...args) {
3039
+ const adsManager = originalGetAdsManager.apply(this, args);
3040
+ if (adsManager) {
3041
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
3042
+ try {
3043
+ instance.attachImaAdsManager(adsManager);
3044
+ } catch {
3045
+ }
3046
+ }
3047
+ }
3048
+ return adsManager;
3049
+ };
3050
+ hookedGetAdsManager.__bidkernelHooked = true;
3051
+ loadedProto.getAdsManager = hookedGetAdsManager;
3461
3052
  }
3462
- return this.config.auctionEnabled;
3053
+ } catch {
3463
3054
  }
3464
- requeue(events) {
3465
- if (!events.length) return;
3466
- this.queue = events.concat(this.queue);
3467
- if (this.queue.length > MAX_QUEUE_SIZE) {
3468
- this.queue.splice(0, this.queue.length - MAX_QUEUE_SIZE);
3055
+ }
3056
+ function hookAdsManagerPrototype(adsManagerCtorOrProto) {
3057
+ if (!adsManagerCtorOrProto) return;
3058
+ try {
3059
+ const proto = adsManagerCtorOrProto.prototype || adsManagerCtorOrProto;
3060
+ if (proto && typeof proto.init === "function" && !proto.init.__bidkernelHooked) {
3061
+ const origInit = proto.init;
3062
+ const hookedInit = function(...args) {
3063
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
3064
+ try {
3065
+ instance.attachImaAdsManager(this);
3066
+ } catch {
3067
+ }
3068
+ }
3069
+ return origInit.apply(this, args);
3070
+ };
3071
+ hookedInit.__bidkernelHooked = true;
3072
+ proto.init = hookedInit;
3073
+ }
3074
+ if (proto && typeof proto.start === "function" && !proto.start.__bidkernelHooked) {
3075
+ const origStart = proto.start;
3076
+ const hookedStart = function(...args) {
3077
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
3078
+ try {
3079
+ instance.attachImaAdsManager(this);
3080
+ } catch {
3081
+ }
3082
+ }
3083
+ return origStart.apply(this, args);
3084
+ };
3085
+ hookedStart.__bidkernelHooked = true;
3086
+ proto.start = hookedStart;
3469
3087
  }
3088
+ } catch {
3470
3089
  }
3471
- // Drains a batch (up to MAX_PAYLOAD_BYTES) from the queue.
3472
- // Shared by flush() and flushBeacon().
3473
- drainBatch() {
3474
- if (this.queue.length === 0 || !this.config.endpoint) return null;
3475
- const domain = typeof window !== "undefined" ? window.location.hostname : "";
3476
- const buildBatch = (evts) => ({
3477
- propertyId: this.config.propertyId,
3478
- pageviewId: this.config.pageviewId,
3479
- sessionId: this.config.sessionId,
3480
- pageUrl: this.pageUrl,
3481
- country: this.config.country,
3482
- region: this.config.region,
3483
- deviceType: this.config.deviceType,
3484
- userId: this.config.userId,
3485
- domain,
3486
- events: evts
3487
- });
3488
- for (; ; ) {
3489
- if (this.queue.length === 0) return null;
3490
- let events = this.queue;
3491
- let encoded = TraceEventBatch.encode(
3492
- buildBatch(events)
3493
- ).finish();
3494
- while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
3495
- events = events.slice(0, Math.floor(events.length / 2));
3496
- encoded = TraceEventBatch.encode(
3497
- buildBatch(events)
3498
- ).finish();
3499
- }
3500
- if (encoded.byteLength > MAX_PAYLOAD_BYTES) {
3501
- const [dropped] = this.queue.splice(0, 1);
3502
- this.log(
3503
- "WARN",
3504
- `Dropping oversized event ${dropped?.eventName || dropped?.eventId} (${encoded.byteLength} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte cap on its own)`
3505
- );
3506
- continue;
3507
- }
3508
- this.queue = this.queue.slice(events.length);
3509
- return {
3510
- url: `${this.config.endpoint}/${this.config.propertyId}`,
3511
- // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
3512
- // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
3513
- encoded,
3514
- events
3090
+ }
3091
+ function hookAdsLoader(adsLoaderCtor) {
3092
+ if (!adsLoaderCtor) return;
3093
+ try {
3094
+ const proto = adsLoaderCtor.prototype;
3095
+ if (!proto) return;
3096
+ if (typeof proto.addEventListener === "function" && !proto.addEventListener.__bidkernelHooked) {
3097
+ const origAddEventListener = proto.addEventListener;
3098
+ const hookedAddEventListener = function(type, listener, ...rest) {
3099
+ if ((type === "adsManagerLoaded" || type === (this?.AdsManagerLoadedEvent?.Type?.ADS_MANAGER_LOADED || "adsManagerLoaded")) && typeof listener === "function") {
3100
+ let wrappedListener = adsLoaderListenerMap.get(listener);
3101
+ if (!wrappedListener) {
3102
+ wrappedListener = function(event) {
3103
+ if (event && typeof event.getAdsManager === "function" && !event.getAdsManager.__bidkernelHooked) {
3104
+ const origGetAdsManager = event.getAdsManager;
3105
+ event.getAdsManager = function(...args) {
3106
+ const adsManager = origGetAdsManager.apply(this, args);
3107
+ if (adsManager) {
3108
+ for (const sdk of BidkernelPrebidAnalytics.getActiveInstances()) {
3109
+ try {
3110
+ sdk.attachImaAdsManager(adsManager);
3111
+ } catch {
3112
+ }
3113
+ }
3114
+ }
3115
+ return adsManager;
3116
+ };
3117
+ event.getAdsManager.__bidkernelHooked = true;
3118
+ }
3119
+ return listener.apply(this, arguments);
3120
+ };
3121
+ adsLoaderListenerMap.set(listener, wrappedListener);
3122
+ }
3123
+ return origAddEventListener.call(this, type, wrappedListener, ...rest);
3124
+ }
3125
+ return origAddEventListener.call(this, type, listener, ...rest);
3126
+ };
3127
+ hookedAddEventListener.__bidkernelHooked = true;
3128
+ proto.addEventListener = hookedAddEventListener;
3129
+ }
3130
+ if (typeof proto.removeEventListener === "function" && !proto.removeEventListener.__bidkernelHooked) {
3131
+ const origRemoveEventListener = proto.removeEventListener;
3132
+ const hookedRemoveEventListener = function(type, listener, ...rest) {
3133
+ const wrapped = typeof listener === "function" ? adsLoaderListenerMap.get(listener) : void 0;
3134
+ return origRemoveEventListener.call(this, type, wrapped || listener, ...rest);
3515
3135
  };
3136
+ hookedRemoveEventListener.__bidkernelHooked = true;
3137
+ proto.removeEventListener = hookedRemoveEventListener;
3516
3138
  }
3139
+ } catch {
3517
3140
  }
3518
- sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
3519
- const fetchOpts = {
3520
- method: "POST",
3521
- // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
3522
- headers: { "Content-Type": SAFE_CONTENT_TYPE },
3523
- body: payload.encoded
3524
- };
3525
- if (useKeepalive) {
3526
- fetchOpts.keepalive = true;
3141
+ }
3142
+ function hookImaPrototype(ima) {
3143
+ if (!ima || typeof ima !== "object") return;
3144
+ try {
3145
+ if (ima.AdsManagerLoadedEvent) {
3146
+ hookAdsManagerLoadedEvent(ima.AdsManagerLoadedEvent);
3527
3147
  }
3528
- fetch(payload.url, fetchOpts).then((res) => {
3529
- if (!res.ok) {
3530
- this.log("WARN", `Failed to send batch: HTTP ${res.status}`);
3531
- if (res.status >= 400 && res.status < 500 && res.status !== 429 && res.status !== 408) {
3532
- this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
3533
- return;
3534
- }
3535
- if (!isReplay) this.handleSendFailure(payload.events);
3536
- } else {
3537
- if (!isReplay) {
3538
- this.consecutiveSendFailures = 0;
3539
- this.nextSendAllowedAt = 0;
3540
- }
3541
- if (onDelivered) onDelivered();
3542
- }
3543
- }).catch((err) => {
3544
- this.log("ERROR", "Failed to send batch", err);
3545
- if (!isReplay) this.handleSendFailure(payload.events);
3546
- });
3547
- const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
3548
- if (proc && typeof proc._tickCallback === "function") {
3148
+ const loadedDesc = Object.getOwnPropertyDescriptor(ima, "AdsManagerLoadedEvent");
3149
+ if (!loadedDesc || loadedDesc.configurable) {
3150
+ let _loadedEvent = ima.AdsManagerLoadedEvent;
3549
3151
  try {
3550
- proc._tickCallback();
3152
+ Object.defineProperty(ima, "AdsManagerLoadedEvent", {
3153
+ configurable: true,
3154
+ enumerable: true,
3155
+ get() {
3156
+ return _loadedEvent;
3157
+ },
3158
+ set(val) {
3159
+ _loadedEvent = val;
3160
+ hookAdsManagerLoadedEvent(val);
3161
+ }
3162
+ });
3551
3163
  } catch {
3552
3164
  }
3553
3165
  }
3554
- }
3555
- handleSendFailure(events) {
3556
- this.consecutiveSendFailures++;
3557
- const backoff = Math.min(
3558
- FLUSH_INTERVAL_MS * 2 ** (this.consecutiveSendFailures - 1),
3559
- MAX_SEND_BACKOFF_MS
3560
- );
3561
- this.nextSendAllowedAt = Date.now() + backoff;
3562
- if (this.consecutiveSendFailures <= MAX_CONSECUTIVE_SEND_FAILURES) {
3563
- this.requeue(events);
3564
- } else {
3565
- this.log(
3566
- "WARN",
3567
- `Dropping ${events.length} events after ${this.consecutiveSendFailures} consecutive send failures`
3568
- );
3569
- }
3570
- }
3571
- flush() {
3572
- if (Date.now() < this.nextSendAllowedAt) return;
3573
- let payload = this.drainBatch();
3574
- while (payload) {
3575
- this.sendFetch(payload, false);
3576
- if (Date.now() < this.nextSendAllowedAt) break;
3577
- payload = this.drainBatch();
3166
+ if (ima.AdsLoader) {
3167
+ hookAdsLoader(ima.AdsLoader);
3578
3168
  }
3579
- }
3580
- flushBeacon() {
3581
- let payload = this.drainBatch();
3582
- while (payload) {
3583
- let sent = false;
3584
- if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
3585
- try {
3586
- const blob = new Blob([payload.encoded], { type: SAFE_CONTENT_TYPE });
3587
- sent = navigator.sendBeacon(payload.url, blob);
3588
- } catch {
3589
- sent = false;
3590
- }
3169
+ const loaderDesc = Object.getOwnPropertyDescriptor(ima, "AdsLoader");
3170
+ if (!loaderDesc || loaderDesc.configurable) {
3171
+ let _loader = ima.AdsLoader;
3172
+ try {
3173
+ Object.defineProperty(ima, "AdsLoader", {
3174
+ configurable: true,
3175
+ enumerable: true,
3176
+ get() {
3177
+ return _loader;
3178
+ },
3179
+ set(val) {
3180
+ _loader = val;
3181
+ hookAdsLoader(val);
3182
+ }
3183
+ });
3184
+ } catch {
3591
3185
  }
3592
- if (!sent) {
3593
- const persistKey = this.persistBatch(payload.encoded);
3594
- this.sendFetch(payload, true, () => this.removePersistedBatch(persistKey));
3186
+ }
3187
+ if (ima.AdsManager) {
3188
+ hookAdsManagerPrototype(ima.AdsManager);
3189
+ }
3190
+ const mgrDesc = Object.getOwnPropertyDescriptor(ima, "AdsManager");
3191
+ if (!mgrDesc || mgrDesc.configurable) {
3192
+ let _mgr = ima.AdsManager;
3193
+ try {
3194
+ Object.defineProperty(ima, "AdsManager", {
3195
+ configurable: true,
3196
+ enumerable: true,
3197
+ get() {
3198
+ return _mgr;
3199
+ },
3200
+ set(val) {
3201
+ _mgr = val;
3202
+ hookAdsManagerPrototype(val);
3203
+ }
3204
+ });
3205
+ } catch {
3595
3206
  }
3596
- payload = this.drainBatch();
3597
3207
  }
3598
- this.schedulePersistedCleanup();
3208
+ } catch {
3599
3209
  }
3600
- // --- Exit-batch persistence -----------------------------------------------
3601
- // Key prefix for this instance's property. encodeURIComponent keeps the
3602
- // property segment free of the "_" the timestamp suffix is parsed against,
3603
- // so a property ID containing one cannot forge another property's prefix.
3604
- pendingKeyPrefix() {
3605
- return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
3210
+ }
3211
+ function initImaInterception() {
3212
+ if (typeof window === "undefined") return;
3213
+ const win = window;
3214
+ if (win.google?.ima) {
3215
+ hookImaPrototype(win.google.ima);
3606
3216
  }
3607
- persistBatch(encoded) {
3608
- if (!storageAllowed) return null;
3609
- try {
3610
- if (typeof localStorage === "undefined") return null;
3611
- if (!this.config.propertyId) return null;
3612
- let pendingCount = 0;
3613
- for (let i = 0; i < localStorage.length; i++) {
3614
- const k = localStorage.key(i);
3615
- if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
3217
+ if (win.google && typeof win.google === "object") {
3218
+ const imaDesc = Object.getOwnPropertyDescriptor(win.google, "ima");
3219
+ if (!imaDesc || imaDesc.configurable) {
3220
+ let _ima = win.google.ima;
3221
+ try {
3222
+ Object.defineProperty(win.google, "ima", {
3223
+ configurable: true,
3224
+ enumerable: true,
3225
+ get() {
3226
+ return _ima;
3227
+ },
3228
+ set(val) {
3229
+ _ima = val;
3230
+ hookImaPrototype(val);
3231
+ }
3232
+ });
3233
+ } catch {
3616
3234
  }
3617
- if (pendingCount >= MAX_PENDING_BATCHES) return null;
3618
- const key = `${this.pendingKeyPrefix()}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
3619
- localStorage.setItem(key, bytesToBase64(encoded));
3620
- this.persistedBatchKeys.push(key);
3621
- return key;
3622
- } catch {
3623
- return null;
3624
3235
  }
3625
3236
  }
3626
- removePersistedBatch(key) {
3627
- if (!key) return;
3237
+ const googleDesc = Object.getOwnPropertyDescriptor(win, "google");
3238
+ if (!googleDesc || googleDesc.configurable) {
3239
+ let _google = win.google;
3628
3240
  try {
3629
- if (typeof localStorage !== "undefined") localStorage.removeItem(key);
3241
+ Object.defineProperty(win, "google", {
3242
+ configurable: true,
3243
+ enumerable: true,
3244
+ get() {
3245
+ return _google;
3246
+ },
3247
+ set(val) {
3248
+ _google = val;
3249
+ if (_google && typeof _google === "object") {
3250
+ if (_google.ima) {
3251
+ hookImaPrototype(_google.ima);
3252
+ }
3253
+ const iDesc = Object.getOwnPropertyDescriptor(_google, "ima");
3254
+ if (!iDesc || iDesc.configurable) {
3255
+ let _i = _google.ima;
3256
+ try {
3257
+ Object.defineProperty(_google, "ima", {
3258
+ configurable: true,
3259
+ enumerable: true,
3260
+ get() {
3261
+ return _i;
3262
+ },
3263
+ set(iv) {
3264
+ _i = iv;
3265
+ hookImaPrototype(iv);
3266
+ }
3267
+ });
3268
+ } catch {
3269
+ }
3270
+ }
3271
+ }
3272
+ }
3273
+ });
3630
3274
  } catch {
3631
3275
  }
3632
- const idx = this.persistedBatchKeys.indexOf(key);
3633
- if (idx !== -1) this.persistedBatchKeys.splice(idx, 1);
3634
3276
  }
3635
- // A page that is still running PENDING_BATCH_CLEANUP_DELAY_MS after an exit
3636
- // flush was never torn down, so its beacons have long since gone out; drop
3637
- // the persisted copies rather than letting a later pageview resend them.
3638
- schedulePersistedCleanup() {
3639
- if (this.persistedBatchKeys.length === 0) return;
3640
- if (this.persistedCleanupTimer) clearTimeout(this.persistedCleanupTimer);
3641
- this.persistedCleanupTimer = setTimeout(() => {
3642
- this.persistedCleanupTimer = null;
3643
- for (const key of Array.from(this.persistedBatchKeys)) {
3644
- this.removePersistedBatch(key);
3277
+ }
3278
+ if (typeof window !== "undefined") {
3279
+ initImaInterception();
3280
+ }
3281
+ function findCachedWinningBid(analytics, filter) {
3282
+ const self = internals(analytics);
3283
+ const now = Date.now();
3284
+ const isConsumed = (entry) => {
3285
+ return self.hasImpressionEmitted(
3286
+ entry.adUnitCode,
3287
+ void 0,
3288
+ entry.auctionId,
3289
+ entry.bidTrace.creativeId
3290
+ );
3291
+ };
3292
+ if (filter?.creativeId || filter?.adId) {
3293
+ for (const entry of Array.from(self.cachedWinningBids.values())) {
3294
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
3295
+ if (filter.creativeId && (entry.bidTrace.creativeId === filter.creativeId || entry.rawBid?.creativeId === filter.creativeId) || filter.adId && (entry.rawBid?.adId === filter.adId || entry.adUnitCode === filter.adId)) {
3296
+ return entry;
3645
3297
  }
3646
- }, PENDING_BATCH_CLEANUP_DELAY_MS);
3298
+ }
3647
3299
  }
3648
- // Resend batches a previous pageview persisted at exit but could not
3649
- // confirm. Only this property's own keys are claimed: batches belonging to
3650
- // another property sharing this origin are left for that property's next
3651
- // pageview, since resending them here would file them under this property.
3652
- // Keys are claimed (removed) before sending so concurrent tabs don't
3653
- // double-send; the batch bytes carry their original pageview, session, and
3654
- // event timestamps, so late rows attribute correctly.
3655
- resendPersistedBatches() {
3656
- try {
3657
- if (typeof localStorage === "undefined" || !this.config.endpoint) return;
3658
- if (!this.config.propertyId) return;
3659
- const ownPrefix = this.pendingKeyPrefix();
3660
- const keys = [];
3661
- const legacyKeys = [];
3662
- for (let i = 0; i < localStorage.length; i++) {
3663
- const k = localStorage.key(i);
3664
- if (!k || !k.startsWith(PENDING_BATCH_KEY_PREFIX)) continue;
3665
- if (k.startsWith(ownPrefix)) {
3666
- keys.push(k);
3667
- } else if (isLegacyPendingKey(k)) {
3668
- legacyKeys.push(k);
3300
+ let bestUnconsumedMatch;
3301
+ if (filter?.mediaType) {
3302
+ for (const entry of Array.from(self.cachedWinningBids.values())) {
3303
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
3304
+ const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
3305
+ if (isEntryVideo) {
3306
+ if (!isConsumed(entry)) {
3307
+ if (!bestUnconsumedMatch || entry.timestamp < bestUnconsumedMatch.timestamp) {
3308
+ bestUnconsumedMatch = entry;
3309
+ }
3669
3310
  }
3670
3311
  }
3671
- for (const key of legacyKeys) {
3672
- try {
3673
- localStorage.removeItem(key);
3674
- } catch {
3312
+ }
3313
+ if (bestUnconsumedMatch) return bestUnconsumedMatch;
3314
+ let bestFallbackMatch;
3315
+ for (const entry of Array.from(self.cachedWinningBids.values())) {
3316
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
3317
+ const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
3318
+ if (isEntryVideo) {
3319
+ if (!bestFallbackMatch || entry.timestamp > bestFallbackMatch.timestamp) {
3320
+ bestFallbackMatch = entry;
3675
3321
  }
3676
3322
  }
3677
- for (const key of keys) {
3678
- let value = null;
3679
- try {
3680
- value = localStorage.getItem(key);
3681
- localStorage.removeItem(key);
3682
- } catch {
3683
- continue;
3684
- }
3685
- if (!value) continue;
3686
- const ts = parseInt(key.slice(ownPrefix.length), 10);
3687
- if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
3688
- let encoded;
3689
- try {
3690
- encoded = base64ToBytes(value);
3691
- } catch {
3692
- continue;
3693
- }
3694
- if (encoded.byteLength === 0) continue;
3695
- this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
3696
- this.sendFetch(
3697
- {
3698
- url: `${this.config.endpoint}/${this.config.propertyId}`,
3699
- encoded,
3700
- events: []
3701
- },
3702
- false,
3703
- void 0,
3704
- true
3705
- );
3323
+ }
3324
+ if (bestFallbackMatch) return bestFallbackMatch;
3325
+ }
3326
+ for (const entry of Array.from(self.cachedWinningBids.values())) {
3327
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
3328
+ if (!isConsumed(entry)) {
3329
+ if (!bestUnconsumedMatch || entry.timestamp > bestUnconsumedMatch.timestamp) {
3330
+ bestUnconsumedMatch = entry;
3706
3331
  }
3707
- } catch {
3708
3332
  }
3709
3333
  }
3710
- log(level, msg, ...args) {
3711
- this.logger.log(level, msg, ...args);
3334
+ if (bestUnconsumedMatch) return bestUnconsumedMatch;
3335
+ let bestRecent;
3336
+ for (const entry of Array.from(self.cachedWinningBids.values())) {
3337
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
3338
+ if (!bestRecent || entry.timestamp > bestRecent.timestamp) {
3339
+ bestRecent = entry;
3340
+ }
3712
3341
  }
3713
- };
3714
- var defaultLogger = createLogger({
3715
- prefix: "[bidkernel]",
3716
- logLevel: "INFO"
3717
- });
3718
- function extractAnalyticsOptions(config, allowProviderless = true) {
3719
- if (!config) return null;
3720
- if (Array.isArray(config)) {
3721
- const matched = config.find((item) => item?.provider === "bidkernel") ?? (allowProviderless ? config.find(
3722
- (item) => item && typeof item === "object" && item.provider === void 0
3723
- ) : void 0);
3724
- return matched ? extractAnalyticsOptions(matched, allowProviderless) : null;
3342
+ return bestRecent;
3343
+ }
3344
+ function attachImaAdsManager(analytics, adsManager, options) {
3345
+ const self = internals(analytics);
3346
+ if (!adsManager || typeof adsManager.addEventListener !== "function") {
3347
+ self.log("WARN", "Invalid AdsManager passed to attachImaAdsManager");
3348
+ return () => {
3349
+ };
3725
3350
  }
3726
- if (typeof config === "object") {
3727
- if (config.options && typeof config.options === "object") {
3728
- return config.options;
3351
+ if (!adsManager.__bidkernelAttachedInstances) {
3352
+ try {
3353
+ Object.defineProperty(adsManager, "__bidkernelAttachedInstances", {
3354
+ value: /* @__PURE__ */ new Map(),
3355
+ configurable: true,
3356
+ writable: true
3357
+ });
3358
+ } catch {
3359
+ adsManager.__bidkernelAttachedInstances = /* @__PURE__ */ new Map();
3729
3360
  }
3730
- if (config.config && typeof config.config === "object") {
3731
- return config.config;
3361
+ }
3362
+ const attached = adsManager.__bidkernelAttachedInstances;
3363
+ const previous = attached.get(analytics);
3364
+ if (previous) {
3365
+ if (!options) return () => {
3366
+ };
3367
+ previous();
3368
+ }
3369
+ const slotId = options?.slotId || options?.adUnitCode || "video";
3370
+ const adUnitCode = options?.adUnitCode || slotId;
3371
+ const auctionId = options?.auctionId || "";
3372
+ const transactionId = options?.transactionId || "";
3373
+ const getWinningBid = (adData) => {
3374
+ let cached = (auctionId ? self.getCachedBid(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? self.getCachedBid(`${auctionId}:${slotId}`) : void 0) || (adUnitCode !== "video" ? self.getCachedBid(adUnitCode) : void 0) || (slotId !== adUnitCode && slotId !== "video" ? self.getCachedBid(slotId) : void 0);
3375
+ if (!cached) {
3376
+ cached = findCachedWinningBid(analytics, {
3377
+ creativeId: adData?.creativeId,
3378
+ adId: adData?.adId,
3379
+ mediaType: "video"
3380
+ });
3732
3381
  }
3733
- if (config.propertyId || config.endpoint) {
3734
- return config;
3382
+ return cached;
3383
+ };
3384
+ let adStarted = false;
3385
+ const onAdStartedOrImpression = (event) => {
3386
+ adStarted = true;
3387
+ self.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
3388
+ const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
3389
+ const adData = {};
3390
+ if (ad) {
3391
+ adData.creativeId = (typeof ad.getCreativeId === "function" ? ad.getCreativeId() : ad.creativeId) || "";
3392
+ adData.adId = (typeof ad.getAdId === "function" ? ad.getAdId() : ad.id) || "";
3393
+ adData.title = (typeof ad.getTitle === "function" ? ad.getTitle() : ad.title) || "";
3394
+ adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
3395
+ adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
3396
+ }
3397
+ const cached = getWinningBid(adData);
3398
+ const resolvedSlotId = options?.slotId && options.slotId !== "video" ? options.slotId : options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || options?.slotId || options?.adUnitCode || "video";
3399
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || resolvedSlotId;
3400
+ const resolvedAuctionId = auctionId || cached?.auctionId || "";
3401
+ const resolvedTransactionId = transactionId || cached?.transactionId || "";
3402
+ const mergedBid = {
3403
+ ...cached?.rawBid || options?.bid,
3404
+ mediaType: "video",
3405
+ creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
3406
+ };
3407
+ const emitted = self.recordImpression(resolvedSlotId, {
3408
+ adUnitCode: resolvedAdUnitCode,
3409
+ auctionId: resolvedAuctionId,
3410
+ transactionId: resolvedTransactionId,
3411
+ mediaType: "video",
3412
+ bid: mergedBid,
3413
+ metadata: {
3414
+ ...options?.metadata,
3415
+ media_type: "video",
3416
+ ...adData.title ? { ad_title: adData.title } : {},
3417
+ ...Number.isFinite(adData.duration) ? { ad_duration_s: String(adData.duration) } : {}
3418
+ }
3419
+ });
3420
+ if (emitted && options?.onImpression) {
3421
+ try {
3422
+ options.onImpression(resolvedSlotId, { ad, bid: mergedBid });
3423
+ } catch (e) {
3424
+ self.log("ERROR", "Error in onImpression callback", e);
3425
+ }
3735
3426
  }
3736
- }
3737
- return null;
3738
- }
3739
- function getStashedConfig(win) {
3740
- if (!win) return null;
3741
- if (win._bidkernelAnalyticsConfig) {
3742
- const extracted = extractAnalyticsOptions(win._bidkernelAnalyticsConfig);
3743
- if (extracted) return extracted;
3744
- }
3745
- if (win.bidkernelPrebidAnalytics) {
3746
- const bpa = win.bidkernelPrebidAnalytics;
3747
- if (bpa.options) {
3748
- const extracted = extractAnalyticsOptions(bpa.options);
3749
- if (extracted) return extracted;
3427
+ };
3428
+ const onMilestone = (milestone) => {
3429
+ self.log("DEBUG", `IMA AdEvent milestone: ${milestone}`);
3430
+ if (options?.onMilestone) {
3431
+ try {
3432
+ options.onMilestone(milestone, slotId);
3433
+ } catch (e) {
3434
+ self.log("ERROR", "Error in onMilestone callback", e);
3435
+ }
3750
3436
  }
3751
- if (bpa.config) {
3752
- const extracted = extractAnalyticsOptions(bpa.config);
3753
- if (extracted) return extracted;
3437
+ };
3438
+ const onAdClick = (event) => {
3439
+ self.log("DEBUG", "IMA AdEvent.CLICK received", event);
3440
+ const cached = getWinningBid();
3441
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
3442
+ self.enqueue(TraceEventType.CLICK, "click", {
3443
+ auctionId: auctionId || cached?.auctionId,
3444
+ transactionId: transactionId || cached?.transactionId,
3445
+ adUnitCode: resolvedAdUnitCode,
3446
+ bid: cached?.bidTrace || options?.bid,
3447
+ metadata: {
3448
+ ...options?.metadata,
3449
+ media_type: "video"
3450
+ }
3451
+ });
3452
+ };
3453
+ const onAdError = (event) => {
3454
+ self.log("WARN", "IMA AdErrorEvent received", event);
3455
+ const err = typeof event?.getError === "function" ? event.getError() : event?.error || event;
3456
+ const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
3457
+ const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
3458
+ const cached = getWinningBid();
3459
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
3460
+ self.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
3461
+ auctionId: auctionId || cached?.auctionId,
3462
+ transactionId: transactionId || cached?.transactionId,
3463
+ adUnitCode: resolvedAdUnitCode,
3464
+ bid: cached?.bidTrace || options?.bid,
3465
+ metadata: {
3466
+ ...options?.metadata,
3467
+ reason: adStarted ? "ima_playback_error" : "ima_ad_error",
3468
+ playback_phase: adStarted ? "post_start" : "pre_start",
3469
+ message: msg,
3470
+ ...code ? { ima_error_code: String(code) } : {}
3471
+ },
3472
+ error: typeof err === "object" ? err : new Error(msg)
3473
+ });
3474
+ if (options?.onError) {
3475
+ try {
3476
+ options.onError(err);
3477
+ } catch (e) {
3478
+ self.log("ERROR", "Error in onError callback", e);
3479
+ }
3754
3480
  }
3755
- if (bpa.propertyId) {
3756
- return extractAnalyticsOptions(bpa);
3481
+ };
3482
+ const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
3483
+ const adEventType = googleIma?.AdEvent?.Type || {};
3484
+ const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
3485
+ const rawListeners = [
3486
+ { type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
3487
+ { type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
3488
+ {
3489
+ type: adEventType.FIRST_QUARTILE || "firstQuartile",
3490
+ handler: () => onMilestone("firstQuartile")
3491
+ },
3492
+ { type: adEventType.MIDPOINT || "midpoint", handler: () => onMilestone("midpoint") },
3493
+ {
3494
+ type: adEventType.THIRD_QUARTILE || "thirdQuartile",
3495
+ handler: () => onMilestone("thirdQuartile")
3496
+ },
3497
+ { type: adEventType.COMPLETE || "complete", handler: () => onMilestone("complete") },
3498
+ { type: adEventType.CLICK || "click", handler: onAdClick },
3499
+ { type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
3500
+ ];
3501
+ const seenListenerTypes = /* @__PURE__ */ new Set();
3502
+ const listeners = [];
3503
+ for (const l of rawListeners) {
3504
+ if (!seenListenerTypes.has(l.type)) {
3505
+ seenListenerTypes.add(l.type);
3506
+ listeners.push(l);
3507
+ }
3508
+ }
3509
+ for (const { type, handler } of listeners) {
3510
+ try {
3511
+ adsManager.addEventListener(type, handler);
3512
+ } catch {
3757
3513
  }
3758
3514
  }
3759
- return null;
3515
+ const cleanup = () => {
3516
+ attached.delete(analytics);
3517
+ for (const { type, handler } of listeners) {
3518
+ try {
3519
+ adsManager.removeEventListener(type, handler);
3520
+ } catch {
3521
+ }
3522
+ }
3523
+ self.videoDetachCleanups.delete(cleanup);
3524
+ };
3525
+ attached.set(analytics, cleanup);
3526
+ self.videoDetachCleanups.add(cleanup);
3527
+ return cleanup;
3760
3528
  }
3761
- function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
3762
- const win = typeof window !== "undefined" ? window : {};
3763
- win[pbjsGlobalName] = win[pbjsGlobalName] || {};
3764
- const pbjs = win[pbjsGlobalName];
3765
- const enableInstance = (options) => {
3766
- const previous = win._bidkernelPrebidAnalytics;
3767
- const pinnedEndpoint = previous?.getEndpoint?.() || "";
3768
- const merged = {
3769
- pbjsGlobalName,
3770
- ...defaults,
3771
- ...options
3529
+ function attachVideoPlayer(analytics, target, options) {
3530
+ const self = internals(analytics);
3531
+ if (!target) {
3532
+ self.log("WARN", "attachVideoPlayer called with null/undefined target");
3533
+ return () => {
3772
3534
  };
3773
- if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
3774
- defaultLogger.warn(
3775
- `Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
3776
- );
3777
- merged.endpoint = pinnedEndpoint;
3535
+ }
3536
+ let el = null;
3537
+ if (typeof target === "string") {
3538
+ if (typeof document !== "undefined") {
3539
+ try {
3540
+ el = document.querySelector(target) || document.getElementById(target);
3541
+ } catch {
3542
+ el = document.getElementById(target);
3543
+ }
3544
+ }
3545
+ } else if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
3546
+ el = target;
3547
+ } else if (target && typeof target === "object" && target.nodeType === 1) {
3548
+ el = target;
3549
+ } else if (
3550
+ // Check if target is an IMA AdsManager
3551
+ typeof target.addEventListener === "function" && (typeof target.init === "function" || typeof target.start === "function" || typeof target.getCuePoints === "function" || typeof target.getVolume === "function" || target.__imaAdsManager)
3552
+ ) {
3553
+ return attachImaAdsManager(analytics, target, options);
3554
+ } else if (target && typeof target.addEventListener === "function") {
3555
+ el = target;
3556
+ }
3557
+ const slotId = options?.slotId || options?.adUnitCode || (el ? el.id : "") || "video";
3558
+ const adUnitCode = options?.adUnitCode || slotId;
3559
+ const auctionId = options?.auctionId || "";
3560
+ const transactionId = options?.transactionId || "";
3561
+ const getWinningBid = () => {
3562
+ return (auctionId ? self.getCachedBid(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? self.getCachedBid(`${auctionId}:${slotId}`) : void 0) || self.getCachedBid(adUnitCode) || (slotId !== adUnitCode ? self.getCachedBid(slotId) : void 0);
3563
+ };
3564
+ let videoEl = null;
3565
+ if (el) {
3566
+ if (el.tagName && el.tagName.toLowerCase() === "video") {
3567
+ videoEl = el;
3568
+ } else {
3569
+ videoEl = el.querySelector("video");
3778
3570
  }
3779
- if (previous) {
3571
+ }
3572
+ const quartilesFired = {
3573
+ q1: false,
3574
+ q2: false,
3575
+ q3: false,
3576
+ q4: false
3577
+ };
3578
+ const triggerPlaybackImpression = (activeVideo) => {
3579
+ const vEl = activeVideo || videoEl || (el?.querySelector ? el.querySelector("video") : null);
3580
+ const cached = getWinningBid();
3581
+ const mergedBid = {
3582
+ ...cached?.rawBid || options?.bid,
3583
+ mediaType: "video"
3584
+ };
3585
+ const emitted = self.recordImpression(slotId, {
3586
+ adUnitCode,
3587
+ auctionId: auctionId || cached?.auctionId,
3588
+ transactionId: transactionId || cached?.transactionId,
3589
+ mediaType: "video",
3590
+ bid: mergedBid,
3591
+ metadata: {
3592
+ ...options?.metadata,
3593
+ media_type: "video",
3594
+ ...vEl && Number.isFinite(vEl.duration) ? { video_duration_s: String(Math.round(vEl.duration)) } : {}
3595
+ }
3596
+ });
3597
+ if (emitted && options?.onImpression) {
3780
3598
  try {
3781
- previous.disable();
3599
+ options.onImpression(slotId, { element: vEl || el, bid: mergedBid });
3782
3600
  } catch (e) {
3783
- defaultLogger.warn("Failed to disable previous analytics instance:", e);
3601
+ self.log("ERROR", "Error in onImpression callback", e);
3784
3602
  }
3785
3603
  }
3786
- const analytics = new BidkernelPrebidAnalytics(merged);
3787
- analytics.enable();
3788
- win._bidkernelPrebidAnalytics = analytics;
3789
- return analytics;
3790
3604
  };
3791
- const doRegister = () => {
3792
- try {
3793
- if (pbjs.adapterManager && typeof pbjs.adapterManager.registerAnalyticsAdapter === "function") {
3794
- pbjs.adapterManager.registerAnalyticsAdapter({
3795
- adapter: {
3796
- enableAnalytics: (config) => {
3797
- const options = extractAnalyticsOptions(config, !Array.isArray(config));
3798
- if (options) {
3799
- enableInstance(options);
3800
- }
3801
- },
3802
- disableAnalytics: () => {
3803
- if (win._bidkernelPrebidAnalytics) {
3804
- win._bidkernelPrebidAnalytics.disable();
3805
- }
3806
- }
3807
- },
3808
- code: "bidkernel"
3809
- });
3605
+ const handlePlaying = (e) => {
3606
+ self.log("DEBUG", "HTML5 video playing event received");
3607
+ const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
3608
+ triggerPlaybackImpression(targetVideo);
3609
+ };
3610
+ const handleTimeUpdate = (e) => {
3611
+ const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
3612
+ if (targetVideo && targetVideo.currentTime > 0) {
3613
+ triggerPlaybackImpression(targetVideo);
3614
+ if (Number.isFinite(targetVideo.duration) && targetVideo.duration > 0) {
3615
+ const progress = targetVideo.currentTime / targetVideo.duration;
3616
+ if (progress >= 0.25 && !quartilesFired.q1) {
3617
+ quartilesFired.q1 = true;
3618
+ options?.onMilestone?.("firstQuartile", slotId);
3619
+ }
3620
+ if (progress >= 0.5 && !quartilesFired.q2) {
3621
+ quartilesFired.q2 = true;
3622
+ options?.onMilestone?.("midpoint", slotId);
3623
+ }
3624
+ if (progress >= 0.75 && !quartilesFired.q3) {
3625
+ quartilesFired.q3 = true;
3626
+ options?.onMilestone?.("thirdQuartile", slotId);
3627
+ }
3810
3628
  }
3811
- const stashed = getStashedConfig(win);
3812
- if (stashed && (stashed.propertyId || defaults?.propertyId)) {
3813
- enableInstance(stashed);
3629
+ }
3630
+ };
3631
+ const handleEnded = () => {
3632
+ if (!quartilesFired.q4) {
3633
+ quartilesFired.q4 = true;
3634
+ options?.onMilestone?.("complete", slotId);
3635
+ }
3636
+ };
3637
+ const handleError = (e) => {
3638
+ const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
3639
+ const err = targetVideo?.error || videoEl?.error;
3640
+ const cached = getWinningBid();
3641
+ const msg = err ? `HTML5 video error code ${err.code}: ${err.message}` : "Video playback error";
3642
+ self.log("WARN", msg);
3643
+ self.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
3644
+ auctionId: auctionId || cached?.auctionId,
3645
+ transactionId: transactionId || cached?.transactionId,
3646
+ adUnitCode,
3647
+ bid: cached?.bidTrace || options?.bid,
3648
+ metadata: {
3649
+ ...options?.metadata,
3650
+ reason: "html5_video_error",
3651
+ message: msg,
3652
+ ...err?.code ? { video_error_code: String(err.code) } : {}
3653
+ },
3654
+ error: err ? new Error(msg) : void 0
3655
+ });
3656
+ if (options?.onError) {
3657
+ try {
3658
+ options.onError(err);
3659
+ } catch (e2) {
3660
+ self.log("ERROR", "Error in onError callback", e2);
3814
3661
  }
3815
- } catch (e) {
3816
- defaultLogger.warn("Failed to register standard Prebid analytics adapter:", e);
3817
3662
  }
3818
3663
  };
3819
- if (pbjs.adapterManager && typeof pbjs.adapterManager.registerAnalyticsAdapter === "function") {
3820
- doRegister();
3821
- } else {
3822
- pbjs.que = pbjs.que || [];
3823
- pbjs.que.push(doRegister);
3664
+ const attachListenersToVideo = (targetVideo) => {
3665
+ targetVideo.addEventListener("playing", handlePlaying);
3666
+ targetVideo.addEventListener("play", handlePlaying);
3667
+ targetVideo.addEventListener("timeupdate", handleTimeUpdate);
3668
+ targetVideo.addEventListener("ended", handleEnded);
3669
+ targetVideo.addEventListener("error", handleError);
3670
+ };
3671
+ const removeListenersFromVideo = (targetVideo) => {
3672
+ targetVideo.removeEventListener("playing", handlePlaying);
3673
+ targetVideo.removeEventListener("play", handlePlaying);
3674
+ targetVideo.removeEventListener("timeupdate", handleTimeUpdate);
3675
+ targetVideo.removeEventListener("ended", handleEnded);
3676
+ targetVideo.removeEventListener("error", handleError);
3677
+ };
3678
+ let mutationObserver = null;
3679
+ if (el && el !== videoEl) {
3680
+ el.addEventListener("playing", handlePlaying, true);
3681
+ el.addEventListener("play", handlePlaying, true);
3682
+ el.addEventListener("timeupdate", handleTimeUpdate, true);
3683
+ el.addEventListener("ended", handleEnded, true);
3684
+ el.addEventListener("error", handleError, true);
3685
+ }
3686
+ if (videoEl) {
3687
+ attachListenersToVideo(videoEl);
3688
+ }
3689
+ if (el && el.tagName?.toLowerCase() !== "video" && typeof MutationObserver !== "undefined") {
3690
+ mutationObserver = new MutationObserver(() => {
3691
+ const found = el.querySelector("video");
3692
+ if (found && found !== videoEl) {
3693
+ if (videoEl) removeListenersFromVideo(videoEl);
3694
+ videoEl = found;
3695
+ if (videoEl) attachListenersToVideo(videoEl);
3696
+ }
3697
+ });
3698
+ mutationObserver.observe(el, { childList: true, subtree: true });
3824
3699
  }
3700
+ const cachedInitial = getWinningBid();
3701
+ if (options?.trackViewability !== false && el) {
3702
+ self.observeSlot(el, slotId, {
3703
+ adUnitCode,
3704
+ auctionId: auctionId || cachedInitial?.auctionId,
3705
+ transactionId: transactionId || cachedInitial?.transactionId,
3706
+ mediaType: "video",
3707
+ bid: cachedInitial?.bidTrace || options?.bid,
3708
+ metadata: options?.metadata,
3709
+ emitRefreshEvent: false
3710
+ });
3711
+ }
3712
+ const cleanup = () => {
3713
+ if (mutationObserver) {
3714
+ mutationObserver.disconnect();
3715
+ mutationObserver = null;
3716
+ }
3717
+ if (el && el !== videoEl) {
3718
+ el.removeEventListener("playing", handlePlaying, true);
3719
+ el.removeEventListener("play", handlePlaying, true);
3720
+ el.removeEventListener("timeupdate", handleTimeUpdate, true);
3721
+ el.removeEventListener("ended", handleEnded, true);
3722
+ el.removeEventListener("error", handleError, true);
3723
+ }
3724
+ if (videoEl) {
3725
+ removeListenersFromVideo(videoEl);
3726
+ }
3727
+ self.videoDetachCleanups.delete(cleanup);
3728
+ };
3729
+ self.videoDetachCleanups.add(cleanup);
3730
+ return cleanup;
3825
3731
  }
3732
+ BidkernelPrebidAnalytics.prototype.attachImaAdsManager = function(adsManager, options) {
3733
+ return attachImaAdsManager(this, adsManager, options);
3734
+ };
3735
+ BidkernelPrebidAnalytics.prototype.attachVideoPlayer = function(target, options) {
3736
+ return attachVideoPlayer(this, target, options);
3737
+ };
3738
+ var coreEnable = BidkernelPrebidAnalytics.prototype.enable;
3739
+ BidkernelPrebidAnalytics.prototype.enable = function() {
3740
+ coreEnable.call(this);
3741
+ if (typeof window !== "undefined") {
3742
+ initImaInterception();
3743
+ if (window.google?.ima) {
3744
+ hookImaPrototype(window.google.ima);
3745
+ }
3746
+ }
3747
+ };
3826
3748
 
3827
3749
  // src/index.ts
3828
3750
  function getbidkernel(alias = "bidkernel") {
@@ -3856,7 +3778,10 @@ function getbidkernel(alias = "bidkernel") {
3856
3778
  }
3857
3779
  export {
3858
3780
  BidkernelPrebidAnalytics,
3781
+ CACHED_BID_TTL_MS,
3859
3782
  PrebidEventDeduper,
3783
+ attachImaAdsManager,
3784
+ attachVideoPlayer,
3860
3785
  getOrCreateSessionId,
3861
3786
  getPrebidEventKey,
3862
3787
  getbidkernel,