@bidkernel/analytics 0.7.0 → 0.10.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
@@ -24,6 +24,9 @@ __export(src_exports, {
24
24
  PrebidEventDeduper: () => PrebidEventDeduper,
25
25
  getPrebidEventKey: () => getPrebidEventKey,
26
26
  getbidkernel: () => getbidkernel,
27
+ hookImaPrototype: () => hookImaPrototype,
28
+ initImaInterception: () => initImaInterception,
29
+ isTrustedEndpoint: () => isTrustedEndpoint,
27
30
  registerPrebidAnalytics: () => registerPrebidAnalytics
28
31
  });
29
32
  module.exports = __toCommonJS(src_exports);
@@ -34,26 +37,33 @@ var TraceEventType = {
34
37
  TRACE_EVENT_TYPE_UNSPECIFIED: 0,
35
38
  AUCTION_START: 1,
36
39
  AUCTION_END: 2,
37
- BID_REQUEST: 3,
38
- BID_RESPONSE: 4,
39
- BID_WIN: 5,
40
- BID_TIMEOUT: 6,
40
+ BID_RESPONSE: 3,
41
+ BID_WIN: 4,
41
42
  /** IMPRESSION - ad rendered into DOM */
42
- IMPRESSION: 7,
43
+ IMPRESSION: 5,
43
44
  /** VIEWABLE - IAB viewability threshold met (boolean, once per render cycle) */
44
- VIEWABLE: 8,
45
+ VIEWABLE: 6,
45
46
  /** TIME_IN_VIEW - accumulated in-viewport duration (once per render cycle end) */
46
- TIME_IN_VIEW: 14,
47
- REFRESH: 9,
48
- ERROR: 10,
49
- SLOT_DEFINED: 11,
50
- SLOT_DESTROYED: 12,
47
+ TIME_IN_VIEW: 7,
48
+ REFRESH: 8,
49
+ ERROR: 9,
50
+ SLOT_DEFINED: 10,
51
+ SLOT_DESTROYED: 11,
51
52
  /** CLICK - ad click-through */
52
- CLICK: 13,
53
- /** NO_BID - bidder responded with no bid */
54
- NO_BID: 15,
53
+ CLICK: 12,
55
54
  /** AD_RENDER_FAILED - Prebid failed to render the winning ad */
56
- AD_RENDER_FAILED: 16,
55
+ AD_RENDER_FAILED: 13,
56
+ UNRECOGNIZED: -1
57
+ };
58
+ var BidderOutcome = {
59
+ BIDDER_OUTCOME_UNSPECIFIED: 0,
60
+ /** BIDDER_OUTCOME_BID - returned a bid (CPM > 0) */
61
+ BIDDER_OUTCOME_BID: 1,
62
+ BIDDER_OUTCOME_NO_BID: 2,
63
+ BIDDER_OUTCOME_TIMEOUT: 3,
64
+ BIDDER_OUTCOME_ERROR: 4,
65
+ /** BIDDER_OUTCOME_NO_RESPONSE - requested, nothing came back by auction end */
66
+ BIDDER_OUTCOME_NO_RESPONSE: 5,
57
67
  UNRECOGNIZED: -1
58
68
  };
59
69
  function createBaseTraceEventBatch() {
@@ -284,7 +294,8 @@ function createBaseTraceEvent() {
284
294
  namespace: "",
285
295
  eventId: "",
286
296
  metadata: {},
287
- viewableDurationMs: void 0
297
+ viewableDurationMs: void 0,
298
+ bidderOutcomes: []
288
299
  };
289
300
  }
290
301
  var TraceEvent = {
@@ -322,6 +333,11 @@ var TraceEvent = {
322
333
  if (message.viewableDurationMs !== void 0) {
323
334
  writer.uint32(89).double(message.viewableDurationMs);
324
335
  }
336
+ if (message.bidderOutcomes !== void 0 && message.bidderOutcomes.length !== 0) {
337
+ for (const v of message.bidderOutcomes) {
338
+ BidderOutcomeEntry.encode(v, writer.uint32(98).fork()).join();
339
+ }
340
+ }
325
341
  return writer;
326
342
  },
327
343
  decode(input, length) {
@@ -411,6 +427,16 @@ var TraceEvent = {
411
427
  message.viewableDurationMs = reader.double();
412
428
  continue;
413
429
  }
430
+ case 12: {
431
+ if (tag !== 98) {
432
+ break;
433
+ }
434
+ const el = BidderOutcomeEntry.decode(reader, reader.uint32());
435
+ if (el !== void 0) {
436
+ message.bidderOutcomes.push(el);
437
+ }
438
+ continue;
439
+ }
414
440
  }
415
441
  if ((tag & 7) === 4 || tag === 0) {
416
442
  break;
@@ -443,6 +469,7 @@ var TraceEvent = {
443
469
  {}
444
470
  );
445
471
  message.viewableDurationMs = object.viewableDurationMs ?? void 0;
472
+ message.bidderOutcomes = object.bidderOutcomes?.map((e) => BidderOutcomeEntry.fromPartial(e)) || [];
446
473
  return message;
447
474
  }
448
475
  };
@@ -498,6 +525,91 @@ var TraceEvent_MetadataEntry = {
498
525
  return message;
499
526
  }
500
527
  };
528
+ function createBaseBidderOutcomeEntry() {
529
+ return { bidder: "", adUnitCode: "", mediaType: "", outcome: 0, latencyMs: 0 };
530
+ }
531
+ var BidderOutcomeEntry = {
532
+ encode(message, writer = new import_wire.BinaryWriter()) {
533
+ if (message.bidder !== void 0 && message.bidder !== "") {
534
+ writer.uint32(10).string(message.bidder);
535
+ }
536
+ if (message.adUnitCode !== void 0 && message.adUnitCode !== "") {
537
+ writer.uint32(18).string(message.adUnitCode);
538
+ }
539
+ if (message.mediaType !== void 0 && message.mediaType !== "") {
540
+ writer.uint32(26).string(message.mediaType);
541
+ }
542
+ if (message.outcome !== void 0 && message.outcome !== 0) {
543
+ writer.uint32(32).int32(message.outcome);
544
+ }
545
+ if (message.latencyMs !== void 0 && message.latencyMs !== 0) {
546
+ writer.uint32(41).double(message.latencyMs);
547
+ }
548
+ return writer;
549
+ },
550
+ decode(input, length) {
551
+ const reader = input instanceof import_wire.BinaryReader ? input : new import_wire.BinaryReader(input);
552
+ const end = length === void 0 ? reader.len : reader.pos + length;
553
+ const message = createBaseBidderOutcomeEntry();
554
+ while (reader.pos < end) {
555
+ const tag = reader.uint32();
556
+ switch (tag >>> 3) {
557
+ case 1: {
558
+ if (tag !== 10) {
559
+ break;
560
+ }
561
+ message.bidder = reader.string();
562
+ continue;
563
+ }
564
+ case 2: {
565
+ if (tag !== 18) {
566
+ break;
567
+ }
568
+ message.adUnitCode = reader.string();
569
+ continue;
570
+ }
571
+ case 3: {
572
+ if (tag !== 26) {
573
+ break;
574
+ }
575
+ message.mediaType = reader.string();
576
+ continue;
577
+ }
578
+ case 4: {
579
+ if (tag !== 32) {
580
+ break;
581
+ }
582
+ message.outcome = reader.int32();
583
+ continue;
584
+ }
585
+ case 5: {
586
+ if (tag !== 41) {
587
+ break;
588
+ }
589
+ message.latencyMs = reader.double();
590
+ continue;
591
+ }
592
+ }
593
+ if ((tag & 7) === 4 || tag === 0) {
594
+ break;
595
+ }
596
+ reader.skip(tag & 7);
597
+ }
598
+ return message;
599
+ },
600
+ create(base) {
601
+ return BidderOutcomeEntry.fromPartial(base ?? {});
602
+ },
603
+ fromPartial(object) {
604
+ const message = createBaseBidderOutcomeEntry();
605
+ message.bidder = object.bidder ?? "";
606
+ message.adUnitCode = object.adUnitCode ?? "";
607
+ message.mediaType = object.mediaType ?? "";
608
+ message.outcome = object.outcome ?? 0;
609
+ message.latencyMs = object.latencyMs ?? 0;
610
+ return message;
611
+ }
612
+ };
501
613
  function createBaseBidTrace() {
502
614
  return {
503
615
  bidder: "",
@@ -658,6 +770,8 @@ var MAX_QUEUE_SIZE = 200;
658
770
  var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
659
771
  var MAX_CONSECUTIVE_SEND_FAILURES = 10;
660
772
  var MAX_PAYLOAD_BYTES = 32 * 1024;
773
+ var MAX_METADATA_VALUE_CHARS = 1024;
774
+ var MAX_METADATA_ENTRIES = 16;
661
775
  var SAFE_CONTENT_TYPE = "text/plain";
662
776
  var PENDING_BATCH_KEY_PREFIX = "_bidkernel_pending_";
663
777
  var MAX_PENDING_BATCHES = 20;
@@ -666,11 +780,8 @@ var PENDING_BATCH_CLEANUP_DELAY_MS = 1e4;
666
780
  var EVENT_NAME_TO_TYPE = {
667
781
  auctionStart: TraceEventType.AUCTION_START,
668
782
  auctionEnd: TraceEventType.AUCTION_END,
669
- bidRequest: TraceEventType.BID_REQUEST,
670
783
  bidResponse: TraceEventType.BID_RESPONSE,
671
- bidTimeout: TraceEventType.BID_TIMEOUT,
672
784
  bidWin: TraceEventType.BID_WIN,
673
- noBid: TraceEventType.NO_BID,
674
785
  adRenderFailed: TraceEventType.AD_RENDER_FAILED,
675
786
  click: TraceEventType.CLICK,
676
787
  impression: TraceEventType.IMPRESSION,
@@ -683,6 +794,10 @@ var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
683
794
  TraceEventType.BID_WIN,
684
795
  TraceEventType.CLICK
685
796
  ]);
797
+ var CACHED_BID_TTL_MS = 30 * 60 * 1e3;
798
+ var MAX_CACHED_BID_KEYS = 200;
799
+ var MAX_IMPRESSION_KEYS = 1e3;
800
+ var MAX_TRACKED_AUCTIONS = 50;
686
801
  var SESSION_KEY = "_bidkernel_session";
687
802
  var SESSION_TS_KEY = "_bidkernel_session_ts";
688
803
  var THIRTY_MINUTES_MS = 30 * 60 * 1e3;
@@ -714,6 +829,46 @@ function base64ToBytes(b64) {
714
829
  }
715
830
  return bytes;
716
831
  }
832
+ function isTrustedEndpoint(raw) {
833
+ if (!raw) return false;
834
+ let url;
835
+ try {
836
+ url = new URL(raw);
837
+ } catch {
838
+ return false;
839
+ }
840
+ if (url.username || url.password) return false;
841
+ if (url.protocol === "https:") return true;
842
+ if (url.protocol === "http:") {
843
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
844
+ }
845
+ return false;
846
+ }
847
+ function boundMetadata(metadata) {
848
+ const bounded = {};
849
+ let count = 0;
850
+ for (const key of Object.keys(metadata)) {
851
+ if (count >= MAX_METADATA_ENTRIES) break;
852
+ const value = metadata[key];
853
+ if (value === void 0 || value === null) continue;
854
+ const str = String(value);
855
+ bounded[key] = str.length > MAX_METADATA_VALUE_CHARS ? `${str.slice(0, MAX_METADATA_VALUE_CHARS)}...[truncated]` : str;
856
+ count++;
857
+ }
858
+ return bounded;
859
+ }
860
+ function isLegacyPendingKey(key) {
861
+ const parts = key.slice(PENDING_BATCH_KEY_PREFIX.length).split("_");
862
+ return parts.length === 2 && /^\d+$/.test(parts[0]);
863
+ }
864
+ function sameEndpointOrigin(a, b) {
865
+ if (!a || !b) return false;
866
+ try {
867
+ return new URL(a).origin === new URL(b).origin;
868
+ } catch {
869
+ return false;
870
+ }
871
+ }
717
872
  function extendSession() {
718
873
  const now = Date.now();
719
874
  inMemorySessionTs = now;
@@ -830,22 +985,6 @@ var PrebidEventDeduper = class {
830
985
  return false;
831
986
  }
832
987
  };
833
- function parseSize(raw) {
834
- if (!raw) return { width: 0, height: 0 };
835
- let cur = raw;
836
- while (Array.isArray(cur) && cur.length > 0 && Array.isArray(cur[0])) {
837
- cur = cur[0];
838
- }
839
- if (Array.isArray(cur) && cur.length >= 2) {
840
- const w = Number(cur[0]);
841
- const h = Number(cur[1]);
842
- return {
843
- width: Number.isFinite(w) ? w : 0,
844
- height: Number.isFinite(h) ? h : 0
845
- };
846
- }
847
- return { width: 0, height: 0 };
848
- }
849
988
  function parseBidDimensions(bid) {
850
989
  if (Number.isFinite(bid?.width) && Number.isFinite(bid?.height)) {
851
990
  return { width: bid.width, height: bid.height };
@@ -861,8 +1000,264 @@ function parseBidDimensions(bid) {
861
1000
  height: Number.isFinite(bid?.height) ? bid.height : 0
862
1001
  };
863
1002
  }
1003
+ var adsLoaderListenerMap = /* @__PURE__ */ new WeakMap();
1004
+ function hookAdsManagerLoadedEvent(loadedEventCtor) {
1005
+ if (!loadedEventCtor) return;
1006
+ try {
1007
+ const loadedProto = loadedEventCtor.prototype;
1008
+ if (loadedProto && typeof loadedProto.getAdsManager === "function" && !loadedProto.getAdsManager.__bidkernelHooked) {
1009
+ const originalGetAdsManager = loadedProto.getAdsManager;
1010
+ const hookedGetAdsManager = function(...args) {
1011
+ const adsManager = originalGetAdsManager.apply(this, args);
1012
+ if (adsManager) {
1013
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1014
+ try {
1015
+ instance.attachImaAdsManager(adsManager);
1016
+ } catch {
1017
+ }
1018
+ }
1019
+ }
1020
+ return adsManager;
1021
+ };
1022
+ hookedGetAdsManager.__bidkernelHooked = true;
1023
+ loadedProto.getAdsManager = hookedGetAdsManager;
1024
+ }
1025
+ } catch {
1026
+ }
1027
+ }
1028
+ function hookAdsManagerPrototype(adsManagerCtorOrProto) {
1029
+ if (!adsManagerCtorOrProto) return;
1030
+ try {
1031
+ const proto = adsManagerCtorOrProto.prototype || adsManagerCtorOrProto;
1032
+ if (proto && typeof proto.init === "function" && !proto.init.__bidkernelHooked) {
1033
+ const origInit = proto.init;
1034
+ const hookedInit = function(...args) {
1035
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1036
+ try {
1037
+ instance.attachImaAdsManager(this);
1038
+ } catch {
1039
+ }
1040
+ }
1041
+ return origInit.apply(this, args);
1042
+ };
1043
+ hookedInit.__bidkernelHooked = true;
1044
+ proto.init = hookedInit;
1045
+ }
1046
+ if (proto && typeof proto.start === "function" && !proto.start.__bidkernelHooked) {
1047
+ const origStart = proto.start;
1048
+ const hookedStart = function(...args) {
1049
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1050
+ try {
1051
+ instance.attachImaAdsManager(this);
1052
+ } catch {
1053
+ }
1054
+ }
1055
+ return origStart.apply(this, args);
1056
+ };
1057
+ hookedStart.__bidkernelHooked = true;
1058
+ proto.start = hookedStart;
1059
+ }
1060
+ } catch {
1061
+ }
1062
+ }
1063
+ function hookAdsLoader(adsLoaderCtor) {
1064
+ if (!adsLoaderCtor) return;
1065
+ try {
1066
+ const proto = adsLoaderCtor.prototype;
1067
+ if (!proto) return;
1068
+ if (typeof proto.addEventListener === "function" && !proto.addEventListener.__bidkernelHooked) {
1069
+ const origAddEventListener = proto.addEventListener;
1070
+ const hookedAddEventListener = function(type, listener, ...rest) {
1071
+ if ((type === "adsManagerLoaded" || type === (this?.AdsManagerLoadedEvent?.Type?.ADS_MANAGER_LOADED || "adsManagerLoaded")) && typeof listener === "function") {
1072
+ let wrappedListener = adsLoaderListenerMap.get(listener);
1073
+ if (!wrappedListener) {
1074
+ wrappedListener = function(event) {
1075
+ if (event && typeof event.getAdsManager === "function" && !event.getAdsManager.__bidkernelHooked) {
1076
+ const origGetAdsManager = event.getAdsManager;
1077
+ event.getAdsManager = function(...args) {
1078
+ const adsManager = origGetAdsManager.apply(this, args);
1079
+ if (adsManager) {
1080
+ for (const sdk of BidkernelPrebidAnalytics.getActiveInstances()) {
1081
+ try {
1082
+ sdk.attachImaAdsManager(adsManager);
1083
+ } catch {
1084
+ }
1085
+ }
1086
+ }
1087
+ return adsManager;
1088
+ };
1089
+ event.getAdsManager.__bidkernelHooked = true;
1090
+ }
1091
+ return listener.apply(this, arguments);
1092
+ };
1093
+ adsLoaderListenerMap.set(listener, wrappedListener);
1094
+ }
1095
+ return origAddEventListener.call(this, type, wrappedListener, ...rest);
1096
+ }
1097
+ return origAddEventListener.call(this, type, listener, ...rest);
1098
+ };
1099
+ hookedAddEventListener.__bidkernelHooked = true;
1100
+ proto.addEventListener = hookedAddEventListener;
1101
+ }
1102
+ if (typeof proto.removeEventListener === "function" && !proto.removeEventListener.__bidkernelHooked) {
1103
+ const origRemoveEventListener = proto.removeEventListener;
1104
+ const hookedRemoveEventListener = function(type, listener, ...rest) {
1105
+ const wrapped = typeof listener === "function" ? adsLoaderListenerMap.get(listener) : void 0;
1106
+ return origRemoveEventListener.call(this, type, wrapped || listener, ...rest);
1107
+ };
1108
+ hookedRemoveEventListener.__bidkernelHooked = true;
1109
+ proto.removeEventListener = hookedRemoveEventListener;
1110
+ }
1111
+ } catch {
1112
+ }
1113
+ }
1114
+ function hookImaPrototype(ima) {
1115
+ if (!ima || typeof ima !== "object") return;
1116
+ try {
1117
+ if (ima.AdsManagerLoadedEvent) {
1118
+ hookAdsManagerLoadedEvent(ima.AdsManagerLoadedEvent);
1119
+ }
1120
+ const loadedDesc = Object.getOwnPropertyDescriptor(ima, "AdsManagerLoadedEvent");
1121
+ if (!loadedDesc || loadedDesc.configurable) {
1122
+ let _loadedEvent = ima.AdsManagerLoadedEvent;
1123
+ try {
1124
+ Object.defineProperty(ima, "AdsManagerLoadedEvent", {
1125
+ configurable: true,
1126
+ enumerable: true,
1127
+ get() {
1128
+ return _loadedEvent;
1129
+ },
1130
+ set(val) {
1131
+ _loadedEvent = val;
1132
+ hookAdsManagerLoadedEvent(val);
1133
+ }
1134
+ });
1135
+ } catch {
1136
+ }
1137
+ }
1138
+ if (ima.AdsLoader) {
1139
+ hookAdsLoader(ima.AdsLoader);
1140
+ }
1141
+ const loaderDesc = Object.getOwnPropertyDescriptor(ima, "AdsLoader");
1142
+ if (!loaderDesc || loaderDesc.configurable) {
1143
+ let _loader = ima.AdsLoader;
1144
+ try {
1145
+ Object.defineProperty(ima, "AdsLoader", {
1146
+ configurable: true,
1147
+ enumerable: true,
1148
+ get() {
1149
+ return _loader;
1150
+ },
1151
+ set(val) {
1152
+ _loader = val;
1153
+ hookAdsLoader(val);
1154
+ }
1155
+ });
1156
+ } catch {
1157
+ }
1158
+ }
1159
+ if (ima.AdsManager) {
1160
+ hookAdsManagerPrototype(ima.AdsManager);
1161
+ }
1162
+ const mgrDesc = Object.getOwnPropertyDescriptor(ima, "AdsManager");
1163
+ if (!mgrDesc || mgrDesc.configurable) {
1164
+ let _mgr = ima.AdsManager;
1165
+ try {
1166
+ Object.defineProperty(ima, "AdsManager", {
1167
+ configurable: true,
1168
+ enumerable: true,
1169
+ get() {
1170
+ return _mgr;
1171
+ },
1172
+ set(val) {
1173
+ _mgr = val;
1174
+ hookAdsManagerPrototype(val);
1175
+ }
1176
+ });
1177
+ } catch {
1178
+ }
1179
+ }
1180
+ } catch {
1181
+ }
1182
+ }
1183
+ function initImaInterception() {
1184
+ if (typeof window === "undefined") return;
1185
+ const win = window;
1186
+ if (win.google?.ima) {
1187
+ hookImaPrototype(win.google.ima);
1188
+ }
1189
+ if (win.google && typeof win.google === "object") {
1190
+ const imaDesc = Object.getOwnPropertyDescriptor(win.google, "ima");
1191
+ if (!imaDesc || imaDesc.configurable) {
1192
+ let _ima = win.google.ima;
1193
+ try {
1194
+ Object.defineProperty(win.google, "ima", {
1195
+ configurable: true,
1196
+ enumerable: true,
1197
+ get() {
1198
+ return _ima;
1199
+ },
1200
+ set(val) {
1201
+ _ima = val;
1202
+ hookImaPrototype(val);
1203
+ }
1204
+ });
1205
+ } catch {
1206
+ }
1207
+ }
1208
+ }
1209
+ const googleDesc = Object.getOwnPropertyDescriptor(win, "google");
1210
+ if (!googleDesc || googleDesc.configurable) {
1211
+ let _google = win.google;
1212
+ try {
1213
+ Object.defineProperty(win, "google", {
1214
+ configurable: true,
1215
+ enumerable: true,
1216
+ get() {
1217
+ return _google;
1218
+ },
1219
+ set(val) {
1220
+ _google = val;
1221
+ if (_google && typeof _google === "object") {
1222
+ if (_google.ima) {
1223
+ hookImaPrototype(_google.ima);
1224
+ }
1225
+ const iDesc = Object.getOwnPropertyDescriptor(_google, "ima");
1226
+ if (!iDesc || iDesc.configurable) {
1227
+ let _i = _google.ima;
1228
+ try {
1229
+ Object.defineProperty(_google, "ima", {
1230
+ configurable: true,
1231
+ enumerable: true,
1232
+ get() {
1233
+ return _i;
1234
+ },
1235
+ set(iv) {
1236
+ _i = iv;
1237
+ hookImaPrototype(iv);
1238
+ }
1239
+ });
1240
+ } catch {
1241
+ }
1242
+ }
1243
+ }
1244
+ }
1245
+ });
1246
+ } catch {
1247
+ }
1248
+ }
1249
+ }
1250
+ if (typeof window !== "undefined") {
1251
+ initImaInterception();
1252
+ }
864
1253
  var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
865
1254
  static activeInstances = /* @__PURE__ */ new Set();
1255
+ static getActiveInstances() {
1256
+ return _BidkernelPrebidAnalytics.activeInstances;
1257
+ }
1258
+ static initImaInterception() {
1259
+ initImaInterception();
1260
+ }
866
1261
  config;
867
1262
  queue = [];
868
1263
  errorCount = 0;
@@ -897,9 +1292,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
897
1292
  cachedWinningBids = /* @__PURE__ */ new Map();
898
1293
  // Active video player attachment cleanup routines
899
1294
  videoDetachCleanups = /* @__PURE__ */ new Set();
1295
+ // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1296
+ auctionBidderOutcomes = /* @__PURE__ */ new Map();
900
1297
  constructor(config) {
1298
+ const requestedEndpoint = config.endpoint || "";
1299
+ const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
901
1300
  this.config = {
902
- endpoint: config.endpoint || "",
1301
+ endpoint,
903
1302
  propertyId: config.propertyId || "",
904
1303
  pageviewId: config.pageviewId || generateUUID(),
905
1304
  sessionId: config.sessionId || getOrCreateSessionId(),
@@ -915,6 +1314,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
915
1314
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
916
1315
  attachPbjsListeners: config.attachPbjsListeners ?? true
917
1316
  };
1317
+ if (requestedEndpoint && !endpoint) {
1318
+ this.log(
1319
+ "ERROR",
1320
+ `Rejected untrusted analytics endpoint ${requestedEndpoint}: must be an absolute https:// URL without embedded credentials. Events will not be transmitted.`
1321
+ );
1322
+ }
918
1323
  this.boundFlushBeacon = () => {
919
1324
  this.flushAllSlotsTimeInView();
920
1325
  this.flushBeacon();
@@ -1016,6 +1421,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1016
1421
  }
1017
1422
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
1018
1423
  this.resendPersistedBatches();
1424
+ initImaInterception();
1425
+ if (window.google?.ima) {
1426
+ hookImaPrototype(window.google.ima);
1427
+ }
1019
1428
  }
1020
1429
  }
1021
1430
  disable() {
@@ -1052,6 +1461,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1052
1461
  this.cachedWinningBids.clear();
1053
1462
  this.slotEmittedImpressionKeys.clear();
1054
1463
  this.slotLastAuctionIds.clear();
1464
+ this.auctionBidderOutcomes.clear();
1055
1465
  if (this.flushTimer) {
1056
1466
  clearInterval(this.flushTimer);
1057
1467
  this.flushTimer = null;
@@ -1455,7 +1865,139 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1455
1865
  this.slotEmittedImpressionKeys.delete(key);
1456
1866
  }
1457
1867
  }
1458
- this.cachedWinningBids.delete(slotId);
1868
+ const cached = this.cachedWinningBids.get(slotId);
1869
+ if (cached) this.deleteCachedBidEntry(cached);
1870
+ }
1871
+ // --- Bounded caches -------------------------------------------------------
1872
+ /**
1873
+ * Reads a cached winning bid, treating an entry past CACHED_BID_TTL_MS as
1874
+ * absent and dropping it. Every read goes through here so a stale bid can
1875
+ * never be married to a much later render.
1876
+ */
1877
+ getCachedBid(key) {
1878
+ const entry = this.cachedWinningBids.get(key);
1879
+ if (!entry) return void 0;
1880
+ if (Date.now() - entry.timestamp > CACHED_BID_TTL_MS) {
1881
+ this.deleteCachedBidEntry(entry);
1882
+ return void 0;
1883
+ }
1884
+ return entry;
1885
+ }
1886
+ /** Removes every alias pointing at one entry. Aliases are stored by identity. */
1887
+ deleteCachedBidEntry(entry) {
1888
+ for (const [key, value] of Array.from(this.cachedWinningBids)) {
1889
+ if (value === entry) this.cachedWinningBids.delete(key);
1890
+ }
1891
+ }
1892
+ /**
1893
+ * Drops expired entries, then evicts oldest-first until the key cap holds.
1894
+ * Map iterates in insertion order and one win's aliases are written
1895
+ * consecutively, so eviction takes a whole bid's aliases together.
1896
+ */
1897
+ pruneCachedWinningBids() {
1898
+ const now = Date.now();
1899
+ for (const [key, entry] of Array.from(this.cachedWinningBids)) {
1900
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) this.cachedWinningBids.delete(key);
1901
+ }
1902
+ while (this.cachedWinningBids.size > MAX_CACHED_BID_KEYS) {
1903
+ const oldest = this.cachedWinningBids.keys().next();
1904
+ if (oldest.done) break;
1905
+ this.cachedWinningBids.delete(oldest.value);
1906
+ }
1907
+ }
1908
+ /**
1909
+ * Searches cached winning bids for an entry matching the given filters (creativeId,
1910
+ * adId, or mediaType), falling back to the most recent winning video bid.
1911
+ */
1912
+ findCachedWinningBid(filter) {
1913
+ const now = Date.now();
1914
+ const isConsumed = (entry) => {
1915
+ return this.hasImpressionEmitted(
1916
+ entry.adUnitCode,
1917
+ void 0,
1918
+ entry.auctionId,
1919
+ entry.bidTrace.creativeId
1920
+ );
1921
+ };
1922
+ if (filter?.creativeId || filter?.adId) {
1923
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1924
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1925
+ if (filter.creativeId && (entry.bidTrace.creativeId === filter.creativeId || entry.rawBid?.creativeId === filter.creativeId) || filter.adId && (entry.rawBid?.adId === filter.adId || entry.adUnitCode === filter.adId)) {
1926
+ return entry;
1927
+ }
1928
+ }
1929
+ }
1930
+ let bestUnconsumedMatch;
1931
+ if (filter?.mediaType) {
1932
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1933
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1934
+ const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
1935
+ if (isEntryVideo) {
1936
+ if (!isConsumed(entry)) {
1937
+ if (!bestUnconsumedMatch || entry.timestamp < bestUnconsumedMatch.timestamp) {
1938
+ bestUnconsumedMatch = entry;
1939
+ }
1940
+ }
1941
+ }
1942
+ }
1943
+ if (bestUnconsumedMatch) return bestUnconsumedMatch;
1944
+ let bestFallbackMatch;
1945
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1946
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1947
+ const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
1948
+ if (isEntryVideo) {
1949
+ if (!bestFallbackMatch || entry.timestamp > bestFallbackMatch.timestamp) {
1950
+ bestFallbackMatch = entry;
1951
+ }
1952
+ }
1953
+ }
1954
+ if (bestFallbackMatch) return bestFallbackMatch;
1955
+ }
1956
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1957
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1958
+ if (!isConsumed(entry)) {
1959
+ if (!bestUnconsumedMatch || entry.timestamp > bestUnconsumedMatch.timestamp) {
1960
+ bestUnconsumedMatch = entry;
1961
+ }
1962
+ }
1963
+ }
1964
+ if (bestUnconsumedMatch) return bestUnconsumedMatch;
1965
+ let bestRecent;
1966
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1967
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1968
+ if (!bestRecent || entry.timestamp > bestRecent.timestamp) {
1969
+ bestRecent = entry;
1970
+ }
1971
+ }
1972
+ return bestRecent;
1973
+ }
1974
+ /** Records a dedup key, evicting oldest-first at the cap. */
1975
+ addImpressionKey(key) {
1976
+ if (this.slotEmittedImpressionKeys.has(key)) return;
1977
+ while (this.slotEmittedImpressionKeys.size >= MAX_IMPRESSION_KEYS) {
1978
+ const oldest = this.slotEmittedImpressionKeys.values().next();
1979
+ if (oldest.done) break;
1980
+ this.slotEmittedImpressionKeys.delete(oldest.value);
1981
+ }
1982
+ this.slotEmittedImpressionKeys.add(key);
1983
+ }
1984
+ /**
1985
+ * The bidder-outcome map for an auction, creating it if absent. Evicts the
1986
+ * oldest auction at the cap: without this, any auction whose auctionEnd never
1987
+ * fires (abandoned, navigated away from mid-flight) leaks its map forever.
1988
+ */
1989
+ getOrCreateAuctionOutcomes(auctionId) {
1990
+ let auctionMap = this.auctionBidderOutcomes.get(auctionId);
1991
+ if (!auctionMap) {
1992
+ auctionMap = /* @__PURE__ */ new Map();
1993
+ this.auctionBidderOutcomes.set(auctionId, auctionMap);
1994
+ }
1995
+ while (this.auctionBidderOutcomes.size > MAX_TRACKED_AUCTIONS) {
1996
+ const oldest = this.auctionBidderOutcomes.keys().next();
1997
+ if (oldest.done || oldest.value === auctionId) break;
1998
+ this.auctionBidderOutcomes.delete(oldest.value);
1999
+ }
2000
+ return auctionMap;
1459
2001
  }
1460
2002
  hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1461
2003
  const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
@@ -1475,14 +2017,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1475
2017
  }
1476
2018
  markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1477
2019
  const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
1478
- this.slotEmittedImpressionKeys.add(baseSlotKey);
2020
+ this.addImpressionKey(baseSlotKey);
1479
2021
  if (creativeId) {
1480
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
2022
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1481
2023
  }
1482
2024
  if (auctionId) {
1483
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
2025
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1484
2026
  if (creativeId) {
1485
- this.slotEmittedImpressionKeys.add(
2027
+ this.addImpressionKey(
1486
2028
  `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
1487
2029
  );
1488
2030
  }
@@ -1492,7 +2034,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1492
2034
  if (!this.isEnabled) return false;
1493
2035
  const resolvedSlotId = slotId || options?.adUnitCode || "";
1494
2036
  const adUnitCode = options?.adUnitCode || resolvedSlotId;
1495
- const cached = (options?.auctionId && resolvedSlotId ? this.cachedWinningBids.get(`${options.auctionId}:${resolvedSlotId}`) : void 0) || (options?.auctionId && adUnitCode ? this.cachedWinningBids.get(`${options.auctionId}:${adUnitCode}`) : void 0) || (resolvedSlotId ? this.cachedWinningBids.get(resolvedSlotId) : void 0) || (adUnitCode ? this.cachedWinningBids.get(adUnitCode) : void 0);
2037
+ const cached = (options?.auctionId && resolvedSlotId ? this.getCachedBid(`${options.auctionId}:${resolvedSlotId}`) : void 0) || (options?.auctionId && adUnitCode ? this.getCachedBid(`${options.auctionId}:${adUnitCode}`) : void 0) || (resolvedSlotId ? this.getCachedBid(resolvedSlotId) : void 0) || (adUnitCode ? this.getCachedBid(adUnitCode) : void 0);
1496
2038
  const rawBid = options?.bid || cached?.rawBid || {};
1497
2039
  const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
1498
2040
  const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
@@ -1588,14 +2130,40 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1588
2130
  return () => {
1589
2131
  };
1590
2132
  }
2133
+ if (!adsManager.__bidkernelAttachedInstances) {
2134
+ try {
2135
+ Object.defineProperty(adsManager, "__bidkernelAttachedInstances", {
2136
+ value: /* @__PURE__ */ new Set(),
2137
+ configurable: true,
2138
+ writable: true
2139
+ });
2140
+ } catch {
2141
+ adsManager.__bidkernelAttachedInstances = /* @__PURE__ */ new Set();
2142
+ }
2143
+ }
2144
+ if (adsManager.__bidkernelAttachedInstances.has(this)) {
2145
+ return () => {
2146
+ };
2147
+ }
2148
+ adsManager.__bidkernelAttachedInstances.add(this);
1591
2149
  const slotId = options?.slotId || options?.adUnitCode || "video";
1592
2150
  const adUnitCode = options?.adUnitCode || slotId;
1593
2151
  const auctionId = options?.auctionId || "";
1594
2152
  const transactionId = options?.transactionId || "";
1595
- const getWinningBid = () => {
1596
- return (auctionId ? this.cachedWinningBids.get(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? this.cachedWinningBids.get(`${auctionId}:${slotId}`) : void 0) || this.cachedWinningBids.get(adUnitCode) || (slotId !== adUnitCode ? this.cachedWinningBids.get(slotId) : void 0);
2153
+ const getWinningBid = (adData) => {
2154
+ 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);
2155
+ if (!cached) {
2156
+ cached = this.findCachedWinningBid({
2157
+ creativeId: adData?.creativeId,
2158
+ adId: adData?.adId,
2159
+ mediaType: "video"
2160
+ });
2161
+ }
2162
+ return cached;
1597
2163
  };
2164
+ let adStarted = false;
1598
2165
  const onAdStartedOrImpression = (event) => {
2166
+ adStarted = true;
1599
2167
  this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
1600
2168
  const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
1601
2169
  const adData = {};
@@ -1606,16 +2174,20 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1606
2174
  adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
1607
2175
  adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
1608
2176
  }
1609
- const cached = getWinningBid();
2177
+ const cached = getWinningBid(adData);
2178
+ const resolvedSlotId = options?.slotId && options.slotId !== "video" ? options.slotId : options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || options?.slotId || options?.adUnitCode || "video";
2179
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || resolvedSlotId;
2180
+ const resolvedAuctionId = auctionId || cached?.auctionId || "";
2181
+ const resolvedTransactionId = transactionId || cached?.transactionId || "";
1610
2182
  const mergedBid = {
1611
2183
  ...cached?.rawBid || options?.bid,
1612
2184
  mediaType: "video",
1613
2185
  creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
1614
2186
  };
1615
- const emitted = this.recordImpression(slotId, {
1616
- adUnitCode,
1617
- auctionId: auctionId || cached?.auctionId,
1618
- transactionId: transactionId || cached?.transactionId,
2187
+ const emitted = this.recordImpression(resolvedSlotId, {
2188
+ adUnitCode: resolvedAdUnitCode,
2189
+ auctionId: resolvedAuctionId,
2190
+ transactionId: resolvedTransactionId,
1619
2191
  mediaType: "video",
1620
2192
  bid: mergedBid,
1621
2193
  metadata: {
@@ -1627,7 +2199,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1627
2199
  });
1628
2200
  if (emitted && options?.onImpression) {
1629
2201
  try {
1630
- options.onImpression(slotId, { ad, bid: mergedBid });
2202
+ options.onImpression(resolvedSlotId, { ad, bid: mergedBid });
1631
2203
  } catch (e) {
1632
2204
  this.log("ERROR", "Error in onImpression callback", e);
1633
2205
  }
@@ -1646,10 +2218,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1646
2218
  const onAdClick = (event) => {
1647
2219
  this.log("DEBUG", "IMA AdEvent.CLICK received", event);
1648
2220
  const cached = getWinningBid();
2221
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
1649
2222
  this.enqueue(TraceEventType.CLICK, "click", {
1650
2223
  auctionId: auctionId || cached?.auctionId,
1651
2224
  transactionId: transactionId || cached?.transactionId,
1652
- adUnitCode,
2225
+ adUnitCode: resolvedAdUnitCode,
1653
2226
  bid: cached?.bidTrace || options?.bid,
1654
2227
  metadata: {
1655
2228
  ...options?.metadata,
@@ -1663,14 +2236,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1663
2236
  const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
1664
2237
  const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
1665
2238
  const cached = getWinningBid();
2239
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
1666
2240
  this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
1667
2241
  auctionId: auctionId || cached?.auctionId,
1668
2242
  transactionId: transactionId || cached?.transactionId,
1669
- adUnitCode,
2243
+ adUnitCode: resolvedAdUnitCode,
1670
2244
  bid: cached?.bidTrace || options?.bid,
1671
2245
  metadata: {
1672
2246
  ...options?.metadata,
1673
- reason: "ima_ad_error",
2247
+ reason: adStarted ? "ima_playback_error" : "ima_ad_error",
2248
+ playback_phase: adStarted ? "post_start" : "pre_start",
1674
2249
  message: msg,
1675
2250
  ...code ? { ima_error_code: String(code) } : {}
1676
2251
  },
@@ -1687,7 +2262,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1687
2262
  const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
1688
2263
  const adEventType = googleIma?.AdEvent?.Type || {};
1689
2264
  const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
1690
- const listeners = [
2265
+ const rawListeners = [
1691
2266
  { type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
1692
2267
  { type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
1693
2268
  {
@@ -1703,6 +2278,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1703
2278
  { type: adEventType.CLICK || "click", handler: onAdClick },
1704
2279
  { type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
1705
2280
  ];
2281
+ const seenListenerTypes = /* @__PURE__ */ new Set();
2282
+ const listeners = [];
2283
+ for (const l of rawListeners) {
2284
+ if (!seenListenerTypes.has(l.type)) {
2285
+ seenListenerTypes.add(l.type);
2286
+ listeners.push(l);
2287
+ }
2288
+ }
1706
2289
  for (const { type, handler } of listeners) {
1707
2290
  try {
1708
2291
  adsManager.addEventListener(type, handler);
@@ -1710,6 +2293,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1710
2293
  }
1711
2294
  }
1712
2295
  const cleanup = () => {
2296
+ adsManager.__bidkernelAttachedInstances?.delete(this);
1713
2297
  for (const { type, handler } of listeners) {
1714
2298
  try {
1715
2299
  adsManager.removeEventListener(type, handler);
@@ -1757,7 +2341,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1757
2341
  const auctionId = options?.auctionId || "";
1758
2342
  const transactionId = options?.transactionId || "";
1759
2343
  const getWinningBid = () => {
1760
- return (auctionId ? this.cachedWinningBids.get(`${auctionId}:${adUnitCode}`) : void 0) || (auctionId && slotId !== adUnitCode ? this.cachedWinningBids.get(`${auctionId}:${slotId}`) : void 0) || this.cachedWinningBids.get(adUnitCode) || (slotId !== adUnitCode ? this.cachedWinningBids.get(slotId) : void 0);
2344
+ 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);
1761
2345
  };
1762
2346
  let videoEl = null;
1763
2347
  if (el) {
@@ -1993,6 +2577,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1993
2577
  refreshIndex: record.refreshIndex
1994
2578
  };
1995
2579
  }
2580
+ /** The validated ingest endpoint in use, or "" when none passed validation. */
2581
+ getEndpoint() {
2582
+ return this.config.endpoint;
2583
+ }
1996
2584
  getRefreshIndex(slotId) {
1997
2585
  return this.slotRefreshIndices.get(slotId) ?? 0;
1998
2586
  }
@@ -2033,17 +2621,29 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2033
2621
  handleAuctionInit(data) {
2034
2622
  if (this.isDuplicate("auctionInit", data)) return;
2035
2623
  this.log("DEBUG", "auctionInit", data);
2624
+ const auctionId = data.auctionId || "";
2625
+ if (auctionId) {
2626
+ this.auctionBidderOutcomes.delete(auctionId);
2627
+ this.getOrCreateAuctionOutcomes(auctionId);
2628
+ }
2036
2629
  this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
2037
- auctionId: data.auctionId || "",
2630
+ auctionId,
2038
2631
  transactionId: data.transactionId || ""
2039
2632
  });
2040
2633
  }
2041
2634
  handleAuctionEnd(data) {
2042
2635
  if (this.isDuplicate("auctionEnd", data)) return;
2043
2636
  this.log("DEBUG", "auctionEnd", data);
2637
+ const auctionId = data.auctionId || "";
2638
+ const outcomesMap = auctionId ? this.auctionBidderOutcomes.get(auctionId) : void 0;
2639
+ const bidderOutcomes = outcomesMap ? Array.from(outcomesMap.values()) : [];
2640
+ if (auctionId) {
2641
+ this.auctionBidderOutcomes.delete(auctionId);
2642
+ }
2044
2643
  this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
2045
- auctionId: data.auctionId || "",
2046
- transactionId: data.transactionId || ""
2644
+ auctionId,
2645
+ transactionId: data.transactionId || "",
2646
+ bidderOutcomes
2047
2647
  });
2048
2648
  }
2049
2649
  handleBidRequested(data) {
@@ -2051,88 +2651,100 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2051
2651
  this.log("DEBUG", "bidRequested", data);
2052
2652
  const auctionId = data.auctionId || "";
2053
2653
  const bidder = data.bidderCode || data.bidder || "";
2654
+ if (!auctionId || !bidder) return;
2655
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2054
2656
  if (Array.isArray(data.bids)) {
2055
2657
  data.bids.forEach((bid) => {
2658
+ const adUnitCode = bid.adUnitCode || "";
2056
2659
  const mediaTypes = Object.keys(bid.mediaTypes || {});
2057
- const gpid = bid.ortb2Imp?.ext?.gpid || bid.gpid || data.gpid || "";
2058
- const transactionId = bid.transactionId || bid.ortb2Imp?.id || data.transactionId || "";
2059
- if (mediaTypes.length === 0) {
2060
- const mediaType = bid.mediaType || "banner";
2061
- const { width, height } = parseSize(bid.sizes || bid.playerSize);
2062
- this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
2063
- auctionId,
2064
- transactionId,
2065
- adUnitCode: bid.adUnitCode || "",
2066
- gpid,
2067
- bid: {
2068
- bidder,
2069
- mediaType,
2070
- width,
2071
- height
2072
- }
2660
+ const mediaType = mediaTypes.length > 0 ? mediaTypes[0] : bid.mediaType || "banner";
2661
+ const key = `${bidder}:${adUnitCode}`;
2662
+ if (!auctionMap.has(key)) {
2663
+ auctionMap.set(key, {
2664
+ bidder,
2665
+ adUnitCode,
2666
+ mediaType,
2667
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE,
2668
+ latencyMs: 0
2073
2669
  });
2074
- return;
2075
2670
  }
2076
- mediaTypes.forEach((mediaType) => {
2077
- let rawSize = null;
2078
- if (mediaType === "banner") {
2079
- rawSize = bid.mediaTypes?.banner?.sizes || bid.sizes;
2080
- } else if (mediaType === "video") {
2081
- rawSize = bid.mediaTypes?.video?.playerSize || bid.mediaTypes?.video?.sizes || bid.playerSize;
2082
- } else {
2083
- rawSize = bid.mediaTypes?.[mediaType]?.sizes;
2084
- }
2085
- const { width, height } = parseSize(rawSize);
2086
- this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
2087
- auctionId,
2088
- transactionId,
2089
- adUnitCode: bid.adUnitCode || "",
2090
- gpid,
2091
- bid: {
2092
- bidder,
2093
- mediaType,
2094
- width,
2095
- height
2096
- }
2097
- });
2098
- });
2099
2671
  });
2100
2672
  }
2101
2673
  }
2102
2674
  handleBidResponse(data) {
2103
2675
  if (this.isDuplicate("bidResponse", data)) return;
2104
2676
  this.log("DEBUG", "bidResponse", data);
2105
- this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
2106
- auctionId: data.auctionId || "",
2107
- transactionId: data.transactionId || "",
2108
- adUnitCode: data.adUnitCode || "",
2109
- bid: {
2110
- bidder: data.bidderCode || data.bidder || "",
2111
- cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
2112
- currency: data.originalCurrency ?? data.currency ?? "USD",
2113
- ...parseBidDimensions(data),
2114
- dealId: data.dealId || "",
2115
- mediaType: data.mediaType || "banner",
2116
- latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
2117
- advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2118
- creativeId: data.creativeId || ""
2677
+ const auctionId = data.auctionId || "";
2678
+ const bidder = data.bidderCode || data.bidder || "";
2679
+ const adUnitCode = data.adUnitCode || "";
2680
+ const cpm = Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0;
2681
+ const latencyMs = Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0;
2682
+ const resMediaTypes = Object.keys(data.mediaTypes || {});
2683
+ const mediaType = data.mediaType || (data.mediaTypes?.video ? "video" : resMediaTypes.length > 0 ? resMediaTypes[0] : "banner");
2684
+ if (auctionId && bidder) {
2685
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2686
+ const key = `${bidder}:${adUnitCode}`;
2687
+ const entry = auctionMap.get(key);
2688
+ const outcome = cpm > 0 ? BidderOutcome.BIDDER_OUTCOME_BID : BidderOutcome.BIDDER_OUTCOME_NO_BID;
2689
+ if (entry) {
2690
+ entry.outcome = outcome;
2691
+ entry.latencyMs = latencyMs;
2692
+ if (mediaType) entry.mediaType = mediaType;
2693
+ } else {
2694
+ auctionMap.set(key, {
2695
+ bidder,
2696
+ adUnitCode,
2697
+ mediaType,
2698
+ outcome,
2699
+ latencyMs
2700
+ });
2119
2701
  }
2120
- });
2702
+ }
2703
+ if (cpm > 0) {
2704
+ this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
2705
+ auctionId,
2706
+ transactionId: data.transactionId || "",
2707
+ adUnitCode,
2708
+ bid: {
2709
+ bidder,
2710
+ cpm,
2711
+ currency: data.originalCurrency ?? data.currency ?? "USD",
2712
+ ...parseBidDimensions(data),
2713
+ dealId: data.dealId || "",
2714
+ mediaType,
2715
+ latencyMs,
2716
+ advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2717
+ creativeId: data.creativeId || ""
2718
+ }
2719
+ });
2720
+ }
2121
2721
  }
2122
2722
  handleBidTimeout(data) {
2123
2723
  if (this.isDuplicate("bidTimeout", data)) return;
2124
2724
  this.log("DEBUG", "bidTimeout", data);
2125
2725
  const items = Array.isArray(data) ? data : data ? [data] : [];
2126
2726
  for (const t of items) {
2127
- this.enqueue(TraceEventType.BID_TIMEOUT, "bidTimeout", {
2128
- auctionId: t.auctionId || "",
2129
- transactionId: t.transactionId || "",
2130
- adUnitCode: t.adUnitCode || "",
2131
- bid: {
2132
- bidder: t.bidderCode || t.bidder || "",
2133
- latencyMs: Number.isFinite(t.timeout) ? t.timeout : 0
2727
+ const auctionId = t.auctionId || "";
2728
+ const bidder = t.bidderCode || t.bidder || "";
2729
+ const adUnitCode = t.adUnitCode || "";
2730
+ const latencyMs = Number.isFinite(t.timeout) ? t.timeout : 0;
2731
+ if (auctionId && bidder) {
2732
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2733
+ const key = `${bidder}:${adUnitCode}`;
2734
+ const entry = auctionMap.get(key);
2735
+ if (entry) {
2736
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_TIMEOUT;
2737
+ if (latencyMs > 0) entry.latencyMs = latencyMs;
2738
+ } else {
2739
+ auctionMap.set(key, {
2740
+ bidder,
2741
+ adUnitCode,
2742
+ mediaType: "banner",
2743
+ outcome: BidderOutcome.BIDDER_OUTCOME_TIMEOUT,
2744
+ latencyMs
2745
+ });
2134
2746
  }
2135
- });
2747
+ }
2136
2748
  }
2137
2749
  }
2138
2750
  handleBidWon(data) {
@@ -2141,13 +2753,15 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2141
2753
  const auctionId = data.auctionId || "";
2142
2754
  const transactionId = data.transactionId || "";
2143
2755
  const adUnitCode = data.adUnitCode || data.adId || "";
2756
+ const wonMediaTypes = Object.keys(data.mediaTypes || {});
2757
+ const resolvedMediaType = data.mediaType || (data.mediaTypes?.video ? "video" : wonMediaTypes.length > 0 ? wonMediaTypes[0] : "banner");
2144
2758
  const bidTrace = {
2145
2759
  bidder: data.bidderCode || data.bidder || "",
2146
2760
  cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
2147
2761
  currency: data.originalCurrency ?? data.currency ?? "USD",
2148
2762
  ...parseBidDimensions(data),
2149
2763
  dealId: data.dealId || "",
2150
- mediaType: data.mediaType || "banner",
2764
+ mediaType: resolvedMediaType,
2151
2765
  latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
2152
2766
  advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2153
2767
  creativeId: data.creativeId || ""
@@ -2171,6 +2785,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2171
2785
  this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
2172
2786
  }
2173
2787
  }
2788
+ this.pruneCachedWinningBids();
2174
2789
  }
2175
2790
  this.enqueue(TraceEventType.BID_WIN, "bidWon", {
2176
2791
  auctionId,
@@ -2182,14 +2797,27 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2182
2797
  handleNoBid(data) {
2183
2798
  if (this.isDuplicate("noBid", data)) return;
2184
2799
  this.log("DEBUG", "noBid", data);
2185
- this.enqueue(TraceEventType.NO_BID, "noBid", {
2186
- auctionId: data.auctionId || "",
2187
- transactionId: data.transactionId || "",
2188
- adUnitCode: data.adUnitCode || "",
2189
- bid: {
2190
- bidder: data.bidderCode || data.bidder || ""
2800
+ const auctionId = data.auctionId || "";
2801
+ const bidder = data.bidderCode || data.bidder || "";
2802
+ const adUnitCode = data.adUnitCode || "";
2803
+ if (auctionId && bidder) {
2804
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2805
+ const key = `${bidder}:${adUnitCode}`;
2806
+ const entry = auctionMap.get(key);
2807
+ if (entry) {
2808
+ if (entry.outcome === BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE) {
2809
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_NO_BID;
2810
+ }
2811
+ } else {
2812
+ auctionMap.set(key, {
2813
+ bidder,
2814
+ adUnitCode,
2815
+ mediaType: "banner",
2816
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_BID,
2817
+ latencyMs: 0
2818
+ });
2191
2819
  }
2192
- });
2820
+ }
2193
2821
  }
2194
2822
  handleAdRenderFailed(data) {
2195
2823
  if (this.isDuplicate("adRenderFailed", data)) return;
@@ -2221,14 +2849,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2221
2849
  const mediaType = bid.mediaType || "banner";
2222
2850
  const slotKey = data.adUnitCode || bid.adUnitCode || "";
2223
2851
  const altKey = data.adId || bid.adId || "";
2224
- const cached = (auctionId && slotKey ? this.cachedWinningBids.get(`${auctionId}:${slotKey}`) : void 0) || (auctionId && altKey ? this.cachedWinningBids.get(`${auctionId}:${altKey}`) : void 0) || (slotKey ? this.cachedWinningBids.get(slotKey) : void 0) || (altKey ? this.cachedWinningBids.get(altKey) : void 0);
2852
+ const cached = (auctionId && slotKey ? this.getCachedBid(`${auctionId}:${slotKey}`) : void 0) || (auctionId && altKey ? this.getCachedBid(`${auctionId}:${altKey}`) : void 0) || (slotKey ? this.getCachedBid(slotKey) : void 0) || (altKey ? this.getCachedBid(altKey) : void 0);
2853
+ const isVideo = mediaType === "video" || bid.mediaType === "video" || bid.mediaTypes?.video !== void 0 || cached?.bidTrace?.mediaType === "video" || cached?.rawBid?.mediaType === "video" || adUnitCode && this.slotViewabilityRecords.get(adUnitCode)?.mediaType === "video";
2854
+ const resolvedMediaType = isVideo ? "video" : mediaType || cached?.bidTrace?.mediaType || "banner";
2225
2855
  const bidTrace = {
2226
2856
  bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
2227
2857
  cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
2228
2858
  currency: bid.originalCurrency ?? bid.currency ?? cached?.bidTrace?.currency ?? "USD",
2229
2859
  ...parseBidDimensions(bid).width ? parseBidDimensions(bid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(bid),
2230
2860
  dealId: bid.dealId || cached?.bidTrace?.dealId || "",
2231
- mediaType: mediaType || cached?.bidTrace?.mediaType || "banner",
2861
+ mediaType: resolvedMediaType,
2232
2862
  latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
2233
2863
  advertiserDomain: bid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
2234
2864
  creativeId: bid.creativeId || cached?.bidTrace?.creativeId || ""
@@ -2267,28 +2897,35 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2267
2897
  }
2268
2898
  }
2269
2899
  const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
2270
- const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
2271
- adUnitCode,
2272
- currentRefreshIndex,
2273
- resolvedAuctionId,
2274
- bidTrace.creativeId
2275
- ) : false;
2276
- if (!alreadyEmitted) {
2277
- if (adUnitCode) {
2278
- this.markImpressionEmitted(
2900
+ if (!isVideo) {
2901
+ const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
2902
+ adUnitCode,
2903
+ currentRefreshIndex,
2904
+ resolvedAuctionId,
2905
+ bidTrace.creativeId
2906
+ ) : false;
2907
+ if (!alreadyEmitted) {
2908
+ if (adUnitCode) {
2909
+ this.markImpressionEmitted(
2910
+ adUnitCode,
2911
+ currentRefreshIndex,
2912
+ resolvedAuctionId,
2913
+ bidTrace.creativeId
2914
+ );
2915
+ }
2916
+ this.enqueue(TraceEventType.IMPRESSION, "impression", {
2917
+ auctionId: resolvedAuctionId,
2918
+ transactionId: resolvedTransactionId,
2279
2919
  adUnitCode,
2280
- currentRefreshIndex,
2281
- resolvedAuctionId,
2282
- bidTrace.creativeId
2283
- );
2920
+ bid: bidTrace,
2921
+ metadata: { refresh_index: String(currentRefreshIndex) }
2922
+ });
2284
2923
  }
2285
- this.enqueue(TraceEventType.IMPRESSION, "impression", {
2286
- auctionId: resolvedAuctionId,
2287
- transactionId: resolvedTransactionId,
2288
- adUnitCode,
2289
- bid: bidTrace,
2290
- metadata: { refresh_index: String(currentRefreshIndex) }
2291
- });
2924
+ } else {
2925
+ this.log(
2926
+ "DEBUG",
2927
+ `adRenderSucceeded received for video ad unit ${adUnitCode || "unknown"}: suppressing IMPRESSION until verified video lifecycle event`
2928
+ );
2292
2929
  }
2293
2930
  if (this.config.viewabilityEnabled && typeof document !== "undefined") {
2294
2931
  let el = null;
@@ -2310,7 +2947,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2310
2947
  }
2311
2948
  if (!el && targetId) {
2312
2949
  try {
2313
- el = document.querySelector(`[data-ad-slot="${targetId}"]`) || document.querySelector(`#${targetId}`);
2950
+ for (const candidate of Array.from(document.querySelectorAll("[data-ad-slot]"))) {
2951
+ if (candidate.getAttribute("data-ad-slot") === targetId) {
2952
+ el = candidate;
2953
+ break;
2954
+ }
2955
+ }
2314
2956
  } catch {
2315
2957
  }
2316
2958
  }
@@ -2319,7 +2961,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2319
2961
  adUnitCode,
2320
2962
  auctionId: resolvedAuctionId,
2321
2963
  transactionId: resolvedTransactionId,
2322
- mediaType,
2964
+ mediaType: resolvedMediaType,
2323
2965
  bid: bidTrace,
2324
2966
  refreshIndex: currentRefreshIndex,
2325
2967
  emitRefreshEvent: false
@@ -2364,6 +3006,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2364
3006
  if (data.viewableDurationMs !== void 0) {
2365
3007
  protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
2366
3008
  }
3009
+ if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
3010
+ protoEvent.bidderOutcomes = data.bidderOutcomes;
3011
+ }
2367
3012
  const metadata = { ...data.metadata };
2368
3013
  if (data.error !== void 0 && data.error !== null) {
2369
3014
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
@@ -2374,8 +3019,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2374
3019
  if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
2375
3020
  metadata.refresh_index = String(data.refreshIndex);
2376
3021
  }
2377
- if (Object.keys(metadata).length > 0) {
2378
- protoEvent.metadata = metadata;
3022
+ const boundedMetadata = boundMetadata(metadata);
3023
+ if (Object.keys(boundedMetadata).length > 0) {
3024
+ protoEvent.metadata = boundedMetadata;
2379
3025
  }
2380
3026
  this.queue.push(protoEvent);
2381
3027
  if (this.queue.length > MAX_QUEUE_SIZE) {
@@ -2413,7 +3059,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2413
3059
  // Shared by flush() and flushBeacon().
2414
3060
  drainBatch() {
2415
3061
  if (this.queue.length === 0 || !this.config.endpoint) return null;
2416
- let events = this.queue;
2417
3062
  const domain = typeof window !== "undefined" ? window.location.hostname : "";
2418
3063
  const buildBatch = (evts) => ({
2419
3064
  propertyId: this.config.propertyId,
@@ -2427,25 +3072,37 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2427
3072
  domain,
2428
3073
  events: evts
2429
3074
  });
2430
- let encoded = TraceEventBatch.encode(
2431
- buildBatch(events)
2432
- ).finish();
2433
- while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
2434
- events = events.slice(0, Math.floor(events.length / 2));
2435
- encoded = TraceEventBatch.encode(
3075
+ for (; ; ) {
3076
+ if (this.queue.length === 0) return null;
3077
+ let events = this.queue;
3078
+ let encoded = TraceEventBatch.encode(
2436
3079
  buildBatch(events)
2437
3080
  ).finish();
3081
+ while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
3082
+ events = events.slice(0, Math.floor(events.length / 2));
3083
+ encoded = TraceEventBatch.encode(
3084
+ buildBatch(events)
3085
+ ).finish();
3086
+ }
3087
+ if (encoded.byteLength > MAX_PAYLOAD_BYTES) {
3088
+ const [dropped] = this.queue.splice(0, 1);
3089
+ this.log(
3090
+ "WARN",
3091
+ `Dropping oversized event ${dropped?.eventName || dropped?.eventId} (${encoded.byteLength} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte cap on its own)`
3092
+ );
3093
+ continue;
3094
+ }
3095
+ this.queue = this.queue.slice(events.length);
3096
+ return {
3097
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
3098
+ // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
3099
+ // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
3100
+ encoded,
3101
+ events
3102
+ };
2438
3103
  }
2439
- this.queue = this.queue.slice(events.length);
2440
- return {
2441
- url: `${this.config.endpoint}/${this.config.propertyId}`,
2442
- // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
2443
- // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
2444
- encoded,
2445
- events
2446
- };
2447
3104
  }
2448
- sendFetch(payload, useKeepalive = false, onDelivered) {
3105
+ sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
2449
3106
  const fetchOpts = {
2450
3107
  method: "POST",
2451
3108
  // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
@@ -2462,15 +3119,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2462
3119
  this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
2463
3120
  return;
2464
3121
  }
2465
- this.handleSendFailure(payload.events);
3122
+ if (!isReplay) this.handleSendFailure(payload.events);
2466
3123
  } else {
2467
- this.consecutiveSendFailures = 0;
2468
- this.nextSendAllowedAt = 0;
3124
+ if (!isReplay) {
3125
+ this.consecutiveSendFailures = 0;
3126
+ this.nextSendAllowedAt = 0;
3127
+ }
2469
3128
  if (onDelivered) onDelivered();
2470
3129
  }
2471
3130
  }).catch((err) => {
2472
3131
  this.log("ERROR", "Failed to send batch", err);
2473
- this.handleSendFailure(payload.events);
3132
+ if (!isReplay) this.handleSendFailure(payload.events);
2474
3133
  });
2475
3134
  const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2476
3135
  if (proc && typeof proc._tickCallback === "function") {
@@ -2526,16 +3185,23 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2526
3185
  this.schedulePersistedCleanup();
2527
3186
  }
2528
3187
  // --- Exit-batch persistence -----------------------------------------------
3188
+ // Key prefix for this instance's property. encodeURIComponent keeps the
3189
+ // property segment free of the "_" the timestamp suffix is parsed against,
3190
+ // so a property ID containing one cannot forge another property's prefix.
3191
+ pendingKeyPrefix() {
3192
+ return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
3193
+ }
2529
3194
  persistBatch(encoded) {
2530
3195
  try {
2531
3196
  if (typeof localStorage === "undefined") return null;
3197
+ if (!this.config.propertyId) return null;
2532
3198
  let pendingCount = 0;
2533
3199
  for (let i = 0; i < localStorage.length; i++) {
2534
3200
  const k = localStorage.key(i);
2535
3201
  if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
2536
3202
  }
2537
3203
  if (pendingCount >= MAX_PENDING_BATCHES) return null;
2538
- const key = `${PENDING_BATCH_KEY_PREFIX}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
3204
+ const key = `${this.pendingKeyPrefix()}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2539
3205
  localStorage.setItem(key, bytesToBase64(encoded));
2540
3206
  this.persistedBatchKeys.push(key);
2541
3207
  return key;
@@ -2566,16 +3232,33 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2566
3232
  }, PENDING_BATCH_CLEANUP_DELAY_MS);
2567
3233
  }
2568
3234
  // Resend batches a previous pageview persisted at exit but could not
2569
- // confirm. Keys are claimed (removed) before sending so concurrent tabs
2570
- // don't double-send; the batch bytes carry their original pageview, session,
2571
- // and event timestamps, so late rows attribute correctly.
3235
+ // confirm. Only this property's own keys are claimed: batches belonging to
3236
+ // another property sharing this origin are left for that property's next
3237
+ // pageview, since resending them here would file them under this property.
3238
+ // Keys are claimed (removed) before sending so concurrent tabs don't
3239
+ // double-send; the batch bytes carry their original pageview, session, and
3240
+ // event timestamps, so late rows attribute correctly.
2572
3241
  resendPersistedBatches() {
2573
3242
  try {
2574
3243
  if (typeof localStorage === "undefined" || !this.config.endpoint) return;
3244
+ if (!this.config.propertyId) return;
3245
+ const ownPrefix = this.pendingKeyPrefix();
2575
3246
  const keys = [];
3247
+ const legacyKeys = [];
2576
3248
  for (let i = 0; i < localStorage.length; i++) {
2577
3249
  const k = localStorage.key(i);
2578
- if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) keys.push(k);
3250
+ if (!k || !k.startsWith(PENDING_BATCH_KEY_PREFIX)) continue;
3251
+ if (k.startsWith(ownPrefix)) {
3252
+ keys.push(k);
3253
+ } else if (isLegacyPendingKey(k)) {
3254
+ legacyKeys.push(k);
3255
+ }
3256
+ }
3257
+ for (const key of legacyKeys) {
3258
+ try {
3259
+ localStorage.removeItem(key);
3260
+ } catch {
3261
+ }
2579
3262
  }
2580
3263
  for (const key of keys) {
2581
3264
  let value = null;
@@ -2586,7 +3269,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2586
3269
  continue;
2587
3270
  }
2588
3271
  if (!value) continue;
2589
- const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
3272
+ const ts = parseInt(key.slice(ownPrefix.length), 10);
2590
3273
  if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
2591
3274
  let encoded;
2592
3275
  try {
@@ -2596,11 +3279,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2596
3279
  }
2597
3280
  if (encoded.byteLength === 0) continue;
2598
3281
  this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
2599
- this.sendFetch({
2600
- url: `${this.config.endpoint}/${this.config.propertyId}`,
2601
- encoded,
2602
- events: []
2603
- });
3282
+ this.sendFetch(
3283
+ {
3284
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
3285
+ encoded,
3286
+ events: []
3287
+ },
3288
+ false,
3289
+ void 0,
3290
+ true
3291
+ );
2604
3292
  }
2605
3293
  } catch {
2606
3294
  }
@@ -2625,11 +3313,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2625
3313
  }
2626
3314
  }
2627
3315
  };
2628
- function extractAnalyticsOptions(config) {
3316
+ function extractAnalyticsOptions(config, allowProviderless = true) {
2629
3317
  if (!config) return null;
2630
3318
  if (Array.isArray(config)) {
2631
- const matched = config.find((item) => item?.provider === "bidkernel") ?? config.find((item) => item && typeof item === "object" && item.provider === void 0);
2632
- return matched ? extractAnalyticsOptions(matched) : null;
3319
+ const matched = config.find((item) => item?.provider === "bidkernel") ?? (allowProviderless ? config.find(
3320
+ (item) => item && typeof item === "object" && item.provider === void 0
3321
+ ) : void 0);
3322
+ return matched ? extractAnalyticsOptions(matched, allowProviderless) : null;
2633
3323
  }
2634
3324
  if (typeof config === "object") {
2635
3325
  if (config.options && typeof config.options === "object") {
@@ -2671,18 +3361,27 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2671
3361
  win[pbjsGlobalName] = win[pbjsGlobalName] || {};
2672
3362
  const pbjs = win[pbjsGlobalName];
2673
3363
  const enableInstance = (options) => {
2674
- if (win._bidkernelPrebidAnalytics) {
3364
+ const previous = win._bidkernelPrebidAnalytics;
3365
+ const pinnedEndpoint = previous?.getEndpoint?.() || "";
3366
+ const merged = {
3367
+ pbjsGlobalName,
3368
+ ...defaults,
3369
+ ...options
3370
+ };
3371
+ if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
3372
+ console.warn(
3373
+ `[bidkernel] Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
3374
+ );
3375
+ merged.endpoint = pinnedEndpoint;
3376
+ }
3377
+ if (previous) {
2675
3378
  try {
2676
- win._bidkernelPrebidAnalytics.disable();
3379
+ previous.disable();
2677
3380
  } catch (e) {
2678
3381
  console.warn("[bidkernel] Failed to disable previous analytics instance:", e);
2679
3382
  }
2680
3383
  }
2681
- const analytics = new BidkernelPrebidAnalytics({
2682
- pbjsGlobalName,
2683
- ...defaults,
2684
- ...options
2685
- });
3384
+ const analytics = new BidkernelPrebidAnalytics(merged);
2686
3385
  analytics.enable();
2687
3386
  win._bidkernelPrebidAnalytics = analytics;
2688
3387
  return analytics;
@@ -2693,7 +3392,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2693
3392
  pbjs.adapterManager.registerAnalyticsAdapter({
2694
3393
  adapter: {
2695
3394
  enableAnalytics: (config) => {
2696
- const options = extractAnalyticsOptions(config);
3395
+ const options = extractAnalyticsOptions(config, !Array.isArray(config));
2697
3396
  if (options) {
2698
3397
  enableInstance(options);
2699
3398
  }
@@ -2759,5 +3458,8 @@ function getbidkernel(alias = "bidkernel") {
2759
3458
  PrebidEventDeduper,
2760
3459
  getPrebidEventKey,
2761
3460
  getbidkernel,
3461
+ hookImaPrototype,
3462
+ initImaInterception,
3463
+ isTrustedEndpoint,
2762
3464
  registerPrebidAnalytics
2763
3465
  });