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