@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.mjs CHANGED
@@ -4,26 +4,33 @@ var TraceEventType = {
4
4
  TRACE_EVENT_TYPE_UNSPECIFIED: 0,
5
5
  AUCTION_START: 1,
6
6
  AUCTION_END: 2,
7
- BID_REQUEST: 3,
8
- BID_RESPONSE: 4,
9
- BID_WIN: 5,
10
- BID_TIMEOUT: 6,
7
+ BID_RESPONSE: 3,
8
+ BID_WIN: 4,
11
9
  /** IMPRESSION - ad rendered into DOM */
12
- IMPRESSION: 7,
10
+ IMPRESSION: 5,
13
11
  /** VIEWABLE - IAB viewability threshold met (boolean, once per render cycle) */
14
- VIEWABLE: 8,
12
+ VIEWABLE: 6,
15
13
  /** TIME_IN_VIEW - accumulated in-viewport duration (once per render cycle end) */
16
- TIME_IN_VIEW: 14,
17
- REFRESH: 9,
18
- ERROR: 10,
19
- SLOT_DEFINED: 11,
20
- SLOT_DESTROYED: 12,
14
+ TIME_IN_VIEW: 7,
15
+ REFRESH: 8,
16
+ ERROR: 9,
17
+ SLOT_DEFINED: 10,
18
+ SLOT_DESTROYED: 11,
21
19
  /** CLICK - ad click-through */
22
- CLICK: 13,
23
- /** NO_BID - bidder responded with no bid */
24
- NO_BID: 15,
20
+ CLICK: 12,
25
21
  /** AD_RENDER_FAILED - Prebid failed to render the winning ad */
26
- AD_RENDER_FAILED: 16,
22
+ AD_RENDER_FAILED: 13,
23
+ UNRECOGNIZED: -1
24
+ };
25
+ var BidderOutcome = {
26
+ BIDDER_OUTCOME_UNSPECIFIED: 0,
27
+ /** BIDDER_OUTCOME_BID - returned a bid (CPM > 0) */
28
+ BIDDER_OUTCOME_BID: 1,
29
+ BIDDER_OUTCOME_NO_BID: 2,
30
+ BIDDER_OUTCOME_TIMEOUT: 3,
31
+ BIDDER_OUTCOME_ERROR: 4,
32
+ /** BIDDER_OUTCOME_NO_RESPONSE - requested, nothing came back by auction end */
33
+ BIDDER_OUTCOME_NO_RESPONSE: 5,
27
34
  UNRECOGNIZED: -1
28
35
  };
29
36
  function createBaseTraceEventBatch() {
@@ -254,7 +261,8 @@ function createBaseTraceEvent() {
254
261
  namespace: "",
255
262
  eventId: "",
256
263
  metadata: {},
257
- viewableDurationMs: void 0
264
+ viewableDurationMs: void 0,
265
+ bidderOutcomes: []
258
266
  };
259
267
  }
260
268
  var TraceEvent = {
@@ -292,6 +300,11 @@ var TraceEvent = {
292
300
  if (message.viewableDurationMs !== void 0) {
293
301
  writer.uint32(89).double(message.viewableDurationMs);
294
302
  }
303
+ if (message.bidderOutcomes !== void 0 && message.bidderOutcomes.length !== 0) {
304
+ for (const v of message.bidderOutcomes) {
305
+ BidderOutcomeEntry.encode(v, writer.uint32(98).fork()).join();
306
+ }
307
+ }
295
308
  return writer;
296
309
  },
297
310
  decode(input, length) {
@@ -381,6 +394,16 @@ var TraceEvent = {
381
394
  message.viewableDurationMs = reader.double();
382
395
  continue;
383
396
  }
397
+ case 12: {
398
+ if (tag !== 98) {
399
+ break;
400
+ }
401
+ const el = BidderOutcomeEntry.decode(reader, reader.uint32());
402
+ if (el !== void 0) {
403
+ message.bidderOutcomes.push(el);
404
+ }
405
+ continue;
406
+ }
384
407
  }
385
408
  if ((tag & 7) === 4 || tag === 0) {
386
409
  break;
@@ -413,6 +436,7 @@ var TraceEvent = {
413
436
  {}
414
437
  );
415
438
  message.viewableDurationMs = object.viewableDurationMs ?? void 0;
439
+ message.bidderOutcomes = object.bidderOutcomes?.map((e) => BidderOutcomeEntry.fromPartial(e)) || [];
416
440
  return message;
417
441
  }
418
442
  };
@@ -468,6 +492,91 @@ var TraceEvent_MetadataEntry = {
468
492
  return message;
469
493
  }
470
494
  };
495
+ function createBaseBidderOutcomeEntry() {
496
+ return { bidder: "", adUnitCode: "", mediaType: "", outcome: 0, latencyMs: 0 };
497
+ }
498
+ var BidderOutcomeEntry = {
499
+ encode(message, writer = new BinaryWriter()) {
500
+ if (message.bidder !== void 0 && message.bidder !== "") {
501
+ writer.uint32(10).string(message.bidder);
502
+ }
503
+ if (message.adUnitCode !== void 0 && message.adUnitCode !== "") {
504
+ writer.uint32(18).string(message.adUnitCode);
505
+ }
506
+ if (message.mediaType !== void 0 && message.mediaType !== "") {
507
+ writer.uint32(26).string(message.mediaType);
508
+ }
509
+ if (message.outcome !== void 0 && message.outcome !== 0) {
510
+ writer.uint32(32).int32(message.outcome);
511
+ }
512
+ if (message.latencyMs !== void 0 && message.latencyMs !== 0) {
513
+ writer.uint32(41).double(message.latencyMs);
514
+ }
515
+ return writer;
516
+ },
517
+ decode(input, length) {
518
+ const reader = input instanceof BinaryReader ? input : new BinaryReader(input);
519
+ const end = length === void 0 ? reader.len : reader.pos + length;
520
+ const message = createBaseBidderOutcomeEntry();
521
+ while (reader.pos < end) {
522
+ const tag = reader.uint32();
523
+ switch (tag >>> 3) {
524
+ case 1: {
525
+ if (tag !== 10) {
526
+ break;
527
+ }
528
+ message.bidder = reader.string();
529
+ continue;
530
+ }
531
+ case 2: {
532
+ if (tag !== 18) {
533
+ break;
534
+ }
535
+ message.adUnitCode = reader.string();
536
+ continue;
537
+ }
538
+ case 3: {
539
+ if (tag !== 26) {
540
+ break;
541
+ }
542
+ message.mediaType = reader.string();
543
+ continue;
544
+ }
545
+ case 4: {
546
+ if (tag !== 32) {
547
+ break;
548
+ }
549
+ message.outcome = reader.int32();
550
+ continue;
551
+ }
552
+ case 5: {
553
+ if (tag !== 41) {
554
+ break;
555
+ }
556
+ message.latencyMs = reader.double();
557
+ continue;
558
+ }
559
+ }
560
+ if ((tag & 7) === 4 || tag === 0) {
561
+ break;
562
+ }
563
+ reader.skip(tag & 7);
564
+ }
565
+ return message;
566
+ },
567
+ create(base) {
568
+ return BidderOutcomeEntry.fromPartial(base ?? {});
569
+ },
570
+ fromPartial(object) {
571
+ const message = createBaseBidderOutcomeEntry();
572
+ message.bidder = object.bidder ?? "";
573
+ message.adUnitCode = object.adUnitCode ?? "";
574
+ message.mediaType = object.mediaType ?? "";
575
+ message.outcome = object.outcome ?? 0;
576
+ message.latencyMs = object.latencyMs ?? 0;
577
+ return message;
578
+ }
579
+ };
471
580
  function createBaseBidTrace() {
472
581
  return {
473
582
  bidder: "",
@@ -628,6 +737,8 @@ var MAX_QUEUE_SIZE = 200;
628
737
  var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
629
738
  var MAX_CONSECUTIVE_SEND_FAILURES = 10;
630
739
  var MAX_PAYLOAD_BYTES = 32 * 1024;
740
+ var MAX_METADATA_VALUE_CHARS = 1024;
741
+ var MAX_METADATA_ENTRIES = 16;
631
742
  var SAFE_CONTENT_TYPE = "text/plain";
632
743
  var PENDING_BATCH_KEY_PREFIX = "_bidkernel_pending_";
633
744
  var MAX_PENDING_BATCHES = 20;
@@ -636,11 +747,8 @@ var PENDING_BATCH_CLEANUP_DELAY_MS = 1e4;
636
747
  var EVENT_NAME_TO_TYPE = {
637
748
  auctionStart: TraceEventType.AUCTION_START,
638
749
  auctionEnd: TraceEventType.AUCTION_END,
639
- bidRequest: TraceEventType.BID_REQUEST,
640
750
  bidResponse: TraceEventType.BID_RESPONSE,
641
- bidTimeout: TraceEventType.BID_TIMEOUT,
642
751
  bidWin: TraceEventType.BID_WIN,
643
- noBid: TraceEventType.NO_BID,
644
752
  adRenderFailed: TraceEventType.AD_RENDER_FAILED,
645
753
  click: TraceEventType.CLICK,
646
754
  impression: TraceEventType.IMPRESSION,
@@ -653,6 +761,10 @@ var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
653
761
  TraceEventType.BID_WIN,
654
762
  TraceEventType.CLICK
655
763
  ]);
764
+ var CACHED_BID_TTL_MS = 30 * 60 * 1e3;
765
+ var MAX_CACHED_BID_KEYS = 200;
766
+ var MAX_IMPRESSION_KEYS = 1e3;
767
+ var MAX_TRACKED_AUCTIONS = 50;
656
768
  var SESSION_KEY = "_bidkernel_session";
657
769
  var SESSION_TS_KEY = "_bidkernel_session_ts";
658
770
  var THIRTY_MINUTES_MS = 30 * 60 * 1e3;
@@ -684,6 +796,46 @@ function base64ToBytes(b64) {
684
796
  }
685
797
  return bytes;
686
798
  }
799
+ function isTrustedEndpoint(raw) {
800
+ if (!raw) return false;
801
+ let url;
802
+ try {
803
+ url = new URL(raw);
804
+ } catch {
805
+ return false;
806
+ }
807
+ if (url.username || url.password) return false;
808
+ if (url.protocol === "https:") return true;
809
+ if (url.protocol === "http:") {
810
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
811
+ }
812
+ return false;
813
+ }
814
+ function boundMetadata(metadata) {
815
+ const bounded = {};
816
+ let count = 0;
817
+ for (const key of Object.keys(metadata)) {
818
+ if (count >= MAX_METADATA_ENTRIES) break;
819
+ const value = metadata[key];
820
+ if (value === void 0 || value === null) continue;
821
+ const str = String(value);
822
+ bounded[key] = str.length > MAX_METADATA_VALUE_CHARS ? `${str.slice(0, MAX_METADATA_VALUE_CHARS)}...[truncated]` : str;
823
+ count++;
824
+ }
825
+ return bounded;
826
+ }
827
+ function isLegacyPendingKey(key) {
828
+ const parts = key.slice(PENDING_BATCH_KEY_PREFIX.length).split("_");
829
+ return parts.length === 2 && /^\d+$/.test(parts[0]);
830
+ }
831
+ function sameEndpointOrigin(a, b) {
832
+ if (!a || !b) return false;
833
+ try {
834
+ return new URL(a).origin === new URL(b).origin;
835
+ } catch {
836
+ return false;
837
+ }
838
+ }
687
839
  function extendSession() {
688
840
  const now = Date.now();
689
841
  inMemorySessionTs = now;
@@ -800,22 +952,6 @@ var PrebidEventDeduper = class {
800
952
  return false;
801
953
  }
802
954
  };
803
- function parseSize(raw) {
804
- if (!raw) return { width: 0, height: 0 };
805
- let cur = raw;
806
- while (Array.isArray(cur) && cur.length > 0 && Array.isArray(cur[0])) {
807
- cur = cur[0];
808
- }
809
- if (Array.isArray(cur) && cur.length >= 2) {
810
- const w = Number(cur[0]);
811
- const h = Number(cur[1]);
812
- return {
813
- width: Number.isFinite(w) ? w : 0,
814
- height: Number.isFinite(h) ? h : 0
815
- };
816
- }
817
- return { width: 0, height: 0 };
818
- }
819
955
  function parseBidDimensions(bid) {
820
956
  if (Number.isFinite(bid?.width) && Number.isFinite(bid?.height)) {
821
957
  return { width: bid.width, height: bid.height };
@@ -831,8 +967,264 @@ function parseBidDimensions(bid) {
831
967
  height: Number.isFinite(bid?.height) ? bid.height : 0
832
968
  };
833
969
  }
970
+ var adsLoaderListenerMap = /* @__PURE__ */ new WeakMap();
971
+ function hookAdsManagerLoadedEvent(loadedEventCtor) {
972
+ if (!loadedEventCtor) return;
973
+ try {
974
+ const loadedProto = loadedEventCtor.prototype;
975
+ if (loadedProto && typeof loadedProto.getAdsManager === "function" && !loadedProto.getAdsManager.__bidkernelHooked) {
976
+ const originalGetAdsManager = loadedProto.getAdsManager;
977
+ const hookedGetAdsManager = function(...args) {
978
+ const adsManager = originalGetAdsManager.apply(this, args);
979
+ if (adsManager) {
980
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
981
+ try {
982
+ instance.attachImaAdsManager(adsManager);
983
+ } catch {
984
+ }
985
+ }
986
+ }
987
+ return adsManager;
988
+ };
989
+ hookedGetAdsManager.__bidkernelHooked = true;
990
+ loadedProto.getAdsManager = hookedGetAdsManager;
991
+ }
992
+ } catch {
993
+ }
994
+ }
995
+ function hookAdsManagerPrototype(adsManagerCtorOrProto) {
996
+ if (!adsManagerCtorOrProto) return;
997
+ try {
998
+ const proto = adsManagerCtorOrProto.prototype || adsManagerCtorOrProto;
999
+ if (proto && typeof proto.init === "function" && !proto.init.__bidkernelHooked) {
1000
+ const origInit = proto.init;
1001
+ const hookedInit = function(...args) {
1002
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1003
+ try {
1004
+ instance.attachImaAdsManager(this);
1005
+ } catch {
1006
+ }
1007
+ }
1008
+ return origInit.apply(this, args);
1009
+ };
1010
+ hookedInit.__bidkernelHooked = true;
1011
+ proto.init = hookedInit;
1012
+ }
1013
+ if (proto && typeof proto.start === "function" && !proto.start.__bidkernelHooked) {
1014
+ const origStart = proto.start;
1015
+ const hookedStart = function(...args) {
1016
+ for (const instance of BidkernelPrebidAnalytics.getActiveInstances()) {
1017
+ try {
1018
+ instance.attachImaAdsManager(this);
1019
+ } catch {
1020
+ }
1021
+ }
1022
+ return origStart.apply(this, args);
1023
+ };
1024
+ hookedStart.__bidkernelHooked = true;
1025
+ proto.start = hookedStart;
1026
+ }
1027
+ } catch {
1028
+ }
1029
+ }
1030
+ function hookAdsLoader(adsLoaderCtor) {
1031
+ if (!adsLoaderCtor) return;
1032
+ try {
1033
+ const proto = adsLoaderCtor.prototype;
1034
+ if (!proto) return;
1035
+ if (typeof proto.addEventListener === "function" && !proto.addEventListener.__bidkernelHooked) {
1036
+ const origAddEventListener = proto.addEventListener;
1037
+ const hookedAddEventListener = function(type, listener, ...rest) {
1038
+ if ((type === "adsManagerLoaded" || type === (this?.AdsManagerLoadedEvent?.Type?.ADS_MANAGER_LOADED || "adsManagerLoaded")) && typeof listener === "function") {
1039
+ let wrappedListener = adsLoaderListenerMap.get(listener);
1040
+ if (!wrappedListener) {
1041
+ wrappedListener = function(event) {
1042
+ if (event && typeof event.getAdsManager === "function" && !event.getAdsManager.__bidkernelHooked) {
1043
+ const origGetAdsManager = event.getAdsManager;
1044
+ event.getAdsManager = function(...args) {
1045
+ const adsManager = origGetAdsManager.apply(this, args);
1046
+ if (adsManager) {
1047
+ for (const sdk of BidkernelPrebidAnalytics.getActiveInstances()) {
1048
+ try {
1049
+ sdk.attachImaAdsManager(adsManager);
1050
+ } catch {
1051
+ }
1052
+ }
1053
+ }
1054
+ return adsManager;
1055
+ };
1056
+ event.getAdsManager.__bidkernelHooked = true;
1057
+ }
1058
+ return listener.apply(this, arguments);
1059
+ };
1060
+ adsLoaderListenerMap.set(listener, wrappedListener);
1061
+ }
1062
+ return origAddEventListener.call(this, type, wrappedListener, ...rest);
1063
+ }
1064
+ return origAddEventListener.call(this, type, listener, ...rest);
1065
+ };
1066
+ hookedAddEventListener.__bidkernelHooked = true;
1067
+ proto.addEventListener = hookedAddEventListener;
1068
+ }
1069
+ if (typeof proto.removeEventListener === "function" && !proto.removeEventListener.__bidkernelHooked) {
1070
+ const origRemoveEventListener = proto.removeEventListener;
1071
+ const hookedRemoveEventListener = function(type, listener, ...rest) {
1072
+ const wrapped = typeof listener === "function" ? adsLoaderListenerMap.get(listener) : void 0;
1073
+ return origRemoveEventListener.call(this, type, wrapped || listener, ...rest);
1074
+ };
1075
+ hookedRemoveEventListener.__bidkernelHooked = true;
1076
+ proto.removeEventListener = hookedRemoveEventListener;
1077
+ }
1078
+ } catch {
1079
+ }
1080
+ }
1081
+ function hookImaPrototype(ima) {
1082
+ if (!ima || typeof ima !== "object") return;
1083
+ try {
1084
+ if (ima.AdsManagerLoadedEvent) {
1085
+ hookAdsManagerLoadedEvent(ima.AdsManagerLoadedEvent);
1086
+ }
1087
+ const loadedDesc = Object.getOwnPropertyDescriptor(ima, "AdsManagerLoadedEvent");
1088
+ if (!loadedDesc || loadedDesc.configurable) {
1089
+ let _loadedEvent = ima.AdsManagerLoadedEvent;
1090
+ try {
1091
+ Object.defineProperty(ima, "AdsManagerLoadedEvent", {
1092
+ configurable: true,
1093
+ enumerable: true,
1094
+ get() {
1095
+ return _loadedEvent;
1096
+ },
1097
+ set(val) {
1098
+ _loadedEvent = val;
1099
+ hookAdsManagerLoadedEvent(val);
1100
+ }
1101
+ });
1102
+ } catch {
1103
+ }
1104
+ }
1105
+ if (ima.AdsLoader) {
1106
+ hookAdsLoader(ima.AdsLoader);
1107
+ }
1108
+ const loaderDesc = Object.getOwnPropertyDescriptor(ima, "AdsLoader");
1109
+ if (!loaderDesc || loaderDesc.configurable) {
1110
+ let _loader = ima.AdsLoader;
1111
+ try {
1112
+ Object.defineProperty(ima, "AdsLoader", {
1113
+ configurable: true,
1114
+ enumerable: true,
1115
+ get() {
1116
+ return _loader;
1117
+ },
1118
+ set(val) {
1119
+ _loader = val;
1120
+ hookAdsLoader(val);
1121
+ }
1122
+ });
1123
+ } catch {
1124
+ }
1125
+ }
1126
+ if (ima.AdsManager) {
1127
+ hookAdsManagerPrototype(ima.AdsManager);
1128
+ }
1129
+ const mgrDesc = Object.getOwnPropertyDescriptor(ima, "AdsManager");
1130
+ if (!mgrDesc || mgrDesc.configurable) {
1131
+ let _mgr = ima.AdsManager;
1132
+ try {
1133
+ Object.defineProperty(ima, "AdsManager", {
1134
+ configurable: true,
1135
+ enumerable: true,
1136
+ get() {
1137
+ return _mgr;
1138
+ },
1139
+ set(val) {
1140
+ _mgr = val;
1141
+ hookAdsManagerPrototype(val);
1142
+ }
1143
+ });
1144
+ } catch {
1145
+ }
1146
+ }
1147
+ } catch {
1148
+ }
1149
+ }
1150
+ function initImaInterception() {
1151
+ if (typeof window === "undefined") return;
1152
+ const win = window;
1153
+ if (win.google?.ima) {
1154
+ hookImaPrototype(win.google.ima);
1155
+ }
1156
+ if (win.google && typeof win.google === "object") {
1157
+ const imaDesc = Object.getOwnPropertyDescriptor(win.google, "ima");
1158
+ if (!imaDesc || imaDesc.configurable) {
1159
+ let _ima = win.google.ima;
1160
+ try {
1161
+ Object.defineProperty(win.google, "ima", {
1162
+ configurable: true,
1163
+ enumerable: true,
1164
+ get() {
1165
+ return _ima;
1166
+ },
1167
+ set(val) {
1168
+ _ima = val;
1169
+ hookImaPrototype(val);
1170
+ }
1171
+ });
1172
+ } catch {
1173
+ }
1174
+ }
1175
+ }
1176
+ const googleDesc = Object.getOwnPropertyDescriptor(win, "google");
1177
+ if (!googleDesc || googleDesc.configurable) {
1178
+ let _google = win.google;
1179
+ try {
1180
+ Object.defineProperty(win, "google", {
1181
+ configurable: true,
1182
+ enumerable: true,
1183
+ get() {
1184
+ return _google;
1185
+ },
1186
+ set(val) {
1187
+ _google = val;
1188
+ if (_google && typeof _google === "object") {
1189
+ if (_google.ima) {
1190
+ hookImaPrototype(_google.ima);
1191
+ }
1192
+ const iDesc = Object.getOwnPropertyDescriptor(_google, "ima");
1193
+ if (!iDesc || iDesc.configurable) {
1194
+ let _i = _google.ima;
1195
+ try {
1196
+ Object.defineProperty(_google, "ima", {
1197
+ configurable: true,
1198
+ enumerable: true,
1199
+ get() {
1200
+ return _i;
1201
+ },
1202
+ set(iv) {
1203
+ _i = iv;
1204
+ hookImaPrototype(iv);
1205
+ }
1206
+ });
1207
+ } catch {
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ });
1213
+ } catch {
1214
+ }
1215
+ }
1216
+ }
1217
+ if (typeof window !== "undefined") {
1218
+ initImaInterception();
1219
+ }
834
1220
  var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
835
1221
  static activeInstances = /* @__PURE__ */ new Set();
1222
+ static getActiveInstances() {
1223
+ return _BidkernelPrebidAnalytics.activeInstances;
1224
+ }
1225
+ static initImaInterception() {
1226
+ initImaInterception();
1227
+ }
836
1228
  config;
837
1229
  queue = [];
838
1230
  errorCount = 0;
@@ -867,9 +1259,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
867
1259
  cachedWinningBids = /* @__PURE__ */ new Map();
868
1260
  // Active video player attachment cleanup routines
869
1261
  videoDetachCleanups = /* @__PURE__ */ new Set();
1262
+ // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1263
+ auctionBidderOutcomes = /* @__PURE__ */ new Map();
870
1264
  constructor(config) {
1265
+ const requestedEndpoint = config.endpoint || "";
1266
+ const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
871
1267
  this.config = {
872
- endpoint: config.endpoint || "",
1268
+ endpoint,
873
1269
  propertyId: config.propertyId || "",
874
1270
  pageviewId: config.pageviewId || generateUUID(),
875
1271
  sessionId: config.sessionId || getOrCreateSessionId(),
@@ -885,6 +1281,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
885
1281
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
886
1282
  attachPbjsListeners: config.attachPbjsListeners ?? true
887
1283
  };
1284
+ if (requestedEndpoint && !endpoint) {
1285
+ this.log(
1286
+ "ERROR",
1287
+ `Rejected untrusted analytics endpoint ${requestedEndpoint}: must be an absolute https:// URL without embedded credentials. Events will not be transmitted.`
1288
+ );
1289
+ }
888
1290
  this.boundFlushBeacon = () => {
889
1291
  this.flushAllSlotsTimeInView();
890
1292
  this.flushBeacon();
@@ -986,6 +1388,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
986
1388
  }
987
1389
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
988
1390
  this.resendPersistedBatches();
1391
+ initImaInterception();
1392
+ if (window.google?.ima) {
1393
+ hookImaPrototype(window.google.ima);
1394
+ }
989
1395
  }
990
1396
  }
991
1397
  disable() {
@@ -1022,6 +1428,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1022
1428
  this.cachedWinningBids.clear();
1023
1429
  this.slotEmittedImpressionKeys.clear();
1024
1430
  this.slotLastAuctionIds.clear();
1431
+ this.auctionBidderOutcomes.clear();
1025
1432
  if (this.flushTimer) {
1026
1433
  clearInterval(this.flushTimer);
1027
1434
  this.flushTimer = null;
@@ -1425,7 +1832,139 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1425
1832
  this.slotEmittedImpressionKeys.delete(key);
1426
1833
  }
1427
1834
  }
1428
- this.cachedWinningBids.delete(slotId);
1835
+ const cached = this.cachedWinningBids.get(slotId);
1836
+ if (cached) this.deleteCachedBidEntry(cached);
1837
+ }
1838
+ // --- Bounded caches -------------------------------------------------------
1839
+ /**
1840
+ * Reads a cached winning bid, treating an entry past CACHED_BID_TTL_MS as
1841
+ * absent and dropping it. Every read goes through here so a stale bid can
1842
+ * never be married to a much later render.
1843
+ */
1844
+ getCachedBid(key) {
1845
+ const entry = this.cachedWinningBids.get(key);
1846
+ if (!entry) return void 0;
1847
+ if (Date.now() - entry.timestamp > CACHED_BID_TTL_MS) {
1848
+ this.deleteCachedBidEntry(entry);
1849
+ return void 0;
1850
+ }
1851
+ return entry;
1852
+ }
1853
+ /** Removes every alias pointing at one entry. Aliases are stored by identity. */
1854
+ deleteCachedBidEntry(entry) {
1855
+ for (const [key, value] of Array.from(this.cachedWinningBids)) {
1856
+ if (value === entry) this.cachedWinningBids.delete(key);
1857
+ }
1858
+ }
1859
+ /**
1860
+ * Drops expired entries, then evicts oldest-first until the key cap holds.
1861
+ * Map iterates in insertion order and one win's aliases are written
1862
+ * consecutively, so eviction takes a whole bid's aliases together.
1863
+ */
1864
+ pruneCachedWinningBids() {
1865
+ const now = Date.now();
1866
+ for (const [key, entry] of Array.from(this.cachedWinningBids)) {
1867
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) this.cachedWinningBids.delete(key);
1868
+ }
1869
+ while (this.cachedWinningBids.size > MAX_CACHED_BID_KEYS) {
1870
+ const oldest = this.cachedWinningBids.keys().next();
1871
+ if (oldest.done) break;
1872
+ this.cachedWinningBids.delete(oldest.value);
1873
+ }
1874
+ }
1875
+ /**
1876
+ * Searches cached winning bids for an entry matching the given filters (creativeId,
1877
+ * adId, or mediaType), falling back to the most recent winning video bid.
1878
+ */
1879
+ findCachedWinningBid(filter) {
1880
+ const now = Date.now();
1881
+ const isConsumed = (entry) => {
1882
+ return this.hasImpressionEmitted(
1883
+ entry.adUnitCode,
1884
+ void 0,
1885
+ entry.auctionId,
1886
+ entry.bidTrace.creativeId
1887
+ );
1888
+ };
1889
+ if (filter?.creativeId || filter?.adId) {
1890
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1891
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1892
+ if (filter.creativeId && (entry.bidTrace.creativeId === filter.creativeId || entry.rawBid?.creativeId === filter.creativeId) || filter.adId && (entry.rawBid?.adId === filter.adId || entry.adUnitCode === filter.adId)) {
1893
+ return entry;
1894
+ }
1895
+ }
1896
+ }
1897
+ let bestUnconsumedMatch;
1898
+ if (filter?.mediaType) {
1899
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1900
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1901
+ const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
1902
+ if (isEntryVideo) {
1903
+ if (!isConsumed(entry)) {
1904
+ if (!bestUnconsumedMatch || entry.timestamp < bestUnconsumedMatch.timestamp) {
1905
+ bestUnconsumedMatch = entry;
1906
+ }
1907
+ }
1908
+ }
1909
+ }
1910
+ if (bestUnconsumedMatch) return bestUnconsumedMatch;
1911
+ let bestFallbackMatch;
1912
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1913
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1914
+ const isEntryVideo = entry.bidTrace.mediaType === filter.mediaType || entry.rawBid?.mediaType === filter.mediaType || filter.mediaType === "video" && entry.rawBid?.mediaTypes?.video !== void 0;
1915
+ if (isEntryVideo) {
1916
+ if (!bestFallbackMatch || entry.timestamp > bestFallbackMatch.timestamp) {
1917
+ bestFallbackMatch = entry;
1918
+ }
1919
+ }
1920
+ }
1921
+ if (bestFallbackMatch) return bestFallbackMatch;
1922
+ }
1923
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1924
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1925
+ if (!isConsumed(entry)) {
1926
+ if (!bestUnconsumedMatch || entry.timestamp > bestUnconsumedMatch.timestamp) {
1927
+ bestUnconsumedMatch = entry;
1928
+ }
1929
+ }
1930
+ }
1931
+ if (bestUnconsumedMatch) return bestUnconsumedMatch;
1932
+ let bestRecent;
1933
+ for (const entry of Array.from(this.cachedWinningBids.values())) {
1934
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) continue;
1935
+ if (!bestRecent || entry.timestamp > bestRecent.timestamp) {
1936
+ bestRecent = entry;
1937
+ }
1938
+ }
1939
+ return bestRecent;
1940
+ }
1941
+ /** Records a dedup key, evicting oldest-first at the cap. */
1942
+ addImpressionKey(key) {
1943
+ if (this.slotEmittedImpressionKeys.has(key)) return;
1944
+ while (this.slotEmittedImpressionKeys.size >= MAX_IMPRESSION_KEYS) {
1945
+ const oldest = this.slotEmittedImpressionKeys.values().next();
1946
+ if (oldest.done) break;
1947
+ this.slotEmittedImpressionKeys.delete(oldest.value);
1948
+ }
1949
+ this.slotEmittedImpressionKeys.add(key);
1950
+ }
1951
+ /**
1952
+ * The bidder-outcome map for an auction, creating it if absent. Evicts the
1953
+ * oldest auction at the cap: without this, any auction whose auctionEnd never
1954
+ * fires (abandoned, navigated away from mid-flight) leaks its map forever.
1955
+ */
1956
+ getOrCreateAuctionOutcomes(auctionId) {
1957
+ let auctionMap = this.auctionBidderOutcomes.get(auctionId);
1958
+ if (!auctionMap) {
1959
+ auctionMap = /* @__PURE__ */ new Map();
1960
+ this.auctionBidderOutcomes.set(auctionId, auctionMap);
1961
+ }
1962
+ while (this.auctionBidderOutcomes.size > MAX_TRACKED_AUCTIONS) {
1963
+ const oldest = this.auctionBidderOutcomes.keys().next();
1964
+ if (oldest.done || oldest.value === auctionId) break;
1965
+ this.auctionBidderOutcomes.delete(oldest.value);
1966
+ }
1967
+ return auctionMap;
1429
1968
  }
1430
1969
  hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1431
1970
  const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
@@ -1445,14 +1984,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1445
1984
  }
1446
1985
  markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1447
1986
  const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
1448
- this.slotEmittedImpressionKeys.add(baseSlotKey);
1987
+ this.addImpressionKey(baseSlotKey);
1449
1988
  if (creativeId) {
1450
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1989
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1451
1990
  }
1452
1991
  if (auctionId) {
1453
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1992
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1454
1993
  if (creativeId) {
1455
- this.slotEmittedImpressionKeys.add(
1994
+ this.addImpressionKey(
1456
1995
  `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
1457
1996
  );
1458
1997
  }
@@ -1462,7 +2001,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1462
2001
  if (!this.isEnabled) return false;
1463
2002
  const resolvedSlotId = slotId || options?.adUnitCode || "";
1464
2003
  const adUnitCode = options?.adUnitCode || resolvedSlotId;
1465
- 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);
2004
+ 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);
1466
2005
  const rawBid = options?.bid || cached?.rawBid || {};
1467
2006
  const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
1468
2007
  const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
@@ -1558,14 +2097,40 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1558
2097
  return () => {
1559
2098
  };
1560
2099
  }
2100
+ if (!adsManager.__bidkernelAttachedInstances) {
2101
+ try {
2102
+ Object.defineProperty(adsManager, "__bidkernelAttachedInstances", {
2103
+ value: /* @__PURE__ */ new Set(),
2104
+ configurable: true,
2105
+ writable: true
2106
+ });
2107
+ } catch {
2108
+ adsManager.__bidkernelAttachedInstances = /* @__PURE__ */ new Set();
2109
+ }
2110
+ }
2111
+ if (adsManager.__bidkernelAttachedInstances.has(this)) {
2112
+ return () => {
2113
+ };
2114
+ }
2115
+ adsManager.__bidkernelAttachedInstances.add(this);
1561
2116
  const slotId = options?.slotId || options?.adUnitCode || "video";
1562
2117
  const adUnitCode = options?.adUnitCode || slotId;
1563
2118
  const auctionId = options?.auctionId || "";
1564
2119
  const transactionId = options?.transactionId || "";
1565
- const getWinningBid = () => {
1566
- 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);
2120
+ const getWinningBid = (adData) => {
2121
+ 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);
2122
+ if (!cached) {
2123
+ cached = this.findCachedWinningBid({
2124
+ creativeId: adData?.creativeId,
2125
+ adId: adData?.adId,
2126
+ mediaType: "video"
2127
+ });
2128
+ }
2129
+ return cached;
1567
2130
  };
2131
+ let adStarted = false;
1568
2132
  const onAdStartedOrImpression = (event) => {
2133
+ adStarted = true;
1569
2134
  this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
1570
2135
  const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
1571
2136
  const adData = {};
@@ -1576,16 +2141,20 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1576
2141
  adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
1577
2142
  adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
1578
2143
  }
1579
- const cached = getWinningBid();
2144
+ const cached = getWinningBid(adData);
2145
+ const resolvedSlotId = options?.slotId && options.slotId !== "video" ? options.slotId : options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || options?.slotId || options?.adUnitCode || "video";
2146
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || resolvedSlotId;
2147
+ const resolvedAuctionId = auctionId || cached?.auctionId || "";
2148
+ const resolvedTransactionId = transactionId || cached?.transactionId || "";
1580
2149
  const mergedBid = {
1581
2150
  ...cached?.rawBid || options?.bid,
1582
2151
  mediaType: "video",
1583
2152
  creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
1584
2153
  };
1585
- const emitted = this.recordImpression(slotId, {
1586
- adUnitCode,
1587
- auctionId: auctionId || cached?.auctionId,
1588
- transactionId: transactionId || cached?.transactionId,
2154
+ const emitted = this.recordImpression(resolvedSlotId, {
2155
+ adUnitCode: resolvedAdUnitCode,
2156
+ auctionId: resolvedAuctionId,
2157
+ transactionId: resolvedTransactionId,
1589
2158
  mediaType: "video",
1590
2159
  bid: mergedBid,
1591
2160
  metadata: {
@@ -1597,7 +2166,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1597
2166
  });
1598
2167
  if (emitted && options?.onImpression) {
1599
2168
  try {
1600
- options.onImpression(slotId, { ad, bid: mergedBid });
2169
+ options.onImpression(resolvedSlotId, { ad, bid: mergedBid });
1601
2170
  } catch (e) {
1602
2171
  this.log("ERROR", "Error in onImpression callback", e);
1603
2172
  }
@@ -1616,10 +2185,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1616
2185
  const onAdClick = (event) => {
1617
2186
  this.log("DEBUG", "IMA AdEvent.CLICK received", event);
1618
2187
  const cached = getWinningBid();
2188
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
1619
2189
  this.enqueue(TraceEventType.CLICK, "click", {
1620
2190
  auctionId: auctionId || cached?.auctionId,
1621
2191
  transactionId: transactionId || cached?.transactionId,
1622
- adUnitCode,
2192
+ adUnitCode: resolvedAdUnitCode,
1623
2193
  bid: cached?.bidTrace || options?.bid,
1624
2194
  metadata: {
1625
2195
  ...options?.metadata,
@@ -1633,14 +2203,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1633
2203
  const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
1634
2204
  const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
1635
2205
  const cached = getWinningBid();
2206
+ const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
1636
2207
  this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
1637
2208
  auctionId: auctionId || cached?.auctionId,
1638
2209
  transactionId: transactionId || cached?.transactionId,
1639
- adUnitCode,
2210
+ adUnitCode: resolvedAdUnitCode,
1640
2211
  bid: cached?.bidTrace || options?.bid,
1641
2212
  metadata: {
1642
2213
  ...options?.metadata,
1643
- reason: "ima_ad_error",
2214
+ reason: adStarted ? "ima_playback_error" : "ima_ad_error",
2215
+ playback_phase: adStarted ? "post_start" : "pre_start",
1644
2216
  message: msg,
1645
2217
  ...code ? { ima_error_code: String(code) } : {}
1646
2218
  },
@@ -1657,7 +2229,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1657
2229
  const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
1658
2230
  const adEventType = googleIma?.AdEvent?.Type || {};
1659
2231
  const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
1660
- const listeners = [
2232
+ const rawListeners = [
1661
2233
  { type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
1662
2234
  { type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
1663
2235
  {
@@ -1673,6 +2245,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1673
2245
  { type: adEventType.CLICK || "click", handler: onAdClick },
1674
2246
  { type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
1675
2247
  ];
2248
+ const seenListenerTypes = /* @__PURE__ */ new Set();
2249
+ const listeners = [];
2250
+ for (const l of rawListeners) {
2251
+ if (!seenListenerTypes.has(l.type)) {
2252
+ seenListenerTypes.add(l.type);
2253
+ listeners.push(l);
2254
+ }
2255
+ }
1676
2256
  for (const { type, handler } of listeners) {
1677
2257
  try {
1678
2258
  adsManager.addEventListener(type, handler);
@@ -1680,6 +2260,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1680
2260
  }
1681
2261
  }
1682
2262
  const cleanup = () => {
2263
+ adsManager.__bidkernelAttachedInstances?.delete(this);
1683
2264
  for (const { type, handler } of listeners) {
1684
2265
  try {
1685
2266
  adsManager.removeEventListener(type, handler);
@@ -1727,7 +2308,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1727
2308
  const auctionId = options?.auctionId || "";
1728
2309
  const transactionId = options?.transactionId || "";
1729
2310
  const getWinningBid = () => {
1730
- 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);
2311
+ 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);
1731
2312
  };
1732
2313
  let videoEl = null;
1733
2314
  if (el) {
@@ -1963,6 +2544,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1963
2544
  refreshIndex: record.refreshIndex
1964
2545
  };
1965
2546
  }
2547
+ /** The validated ingest endpoint in use, or "" when none passed validation. */
2548
+ getEndpoint() {
2549
+ return this.config.endpoint;
2550
+ }
1966
2551
  getRefreshIndex(slotId) {
1967
2552
  return this.slotRefreshIndices.get(slotId) ?? 0;
1968
2553
  }
@@ -2003,17 +2588,29 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2003
2588
  handleAuctionInit(data) {
2004
2589
  if (this.isDuplicate("auctionInit", data)) return;
2005
2590
  this.log("DEBUG", "auctionInit", data);
2591
+ const auctionId = data.auctionId || "";
2592
+ if (auctionId) {
2593
+ this.auctionBidderOutcomes.delete(auctionId);
2594
+ this.getOrCreateAuctionOutcomes(auctionId);
2595
+ }
2006
2596
  this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
2007
- auctionId: data.auctionId || "",
2597
+ auctionId,
2008
2598
  transactionId: data.transactionId || ""
2009
2599
  });
2010
2600
  }
2011
2601
  handleAuctionEnd(data) {
2012
2602
  if (this.isDuplicate("auctionEnd", data)) return;
2013
2603
  this.log("DEBUG", "auctionEnd", data);
2604
+ const auctionId = data.auctionId || "";
2605
+ const outcomesMap = auctionId ? this.auctionBidderOutcomes.get(auctionId) : void 0;
2606
+ const bidderOutcomes = outcomesMap ? Array.from(outcomesMap.values()) : [];
2607
+ if (auctionId) {
2608
+ this.auctionBidderOutcomes.delete(auctionId);
2609
+ }
2014
2610
  this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
2015
- auctionId: data.auctionId || "",
2016
- transactionId: data.transactionId || ""
2611
+ auctionId,
2612
+ transactionId: data.transactionId || "",
2613
+ bidderOutcomes
2017
2614
  });
2018
2615
  }
2019
2616
  handleBidRequested(data) {
@@ -2021,88 +2618,100 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2021
2618
  this.log("DEBUG", "bidRequested", data);
2022
2619
  const auctionId = data.auctionId || "";
2023
2620
  const bidder = data.bidderCode || data.bidder || "";
2621
+ if (!auctionId || !bidder) return;
2622
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2024
2623
  if (Array.isArray(data.bids)) {
2025
2624
  data.bids.forEach((bid) => {
2625
+ const adUnitCode = bid.adUnitCode || "";
2026
2626
  const mediaTypes = Object.keys(bid.mediaTypes || {});
2027
- const gpid = bid.ortb2Imp?.ext?.gpid || bid.gpid || data.gpid || "";
2028
- const transactionId = bid.transactionId || bid.ortb2Imp?.id || data.transactionId || "";
2029
- if (mediaTypes.length === 0) {
2030
- const mediaType = bid.mediaType || "banner";
2031
- const { width, height } = parseSize(bid.sizes || bid.playerSize);
2032
- this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
2033
- auctionId,
2034
- transactionId,
2035
- adUnitCode: bid.adUnitCode || "",
2036
- gpid,
2037
- bid: {
2038
- bidder,
2039
- mediaType,
2040
- width,
2041
- height
2042
- }
2627
+ const mediaType = mediaTypes.length > 0 ? mediaTypes[0] : bid.mediaType || "banner";
2628
+ const key = `${bidder}:${adUnitCode}`;
2629
+ if (!auctionMap.has(key)) {
2630
+ auctionMap.set(key, {
2631
+ bidder,
2632
+ adUnitCode,
2633
+ mediaType,
2634
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE,
2635
+ latencyMs: 0
2043
2636
  });
2044
- return;
2045
2637
  }
2046
- mediaTypes.forEach((mediaType) => {
2047
- let rawSize = null;
2048
- if (mediaType === "banner") {
2049
- rawSize = bid.mediaTypes?.banner?.sizes || bid.sizes;
2050
- } else if (mediaType === "video") {
2051
- rawSize = bid.mediaTypes?.video?.playerSize || bid.mediaTypes?.video?.sizes || bid.playerSize;
2052
- } else {
2053
- rawSize = bid.mediaTypes?.[mediaType]?.sizes;
2054
- }
2055
- const { width, height } = parseSize(rawSize);
2056
- this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
2057
- auctionId,
2058
- transactionId,
2059
- adUnitCode: bid.adUnitCode || "",
2060
- gpid,
2061
- bid: {
2062
- bidder,
2063
- mediaType,
2064
- width,
2065
- height
2066
- }
2067
- });
2068
- });
2069
2638
  });
2070
2639
  }
2071
2640
  }
2072
2641
  handleBidResponse(data) {
2073
2642
  if (this.isDuplicate("bidResponse", data)) return;
2074
2643
  this.log("DEBUG", "bidResponse", data);
2075
- this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
2076
- auctionId: data.auctionId || "",
2077
- transactionId: data.transactionId || "",
2078
- adUnitCode: data.adUnitCode || "",
2079
- bid: {
2080
- bidder: data.bidderCode || data.bidder || "",
2081
- cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
2082
- currency: data.originalCurrency ?? data.currency ?? "USD",
2083
- ...parseBidDimensions(data),
2084
- dealId: data.dealId || "",
2085
- mediaType: data.mediaType || "banner",
2086
- latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
2087
- advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2088
- creativeId: data.creativeId || ""
2644
+ const auctionId = data.auctionId || "";
2645
+ const bidder = data.bidderCode || data.bidder || "";
2646
+ const adUnitCode = data.adUnitCode || "";
2647
+ const cpm = Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0;
2648
+ const latencyMs = Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0;
2649
+ const resMediaTypes = Object.keys(data.mediaTypes || {});
2650
+ const mediaType = data.mediaType || (data.mediaTypes?.video ? "video" : resMediaTypes.length > 0 ? resMediaTypes[0] : "banner");
2651
+ if (auctionId && bidder) {
2652
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2653
+ const key = `${bidder}:${adUnitCode}`;
2654
+ const entry = auctionMap.get(key);
2655
+ const outcome = cpm > 0 ? BidderOutcome.BIDDER_OUTCOME_BID : BidderOutcome.BIDDER_OUTCOME_NO_BID;
2656
+ if (entry) {
2657
+ entry.outcome = outcome;
2658
+ entry.latencyMs = latencyMs;
2659
+ if (mediaType) entry.mediaType = mediaType;
2660
+ } else {
2661
+ auctionMap.set(key, {
2662
+ bidder,
2663
+ adUnitCode,
2664
+ mediaType,
2665
+ outcome,
2666
+ latencyMs
2667
+ });
2089
2668
  }
2090
- });
2669
+ }
2670
+ if (cpm > 0) {
2671
+ this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
2672
+ auctionId,
2673
+ transactionId: data.transactionId || "",
2674
+ adUnitCode,
2675
+ bid: {
2676
+ bidder,
2677
+ cpm,
2678
+ currency: data.originalCurrency ?? data.currency ?? "USD",
2679
+ ...parseBidDimensions(data),
2680
+ dealId: data.dealId || "",
2681
+ mediaType,
2682
+ latencyMs,
2683
+ advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2684
+ creativeId: data.creativeId || ""
2685
+ }
2686
+ });
2687
+ }
2091
2688
  }
2092
2689
  handleBidTimeout(data) {
2093
2690
  if (this.isDuplicate("bidTimeout", data)) return;
2094
2691
  this.log("DEBUG", "bidTimeout", data);
2095
2692
  const items = Array.isArray(data) ? data : data ? [data] : [];
2096
2693
  for (const t of items) {
2097
- this.enqueue(TraceEventType.BID_TIMEOUT, "bidTimeout", {
2098
- auctionId: t.auctionId || "",
2099
- transactionId: t.transactionId || "",
2100
- adUnitCode: t.adUnitCode || "",
2101
- bid: {
2102
- bidder: t.bidderCode || t.bidder || "",
2103
- latencyMs: Number.isFinite(t.timeout) ? t.timeout : 0
2694
+ const auctionId = t.auctionId || "";
2695
+ const bidder = t.bidderCode || t.bidder || "";
2696
+ const adUnitCode = t.adUnitCode || "";
2697
+ const latencyMs = Number.isFinite(t.timeout) ? t.timeout : 0;
2698
+ if (auctionId && bidder) {
2699
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2700
+ const key = `${bidder}:${adUnitCode}`;
2701
+ const entry = auctionMap.get(key);
2702
+ if (entry) {
2703
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_TIMEOUT;
2704
+ if (latencyMs > 0) entry.latencyMs = latencyMs;
2705
+ } else {
2706
+ auctionMap.set(key, {
2707
+ bidder,
2708
+ adUnitCode,
2709
+ mediaType: "banner",
2710
+ outcome: BidderOutcome.BIDDER_OUTCOME_TIMEOUT,
2711
+ latencyMs
2712
+ });
2104
2713
  }
2105
- });
2714
+ }
2106
2715
  }
2107
2716
  }
2108
2717
  handleBidWon(data) {
@@ -2111,13 +2720,15 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2111
2720
  const auctionId = data.auctionId || "";
2112
2721
  const transactionId = data.transactionId || "";
2113
2722
  const adUnitCode = data.adUnitCode || data.adId || "";
2723
+ const wonMediaTypes = Object.keys(data.mediaTypes || {});
2724
+ const resolvedMediaType = data.mediaType || (data.mediaTypes?.video ? "video" : wonMediaTypes.length > 0 ? wonMediaTypes[0] : "banner");
2114
2725
  const bidTrace = {
2115
2726
  bidder: data.bidderCode || data.bidder || "",
2116
2727
  cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
2117
2728
  currency: data.originalCurrency ?? data.currency ?? "USD",
2118
2729
  ...parseBidDimensions(data),
2119
2730
  dealId: data.dealId || "",
2120
- mediaType: data.mediaType || "banner",
2731
+ mediaType: resolvedMediaType,
2121
2732
  latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
2122
2733
  advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2123
2734
  creativeId: data.creativeId || ""
@@ -2141,6 +2752,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2141
2752
  this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
2142
2753
  }
2143
2754
  }
2755
+ this.pruneCachedWinningBids();
2144
2756
  }
2145
2757
  this.enqueue(TraceEventType.BID_WIN, "bidWon", {
2146
2758
  auctionId,
@@ -2152,14 +2764,27 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2152
2764
  handleNoBid(data) {
2153
2765
  if (this.isDuplicate("noBid", data)) return;
2154
2766
  this.log("DEBUG", "noBid", data);
2155
- this.enqueue(TraceEventType.NO_BID, "noBid", {
2156
- auctionId: data.auctionId || "",
2157
- transactionId: data.transactionId || "",
2158
- adUnitCode: data.adUnitCode || "",
2159
- bid: {
2160
- bidder: data.bidderCode || data.bidder || ""
2767
+ const auctionId = data.auctionId || "";
2768
+ const bidder = data.bidderCode || data.bidder || "";
2769
+ const adUnitCode = data.adUnitCode || "";
2770
+ if (auctionId && bidder) {
2771
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2772
+ const key = `${bidder}:${adUnitCode}`;
2773
+ const entry = auctionMap.get(key);
2774
+ if (entry) {
2775
+ if (entry.outcome === BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE) {
2776
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_NO_BID;
2777
+ }
2778
+ } else {
2779
+ auctionMap.set(key, {
2780
+ bidder,
2781
+ adUnitCode,
2782
+ mediaType: "banner",
2783
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_BID,
2784
+ latencyMs: 0
2785
+ });
2161
2786
  }
2162
- });
2787
+ }
2163
2788
  }
2164
2789
  handleAdRenderFailed(data) {
2165
2790
  if (this.isDuplicate("adRenderFailed", data)) return;
@@ -2191,14 +2816,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2191
2816
  const mediaType = bid.mediaType || "banner";
2192
2817
  const slotKey = data.adUnitCode || bid.adUnitCode || "";
2193
2818
  const altKey = data.adId || bid.adId || "";
2194
- 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);
2819
+ 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);
2820
+ 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";
2821
+ const resolvedMediaType = isVideo ? "video" : mediaType || cached?.bidTrace?.mediaType || "banner";
2195
2822
  const bidTrace = {
2196
2823
  bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
2197
2824
  cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
2198
2825
  currency: bid.originalCurrency ?? bid.currency ?? cached?.bidTrace?.currency ?? "USD",
2199
2826
  ...parseBidDimensions(bid).width ? parseBidDimensions(bid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(bid),
2200
2827
  dealId: bid.dealId || cached?.bidTrace?.dealId || "",
2201
- mediaType: mediaType || cached?.bidTrace?.mediaType || "banner",
2828
+ mediaType: resolvedMediaType,
2202
2829
  latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
2203
2830
  advertiserDomain: bid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
2204
2831
  creativeId: bid.creativeId || cached?.bidTrace?.creativeId || ""
@@ -2237,28 +2864,35 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2237
2864
  }
2238
2865
  }
2239
2866
  const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
2240
- const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
2241
- adUnitCode,
2242
- currentRefreshIndex,
2243
- resolvedAuctionId,
2244
- bidTrace.creativeId
2245
- ) : false;
2246
- if (!alreadyEmitted) {
2247
- if (adUnitCode) {
2248
- this.markImpressionEmitted(
2867
+ if (!isVideo) {
2868
+ const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
2869
+ adUnitCode,
2870
+ currentRefreshIndex,
2871
+ resolvedAuctionId,
2872
+ bidTrace.creativeId
2873
+ ) : false;
2874
+ if (!alreadyEmitted) {
2875
+ if (adUnitCode) {
2876
+ this.markImpressionEmitted(
2877
+ adUnitCode,
2878
+ currentRefreshIndex,
2879
+ resolvedAuctionId,
2880
+ bidTrace.creativeId
2881
+ );
2882
+ }
2883
+ this.enqueue(TraceEventType.IMPRESSION, "impression", {
2884
+ auctionId: resolvedAuctionId,
2885
+ transactionId: resolvedTransactionId,
2249
2886
  adUnitCode,
2250
- currentRefreshIndex,
2251
- resolvedAuctionId,
2252
- bidTrace.creativeId
2253
- );
2887
+ bid: bidTrace,
2888
+ metadata: { refresh_index: String(currentRefreshIndex) }
2889
+ });
2254
2890
  }
2255
- this.enqueue(TraceEventType.IMPRESSION, "impression", {
2256
- auctionId: resolvedAuctionId,
2257
- transactionId: resolvedTransactionId,
2258
- adUnitCode,
2259
- bid: bidTrace,
2260
- metadata: { refresh_index: String(currentRefreshIndex) }
2261
- });
2891
+ } else {
2892
+ this.log(
2893
+ "DEBUG",
2894
+ `adRenderSucceeded received for video ad unit ${adUnitCode || "unknown"}: suppressing IMPRESSION until verified video lifecycle event`
2895
+ );
2262
2896
  }
2263
2897
  if (this.config.viewabilityEnabled && typeof document !== "undefined") {
2264
2898
  let el = null;
@@ -2280,7 +2914,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2280
2914
  }
2281
2915
  if (!el && targetId) {
2282
2916
  try {
2283
- el = document.querySelector(`[data-ad-slot="${targetId}"]`) || document.querySelector(`#${targetId}`);
2917
+ for (const candidate of Array.from(document.querySelectorAll("[data-ad-slot]"))) {
2918
+ if (candidate.getAttribute("data-ad-slot") === targetId) {
2919
+ el = candidate;
2920
+ break;
2921
+ }
2922
+ }
2284
2923
  } catch {
2285
2924
  }
2286
2925
  }
@@ -2289,7 +2928,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2289
2928
  adUnitCode,
2290
2929
  auctionId: resolvedAuctionId,
2291
2930
  transactionId: resolvedTransactionId,
2292
- mediaType,
2931
+ mediaType: resolvedMediaType,
2293
2932
  bid: bidTrace,
2294
2933
  refreshIndex: currentRefreshIndex,
2295
2934
  emitRefreshEvent: false
@@ -2334,6 +2973,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2334
2973
  if (data.viewableDurationMs !== void 0) {
2335
2974
  protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
2336
2975
  }
2976
+ if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2977
+ protoEvent.bidderOutcomes = data.bidderOutcomes;
2978
+ }
2337
2979
  const metadata = { ...data.metadata };
2338
2980
  if (data.error !== void 0 && data.error !== null) {
2339
2981
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
@@ -2344,8 +2986,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2344
2986
  if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
2345
2987
  metadata.refresh_index = String(data.refreshIndex);
2346
2988
  }
2347
- if (Object.keys(metadata).length > 0) {
2348
- protoEvent.metadata = metadata;
2989
+ const boundedMetadata = boundMetadata(metadata);
2990
+ if (Object.keys(boundedMetadata).length > 0) {
2991
+ protoEvent.metadata = boundedMetadata;
2349
2992
  }
2350
2993
  this.queue.push(protoEvent);
2351
2994
  if (this.queue.length > MAX_QUEUE_SIZE) {
@@ -2383,7 +3026,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2383
3026
  // Shared by flush() and flushBeacon().
2384
3027
  drainBatch() {
2385
3028
  if (this.queue.length === 0 || !this.config.endpoint) return null;
2386
- let events = this.queue;
2387
3029
  const domain = typeof window !== "undefined" ? window.location.hostname : "";
2388
3030
  const buildBatch = (evts) => ({
2389
3031
  propertyId: this.config.propertyId,
@@ -2397,25 +3039,37 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2397
3039
  domain,
2398
3040
  events: evts
2399
3041
  });
2400
- let encoded = TraceEventBatch.encode(
2401
- buildBatch(events)
2402
- ).finish();
2403
- while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
2404
- events = events.slice(0, Math.floor(events.length / 2));
2405
- encoded = TraceEventBatch.encode(
3042
+ for (; ; ) {
3043
+ if (this.queue.length === 0) return null;
3044
+ let events = this.queue;
3045
+ let encoded = TraceEventBatch.encode(
2406
3046
  buildBatch(events)
2407
3047
  ).finish();
3048
+ while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
3049
+ events = events.slice(0, Math.floor(events.length / 2));
3050
+ encoded = TraceEventBatch.encode(
3051
+ buildBatch(events)
3052
+ ).finish();
3053
+ }
3054
+ if (encoded.byteLength > MAX_PAYLOAD_BYTES) {
3055
+ const [dropped] = this.queue.splice(0, 1);
3056
+ this.log(
3057
+ "WARN",
3058
+ `Dropping oversized event ${dropped?.eventName || dropped?.eventId} (${encoded.byteLength} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte cap on its own)`
3059
+ );
3060
+ continue;
3061
+ }
3062
+ this.queue = this.queue.slice(events.length);
3063
+ return {
3064
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
3065
+ // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
3066
+ // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
3067
+ encoded,
3068
+ events
3069
+ };
2408
3070
  }
2409
- this.queue = this.queue.slice(events.length);
2410
- return {
2411
- url: `${this.config.endpoint}/${this.config.propertyId}`,
2412
- // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
2413
- // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
2414
- encoded,
2415
- events
2416
- };
2417
3071
  }
2418
- sendFetch(payload, useKeepalive = false, onDelivered) {
3072
+ sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
2419
3073
  const fetchOpts = {
2420
3074
  method: "POST",
2421
3075
  // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
@@ -2432,15 +3086,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2432
3086
  this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
2433
3087
  return;
2434
3088
  }
2435
- this.handleSendFailure(payload.events);
3089
+ if (!isReplay) this.handleSendFailure(payload.events);
2436
3090
  } else {
2437
- this.consecutiveSendFailures = 0;
2438
- this.nextSendAllowedAt = 0;
3091
+ if (!isReplay) {
3092
+ this.consecutiveSendFailures = 0;
3093
+ this.nextSendAllowedAt = 0;
3094
+ }
2439
3095
  if (onDelivered) onDelivered();
2440
3096
  }
2441
3097
  }).catch((err) => {
2442
3098
  this.log("ERROR", "Failed to send batch", err);
2443
- this.handleSendFailure(payload.events);
3099
+ if (!isReplay) this.handleSendFailure(payload.events);
2444
3100
  });
2445
3101
  const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2446
3102
  if (proc && typeof proc._tickCallback === "function") {
@@ -2496,16 +3152,23 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2496
3152
  this.schedulePersistedCleanup();
2497
3153
  }
2498
3154
  // --- Exit-batch persistence -----------------------------------------------
3155
+ // Key prefix for this instance's property. encodeURIComponent keeps the
3156
+ // property segment free of the "_" the timestamp suffix is parsed against,
3157
+ // so a property ID containing one cannot forge another property's prefix.
3158
+ pendingKeyPrefix() {
3159
+ return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
3160
+ }
2499
3161
  persistBatch(encoded) {
2500
3162
  try {
2501
3163
  if (typeof localStorage === "undefined") return null;
3164
+ if (!this.config.propertyId) return null;
2502
3165
  let pendingCount = 0;
2503
3166
  for (let i = 0; i < localStorage.length; i++) {
2504
3167
  const k = localStorage.key(i);
2505
3168
  if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
2506
3169
  }
2507
3170
  if (pendingCount >= MAX_PENDING_BATCHES) return null;
2508
- const key = `${PENDING_BATCH_KEY_PREFIX}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
3171
+ const key = `${this.pendingKeyPrefix()}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2509
3172
  localStorage.setItem(key, bytesToBase64(encoded));
2510
3173
  this.persistedBatchKeys.push(key);
2511
3174
  return key;
@@ -2536,16 +3199,33 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2536
3199
  }, PENDING_BATCH_CLEANUP_DELAY_MS);
2537
3200
  }
2538
3201
  // Resend batches a previous pageview persisted at exit but could not
2539
- // confirm. Keys are claimed (removed) before sending so concurrent tabs
2540
- // don't double-send; the batch bytes carry their original pageview, session,
2541
- // and event timestamps, so late rows attribute correctly.
3202
+ // confirm. Only this property's own keys are claimed: batches belonging to
3203
+ // another property sharing this origin are left for that property's next
3204
+ // pageview, since resending them here would file them under this property.
3205
+ // Keys are claimed (removed) before sending so concurrent tabs don't
3206
+ // double-send; the batch bytes carry their original pageview, session, and
3207
+ // event timestamps, so late rows attribute correctly.
2542
3208
  resendPersistedBatches() {
2543
3209
  try {
2544
3210
  if (typeof localStorage === "undefined" || !this.config.endpoint) return;
3211
+ if (!this.config.propertyId) return;
3212
+ const ownPrefix = this.pendingKeyPrefix();
2545
3213
  const keys = [];
3214
+ const legacyKeys = [];
2546
3215
  for (let i = 0; i < localStorage.length; i++) {
2547
3216
  const k = localStorage.key(i);
2548
- if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) keys.push(k);
3217
+ if (!k || !k.startsWith(PENDING_BATCH_KEY_PREFIX)) continue;
3218
+ if (k.startsWith(ownPrefix)) {
3219
+ keys.push(k);
3220
+ } else if (isLegacyPendingKey(k)) {
3221
+ legacyKeys.push(k);
3222
+ }
3223
+ }
3224
+ for (const key of legacyKeys) {
3225
+ try {
3226
+ localStorage.removeItem(key);
3227
+ } catch {
3228
+ }
2549
3229
  }
2550
3230
  for (const key of keys) {
2551
3231
  let value = null;
@@ -2556,7 +3236,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2556
3236
  continue;
2557
3237
  }
2558
3238
  if (!value) continue;
2559
- const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
3239
+ const ts = parseInt(key.slice(ownPrefix.length), 10);
2560
3240
  if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
2561
3241
  let encoded;
2562
3242
  try {
@@ -2566,11 +3246,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2566
3246
  }
2567
3247
  if (encoded.byteLength === 0) continue;
2568
3248
  this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
2569
- this.sendFetch({
2570
- url: `${this.config.endpoint}/${this.config.propertyId}`,
2571
- encoded,
2572
- events: []
2573
- });
3249
+ this.sendFetch(
3250
+ {
3251
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
3252
+ encoded,
3253
+ events: []
3254
+ },
3255
+ false,
3256
+ void 0,
3257
+ true
3258
+ );
2574
3259
  }
2575
3260
  } catch {
2576
3261
  }
@@ -2595,11 +3280,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2595
3280
  }
2596
3281
  }
2597
3282
  };
2598
- function extractAnalyticsOptions(config) {
3283
+ function extractAnalyticsOptions(config, allowProviderless = true) {
2599
3284
  if (!config) return null;
2600
3285
  if (Array.isArray(config)) {
2601
- const matched = config.find((item) => item?.provider === "bidkernel") ?? config.find((item) => item && typeof item === "object" && item.provider === void 0);
2602
- return matched ? extractAnalyticsOptions(matched) : null;
3286
+ const matched = config.find((item) => item?.provider === "bidkernel") ?? (allowProviderless ? config.find(
3287
+ (item) => item && typeof item === "object" && item.provider === void 0
3288
+ ) : void 0);
3289
+ return matched ? extractAnalyticsOptions(matched, allowProviderless) : null;
2603
3290
  }
2604
3291
  if (typeof config === "object") {
2605
3292
  if (config.options && typeof config.options === "object") {
@@ -2641,18 +3328,27 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2641
3328
  win[pbjsGlobalName] = win[pbjsGlobalName] || {};
2642
3329
  const pbjs = win[pbjsGlobalName];
2643
3330
  const enableInstance = (options) => {
2644
- if (win._bidkernelPrebidAnalytics) {
3331
+ const previous = win._bidkernelPrebidAnalytics;
3332
+ const pinnedEndpoint = previous?.getEndpoint?.() || "";
3333
+ const merged = {
3334
+ pbjsGlobalName,
3335
+ ...defaults,
3336
+ ...options
3337
+ };
3338
+ if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
3339
+ console.warn(
3340
+ `[bidkernel] Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
3341
+ );
3342
+ merged.endpoint = pinnedEndpoint;
3343
+ }
3344
+ if (previous) {
2645
3345
  try {
2646
- win._bidkernelPrebidAnalytics.disable();
3346
+ previous.disable();
2647
3347
  } catch (e) {
2648
3348
  console.warn("[bidkernel] Failed to disable previous analytics instance:", e);
2649
3349
  }
2650
3350
  }
2651
- const analytics = new BidkernelPrebidAnalytics({
2652
- pbjsGlobalName,
2653
- ...defaults,
2654
- ...options
2655
- });
3351
+ const analytics = new BidkernelPrebidAnalytics(merged);
2656
3352
  analytics.enable();
2657
3353
  win._bidkernelPrebidAnalytics = analytics;
2658
3354
  return analytics;
@@ -2663,7 +3359,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2663
3359
  pbjs.adapterManager.registerAnalyticsAdapter({
2664
3360
  adapter: {
2665
3361
  enableAnalytics: (config) => {
2666
- const options = extractAnalyticsOptions(config);
3362
+ const options = extractAnalyticsOptions(config, !Array.isArray(config));
2667
3363
  if (options) {
2668
3364
  enableInstance(options);
2669
3365
  }
@@ -2728,5 +3424,8 @@ export {
2728
3424
  PrebidEventDeduper,
2729
3425
  getPrebidEventKey,
2730
3426
  getbidkernel,
3427
+ hookImaPrototype,
3428
+ initImaInterception,
3429
+ isTrustedEndpoint,
2731
3430
  registerPrebidAnalytics
2732
3431
  };