@bidkernel/analytics 0.7.0 → 0.9.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,7 @@ __export(src_exports, {
24
24
  PrebidEventDeduper: () => PrebidEventDeduper,
25
25
  getPrebidEventKey: () => getPrebidEventKey,
26
26
  getbidkernel: () => getbidkernel,
27
+ isTrustedEndpoint: () => isTrustedEndpoint,
27
28
  registerPrebidAnalytics: () => registerPrebidAnalytics
28
29
  });
29
30
  module.exports = __toCommonJS(src_exports);
@@ -34,26 +35,33 @@ var TraceEventType = {
34
35
  TRACE_EVENT_TYPE_UNSPECIFIED: 0,
35
36
  AUCTION_START: 1,
36
37
  AUCTION_END: 2,
37
- BID_REQUEST: 3,
38
- BID_RESPONSE: 4,
39
- BID_WIN: 5,
40
- BID_TIMEOUT: 6,
38
+ BID_RESPONSE: 3,
39
+ BID_WIN: 4,
41
40
  /** IMPRESSION - ad rendered into DOM */
42
- IMPRESSION: 7,
41
+ IMPRESSION: 5,
43
42
  /** VIEWABLE - IAB viewability threshold met (boolean, once per render cycle) */
44
- VIEWABLE: 8,
43
+ VIEWABLE: 6,
45
44
  /** 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,
45
+ TIME_IN_VIEW: 7,
46
+ REFRESH: 8,
47
+ ERROR: 9,
48
+ SLOT_DEFINED: 10,
49
+ SLOT_DESTROYED: 11,
51
50
  /** CLICK - ad click-through */
52
- CLICK: 13,
53
- /** NO_BID - bidder responded with no bid */
54
- NO_BID: 15,
51
+ CLICK: 12,
55
52
  /** AD_RENDER_FAILED - Prebid failed to render the winning ad */
56
- AD_RENDER_FAILED: 16,
53
+ AD_RENDER_FAILED: 13,
54
+ UNRECOGNIZED: -1
55
+ };
56
+ var BidderOutcome = {
57
+ BIDDER_OUTCOME_UNSPECIFIED: 0,
58
+ /** BIDDER_OUTCOME_BID - returned a bid (CPM > 0) */
59
+ BIDDER_OUTCOME_BID: 1,
60
+ BIDDER_OUTCOME_NO_BID: 2,
61
+ BIDDER_OUTCOME_TIMEOUT: 3,
62
+ BIDDER_OUTCOME_ERROR: 4,
63
+ /** BIDDER_OUTCOME_NO_RESPONSE - requested, nothing came back by auction end */
64
+ BIDDER_OUTCOME_NO_RESPONSE: 5,
57
65
  UNRECOGNIZED: -1
58
66
  };
59
67
  function createBaseTraceEventBatch() {
@@ -284,7 +292,8 @@ function createBaseTraceEvent() {
284
292
  namespace: "",
285
293
  eventId: "",
286
294
  metadata: {},
287
- viewableDurationMs: void 0
295
+ viewableDurationMs: void 0,
296
+ bidderOutcomes: []
288
297
  };
289
298
  }
290
299
  var TraceEvent = {
@@ -322,6 +331,11 @@ var TraceEvent = {
322
331
  if (message.viewableDurationMs !== void 0) {
323
332
  writer.uint32(89).double(message.viewableDurationMs);
324
333
  }
334
+ if (message.bidderOutcomes !== void 0 && message.bidderOutcomes.length !== 0) {
335
+ for (const v of message.bidderOutcomes) {
336
+ BidderOutcomeEntry.encode(v, writer.uint32(98).fork()).join();
337
+ }
338
+ }
325
339
  return writer;
326
340
  },
327
341
  decode(input, length) {
@@ -411,6 +425,16 @@ var TraceEvent = {
411
425
  message.viewableDurationMs = reader.double();
412
426
  continue;
413
427
  }
428
+ case 12: {
429
+ if (tag !== 98) {
430
+ break;
431
+ }
432
+ const el = BidderOutcomeEntry.decode(reader, reader.uint32());
433
+ if (el !== void 0) {
434
+ message.bidderOutcomes.push(el);
435
+ }
436
+ continue;
437
+ }
414
438
  }
415
439
  if ((tag & 7) === 4 || tag === 0) {
416
440
  break;
@@ -443,6 +467,7 @@ var TraceEvent = {
443
467
  {}
444
468
  );
445
469
  message.viewableDurationMs = object.viewableDurationMs ?? void 0;
470
+ message.bidderOutcomes = object.bidderOutcomes?.map((e) => BidderOutcomeEntry.fromPartial(e)) || [];
446
471
  return message;
447
472
  }
448
473
  };
@@ -498,6 +523,91 @@ var TraceEvent_MetadataEntry = {
498
523
  return message;
499
524
  }
500
525
  };
526
+ function createBaseBidderOutcomeEntry() {
527
+ return { bidder: "", adUnitCode: "", mediaType: "", outcome: 0, latencyMs: 0 };
528
+ }
529
+ var BidderOutcomeEntry = {
530
+ encode(message, writer = new import_wire.BinaryWriter()) {
531
+ if (message.bidder !== void 0 && message.bidder !== "") {
532
+ writer.uint32(10).string(message.bidder);
533
+ }
534
+ if (message.adUnitCode !== void 0 && message.adUnitCode !== "") {
535
+ writer.uint32(18).string(message.adUnitCode);
536
+ }
537
+ if (message.mediaType !== void 0 && message.mediaType !== "") {
538
+ writer.uint32(26).string(message.mediaType);
539
+ }
540
+ if (message.outcome !== void 0 && message.outcome !== 0) {
541
+ writer.uint32(32).int32(message.outcome);
542
+ }
543
+ if (message.latencyMs !== void 0 && message.latencyMs !== 0) {
544
+ writer.uint32(41).double(message.latencyMs);
545
+ }
546
+ return writer;
547
+ },
548
+ decode(input, length) {
549
+ const reader = input instanceof import_wire.BinaryReader ? input : new import_wire.BinaryReader(input);
550
+ const end = length === void 0 ? reader.len : reader.pos + length;
551
+ const message = createBaseBidderOutcomeEntry();
552
+ while (reader.pos < end) {
553
+ const tag = reader.uint32();
554
+ switch (tag >>> 3) {
555
+ case 1: {
556
+ if (tag !== 10) {
557
+ break;
558
+ }
559
+ message.bidder = reader.string();
560
+ continue;
561
+ }
562
+ case 2: {
563
+ if (tag !== 18) {
564
+ break;
565
+ }
566
+ message.adUnitCode = reader.string();
567
+ continue;
568
+ }
569
+ case 3: {
570
+ if (tag !== 26) {
571
+ break;
572
+ }
573
+ message.mediaType = reader.string();
574
+ continue;
575
+ }
576
+ case 4: {
577
+ if (tag !== 32) {
578
+ break;
579
+ }
580
+ message.outcome = reader.int32();
581
+ continue;
582
+ }
583
+ case 5: {
584
+ if (tag !== 41) {
585
+ break;
586
+ }
587
+ message.latencyMs = reader.double();
588
+ continue;
589
+ }
590
+ }
591
+ if ((tag & 7) === 4 || tag === 0) {
592
+ break;
593
+ }
594
+ reader.skip(tag & 7);
595
+ }
596
+ return message;
597
+ },
598
+ create(base) {
599
+ return BidderOutcomeEntry.fromPartial(base ?? {});
600
+ },
601
+ fromPartial(object) {
602
+ const message = createBaseBidderOutcomeEntry();
603
+ message.bidder = object.bidder ?? "";
604
+ message.adUnitCode = object.adUnitCode ?? "";
605
+ message.mediaType = object.mediaType ?? "";
606
+ message.outcome = object.outcome ?? 0;
607
+ message.latencyMs = object.latencyMs ?? 0;
608
+ return message;
609
+ }
610
+ };
501
611
  function createBaseBidTrace() {
502
612
  return {
503
613
  bidder: "",
@@ -658,6 +768,8 @@ var MAX_QUEUE_SIZE = 200;
658
768
  var MAX_SEND_BACKOFF_MS = 5 * 60 * 1e3;
659
769
  var MAX_CONSECUTIVE_SEND_FAILURES = 10;
660
770
  var MAX_PAYLOAD_BYTES = 32 * 1024;
771
+ var MAX_METADATA_VALUE_CHARS = 1024;
772
+ var MAX_METADATA_ENTRIES = 16;
661
773
  var SAFE_CONTENT_TYPE = "text/plain";
662
774
  var PENDING_BATCH_KEY_PREFIX = "_bidkernel_pending_";
663
775
  var MAX_PENDING_BATCHES = 20;
@@ -666,11 +778,8 @@ var PENDING_BATCH_CLEANUP_DELAY_MS = 1e4;
666
778
  var EVENT_NAME_TO_TYPE = {
667
779
  auctionStart: TraceEventType.AUCTION_START,
668
780
  auctionEnd: TraceEventType.AUCTION_END,
669
- bidRequest: TraceEventType.BID_REQUEST,
670
781
  bidResponse: TraceEventType.BID_RESPONSE,
671
- bidTimeout: TraceEventType.BID_TIMEOUT,
672
782
  bidWin: TraceEventType.BID_WIN,
673
- noBid: TraceEventType.NO_BID,
674
783
  adRenderFailed: TraceEventType.AD_RENDER_FAILED,
675
784
  click: TraceEventType.CLICK,
676
785
  impression: TraceEventType.IMPRESSION,
@@ -683,6 +792,10 @@ var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
683
792
  TraceEventType.BID_WIN,
684
793
  TraceEventType.CLICK
685
794
  ]);
795
+ var CACHED_BID_TTL_MS = 30 * 60 * 1e3;
796
+ var MAX_CACHED_BID_KEYS = 200;
797
+ var MAX_IMPRESSION_KEYS = 1e3;
798
+ var MAX_TRACKED_AUCTIONS = 50;
686
799
  var SESSION_KEY = "_bidkernel_session";
687
800
  var SESSION_TS_KEY = "_bidkernel_session_ts";
688
801
  var THIRTY_MINUTES_MS = 30 * 60 * 1e3;
@@ -714,6 +827,46 @@ function base64ToBytes(b64) {
714
827
  }
715
828
  return bytes;
716
829
  }
830
+ function isTrustedEndpoint(raw) {
831
+ if (!raw) return false;
832
+ let url;
833
+ try {
834
+ url = new URL(raw);
835
+ } catch {
836
+ return false;
837
+ }
838
+ if (url.username || url.password) return false;
839
+ if (url.protocol === "https:") return true;
840
+ if (url.protocol === "http:") {
841
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "[::1]";
842
+ }
843
+ return false;
844
+ }
845
+ function boundMetadata(metadata) {
846
+ const bounded = {};
847
+ let count = 0;
848
+ for (const key of Object.keys(metadata)) {
849
+ if (count >= MAX_METADATA_ENTRIES) break;
850
+ const value = metadata[key];
851
+ if (value === void 0 || value === null) continue;
852
+ const str = String(value);
853
+ bounded[key] = str.length > MAX_METADATA_VALUE_CHARS ? `${str.slice(0, MAX_METADATA_VALUE_CHARS)}...[truncated]` : str;
854
+ count++;
855
+ }
856
+ return bounded;
857
+ }
858
+ function isLegacyPendingKey(key) {
859
+ const parts = key.slice(PENDING_BATCH_KEY_PREFIX.length).split("_");
860
+ return parts.length === 2 && /^\d+$/.test(parts[0]);
861
+ }
862
+ function sameEndpointOrigin(a, b) {
863
+ if (!a || !b) return false;
864
+ try {
865
+ return new URL(a).origin === new URL(b).origin;
866
+ } catch {
867
+ return false;
868
+ }
869
+ }
717
870
  function extendSession() {
718
871
  const now = Date.now();
719
872
  inMemorySessionTs = now;
@@ -830,22 +983,6 @@ var PrebidEventDeduper = class {
830
983
  return false;
831
984
  }
832
985
  };
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
986
  function parseBidDimensions(bid) {
850
987
  if (Number.isFinite(bid?.width) && Number.isFinite(bid?.height)) {
851
988
  return { width: bid.width, height: bid.height };
@@ -897,9 +1034,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
897
1034
  cachedWinningBids = /* @__PURE__ */ new Map();
898
1035
  // Active video player attachment cleanup routines
899
1036
  videoDetachCleanups = /* @__PURE__ */ new Set();
1037
+ // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1038
+ auctionBidderOutcomes = /* @__PURE__ */ new Map();
900
1039
  constructor(config) {
1040
+ const requestedEndpoint = config.endpoint || "";
1041
+ const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
901
1042
  this.config = {
902
- endpoint: config.endpoint || "",
1043
+ endpoint,
903
1044
  propertyId: config.propertyId || "",
904
1045
  pageviewId: config.pageviewId || generateUUID(),
905
1046
  sessionId: config.sessionId || getOrCreateSessionId(),
@@ -915,6 +1056,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
915
1056
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
916
1057
  attachPbjsListeners: config.attachPbjsListeners ?? true
917
1058
  };
1059
+ if (requestedEndpoint && !endpoint) {
1060
+ this.log(
1061
+ "ERROR",
1062
+ `Rejected untrusted analytics endpoint ${requestedEndpoint}: must be an absolute https:// URL without embedded credentials. Events will not be transmitted.`
1063
+ );
1064
+ }
918
1065
  this.boundFlushBeacon = () => {
919
1066
  this.flushAllSlotsTimeInView();
920
1067
  this.flushBeacon();
@@ -1052,6 +1199,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1052
1199
  this.cachedWinningBids.clear();
1053
1200
  this.slotEmittedImpressionKeys.clear();
1054
1201
  this.slotLastAuctionIds.clear();
1202
+ this.auctionBidderOutcomes.clear();
1055
1203
  if (this.flushTimer) {
1056
1204
  clearInterval(this.flushTimer);
1057
1205
  this.flushTimer = null;
@@ -1455,7 +1603,73 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1455
1603
  this.slotEmittedImpressionKeys.delete(key);
1456
1604
  }
1457
1605
  }
1458
- this.cachedWinningBids.delete(slotId);
1606
+ const cached = this.cachedWinningBids.get(slotId);
1607
+ if (cached) this.deleteCachedBidEntry(cached);
1608
+ }
1609
+ // --- Bounded caches -------------------------------------------------------
1610
+ /**
1611
+ * Reads a cached winning bid, treating an entry past CACHED_BID_TTL_MS as
1612
+ * absent and dropping it. Every read goes through here so a stale bid can
1613
+ * never be married to a much later render.
1614
+ */
1615
+ getCachedBid(key) {
1616
+ const entry = this.cachedWinningBids.get(key);
1617
+ if (!entry) return void 0;
1618
+ if (Date.now() - entry.timestamp > CACHED_BID_TTL_MS) {
1619
+ this.deleteCachedBidEntry(entry);
1620
+ return void 0;
1621
+ }
1622
+ return entry;
1623
+ }
1624
+ /** Removes every alias pointing at one entry. Aliases are stored by identity. */
1625
+ deleteCachedBidEntry(entry) {
1626
+ for (const [key, value] of Array.from(this.cachedWinningBids)) {
1627
+ if (value === entry) this.cachedWinningBids.delete(key);
1628
+ }
1629
+ }
1630
+ /**
1631
+ * Drops expired entries, then evicts oldest-first until the key cap holds.
1632
+ * Map iterates in insertion order and one win's aliases are written
1633
+ * consecutively, so eviction takes a whole bid's aliases together.
1634
+ */
1635
+ pruneCachedWinningBids() {
1636
+ const now = Date.now();
1637
+ for (const [key, entry] of Array.from(this.cachedWinningBids)) {
1638
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) this.cachedWinningBids.delete(key);
1639
+ }
1640
+ while (this.cachedWinningBids.size > MAX_CACHED_BID_KEYS) {
1641
+ const oldest = this.cachedWinningBids.keys().next();
1642
+ if (oldest.done) break;
1643
+ this.cachedWinningBids.delete(oldest.value);
1644
+ }
1645
+ }
1646
+ /** Records a dedup key, evicting oldest-first at the cap. */
1647
+ addImpressionKey(key) {
1648
+ if (this.slotEmittedImpressionKeys.has(key)) return;
1649
+ while (this.slotEmittedImpressionKeys.size >= MAX_IMPRESSION_KEYS) {
1650
+ const oldest = this.slotEmittedImpressionKeys.values().next();
1651
+ if (oldest.done) break;
1652
+ this.slotEmittedImpressionKeys.delete(oldest.value);
1653
+ }
1654
+ this.slotEmittedImpressionKeys.add(key);
1655
+ }
1656
+ /**
1657
+ * The bidder-outcome map for an auction, creating it if absent. Evicts the
1658
+ * oldest auction at the cap: without this, any auction whose auctionEnd never
1659
+ * fires (abandoned, navigated away from mid-flight) leaks its map forever.
1660
+ */
1661
+ getOrCreateAuctionOutcomes(auctionId) {
1662
+ let auctionMap = this.auctionBidderOutcomes.get(auctionId);
1663
+ if (!auctionMap) {
1664
+ auctionMap = /* @__PURE__ */ new Map();
1665
+ this.auctionBidderOutcomes.set(auctionId, auctionMap);
1666
+ }
1667
+ while (this.auctionBidderOutcomes.size > MAX_TRACKED_AUCTIONS) {
1668
+ const oldest = this.auctionBidderOutcomes.keys().next();
1669
+ if (oldest.done || oldest.value === auctionId) break;
1670
+ this.auctionBidderOutcomes.delete(oldest.value);
1671
+ }
1672
+ return auctionMap;
1459
1673
  }
1460
1674
  hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1461
1675
  const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
@@ -1475,14 +1689,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1475
1689
  }
1476
1690
  markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1477
1691
  const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
1478
- this.slotEmittedImpressionKeys.add(baseSlotKey);
1692
+ this.addImpressionKey(baseSlotKey);
1479
1693
  if (creativeId) {
1480
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1694
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1481
1695
  }
1482
1696
  if (auctionId) {
1483
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1697
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1484
1698
  if (creativeId) {
1485
- this.slotEmittedImpressionKeys.add(
1699
+ this.addImpressionKey(
1486
1700
  `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
1487
1701
  );
1488
1702
  }
@@ -1492,7 +1706,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1492
1706
  if (!this.isEnabled) return false;
1493
1707
  const resolvedSlotId = slotId || options?.adUnitCode || "";
1494
1708
  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);
1709
+ 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
1710
  const rawBid = options?.bid || cached?.rawBid || {};
1497
1711
  const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
1498
1712
  const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
@@ -1593,7 +1807,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1593
1807
  const auctionId = options?.auctionId || "";
1594
1808
  const transactionId = options?.transactionId || "";
1595
1809
  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);
1810
+ 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);
1597
1811
  };
1598
1812
  const onAdStartedOrImpression = (event) => {
1599
1813
  this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
@@ -1757,7 +1971,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1757
1971
  const auctionId = options?.auctionId || "";
1758
1972
  const transactionId = options?.transactionId || "";
1759
1973
  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);
1974
+ 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
1975
  };
1762
1976
  let videoEl = null;
1763
1977
  if (el) {
@@ -1993,6 +2207,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1993
2207
  refreshIndex: record.refreshIndex
1994
2208
  };
1995
2209
  }
2210
+ /** The validated ingest endpoint in use, or "" when none passed validation. */
2211
+ getEndpoint() {
2212
+ return this.config.endpoint;
2213
+ }
1996
2214
  getRefreshIndex(slotId) {
1997
2215
  return this.slotRefreshIndices.get(slotId) ?? 0;
1998
2216
  }
@@ -2033,17 +2251,29 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2033
2251
  handleAuctionInit(data) {
2034
2252
  if (this.isDuplicate("auctionInit", data)) return;
2035
2253
  this.log("DEBUG", "auctionInit", data);
2254
+ const auctionId = data.auctionId || "";
2255
+ if (auctionId) {
2256
+ this.auctionBidderOutcomes.delete(auctionId);
2257
+ this.getOrCreateAuctionOutcomes(auctionId);
2258
+ }
2036
2259
  this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
2037
- auctionId: data.auctionId || "",
2260
+ auctionId,
2038
2261
  transactionId: data.transactionId || ""
2039
2262
  });
2040
2263
  }
2041
2264
  handleAuctionEnd(data) {
2042
2265
  if (this.isDuplicate("auctionEnd", data)) return;
2043
2266
  this.log("DEBUG", "auctionEnd", data);
2267
+ const auctionId = data.auctionId || "";
2268
+ const outcomesMap = auctionId ? this.auctionBidderOutcomes.get(auctionId) : void 0;
2269
+ const bidderOutcomes = outcomesMap ? Array.from(outcomesMap.values()) : [];
2270
+ if (auctionId) {
2271
+ this.auctionBidderOutcomes.delete(auctionId);
2272
+ }
2044
2273
  this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
2045
- auctionId: data.auctionId || "",
2046
- transactionId: data.transactionId || ""
2274
+ auctionId,
2275
+ transactionId: data.transactionId || "",
2276
+ bidderOutcomes
2047
2277
  });
2048
2278
  }
2049
2279
  handleBidRequested(data) {
@@ -2051,88 +2281,99 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2051
2281
  this.log("DEBUG", "bidRequested", data);
2052
2282
  const auctionId = data.auctionId || "";
2053
2283
  const bidder = data.bidderCode || data.bidder || "";
2284
+ if (!auctionId || !bidder) return;
2285
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2054
2286
  if (Array.isArray(data.bids)) {
2055
2287
  data.bids.forEach((bid) => {
2288
+ const adUnitCode = bid.adUnitCode || "";
2056
2289
  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
- }
2290
+ const mediaType = mediaTypes.length > 0 ? mediaTypes[0] : bid.mediaType || "banner";
2291
+ const key = `${bidder}:${adUnitCode}`;
2292
+ if (!auctionMap.has(key)) {
2293
+ auctionMap.set(key, {
2294
+ bidder,
2295
+ adUnitCode,
2296
+ mediaType,
2297
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE,
2298
+ latencyMs: 0
2073
2299
  });
2074
- return;
2075
2300
  }
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
2301
  });
2100
2302
  }
2101
2303
  }
2102
2304
  handleBidResponse(data) {
2103
2305
  if (this.isDuplicate("bidResponse", data)) return;
2104
2306
  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 || ""
2307
+ const auctionId = data.auctionId || "";
2308
+ const bidder = data.bidderCode || data.bidder || "";
2309
+ const adUnitCode = data.adUnitCode || "";
2310
+ const cpm = Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0;
2311
+ const latencyMs = Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0;
2312
+ const mediaType = data.mediaType || "banner";
2313
+ if (auctionId && bidder) {
2314
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2315
+ const key = `${bidder}:${adUnitCode}`;
2316
+ const entry = auctionMap.get(key);
2317
+ const outcome = cpm > 0 ? BidderOutcome.BIDDER_OUTCOME_BID : BidderOutcome.BIDDER_OUTCOME_NO_BID;
2318
+ if (entry) {
2319
+ entry.outcome = outcome;
2320
+ entry.latencyMs = latencyMs;
2321
+ if (mediaType) entry.mediaType = mediaType;
2322
+ } else {
2323
+ auctionMap.set(key, {
2324
+ bidder,
2325
+ adUnitCode,
2326
+ mediaType,
2327
+ outcome,
2328
+ latencyMs
2329
+ });
2119
2330
  }
2120
- });
2331
+ }
2332
+ if (cpm > 0) {
2333
+ this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
2334
+ auctionId,
2335
+ transactionId: data.transactionId || "",
2336
+ adUnitCode,
2337
+ bid: {
2338
+ bidder,
2339
+ cpm,
2340
+ currency: data.originalCurrency ?? data.currency ?? "USD",
2341
+ ...parseBidDimensions(data),
2342
+ dealId: data.dealId || "",
2343
+ mediaType,
2344
+ latencyMs,
2345
+ advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2346
+ creativeId: data.creativeId || ""
2347
+ }
2348
+ });
2349
+ }
2121
2350
  }
2122
2351
  handleBidTimeout(data) {
2123
2352
  if (this.isDuplicate("bidTimeout", data)) return;
2124
2353
  this.log("DEBUG", "bidTimeout", data);
2125
2354
  const items = Array.isArray(data) ? data : data ? [data] : [];
2126
2355
  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
2356
+ const auctionId = t.auctionId || "";
2357
+ const bidder = t.bidderCode || t.bidder || "";
2358
+ const adUnitCode = t.adUnitCode || "";
2359
+ const latencyMs = Number.isFinite(t.timeout) ? t.timeout : 0;
2360
+ if (auctionId && bidder) {
2361
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2362
+ const key = `${bidder}:${adUnitCode}`;
2363
+ const entry = auctionMap.get(key);
2364
+ if (entry) {
2365
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_TIMEOUT;
2366
+ if (latencyMs > 0) entry.latencyMs = latencyMs;
2367
+ } else {
2368
+ auctionMap.set(key, {
2369
+ bidder,
2370
+ adUnitCode,
2371
+ mediaType: "banner",
2372
+ outcome: BidderOutcome.BIDDER_OUTCOME_TIMEOUT,
2373
+ latencyMs
2374
+ });
2134
2375
  }
2135
- });
2376
+ }
2136
2377
  }
2137
2378
  }
2138
2379
  handleBidWon(data) {
@@ -2171,6 +2412,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2171
2412
  this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
2172
2413
  }
2173
2414
  }
2415
+ this.pruneCachedWinningBids();
2174
2416
  }
2175
2417
  this.enqueue(TraceEventType.BID_WIN, "bidWon", {
2176
2418
  auctionId,
@@ -2182,14 +2424,27 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2182
2424
  handleNoBid(data) {
2183
2425
  if (this.isDuplicate("noBid", data)) return;
2184
2426
  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 || ""
2427
+ const auctionId = data.auctionId || "";
2428
+ const bidder = data.bidderCode || data.bidder || "";
2429
+ const adUnitCode = data.adUnitCode || "";
2430
+ if (auctionId && bidder) {
2431
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2432
+ const key = `${bidder}:${adUnitCode}`;
2433
+ const entry = auctionMap.get(key);
2434
+ if (entry) {
2435
+ if (entry.outcome === BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE) {
2436
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_NO_BID;
2437
+ }
2438
+ } else {
2439
+ auctionMap.set(key, {
2440
+ bidder,
2441
+ adUnitCode,
2442
+ mediaType: "banner",
2443
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_BID,
2444
+ latencyMs: 0
2445
+ });
2191
2446
  }
2192
- });
2447
+ }
2193
2448
  }
2194
2449
  handleAdRenderFailed(data) {
2195
2450
  if (this.isDuplicate("adRenderFailed", data)) return;
@@ -2221,7 +2476,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2221
2476
  const mediaType = bid.mediaType || "banner";
2222
2477
  const slotKey = data.adUnitCode || bid.adUnitCode || "";
2223
2478
  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);
2479
+ 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);
2225
2480
  const bidTrace = {
2226
2481
  bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
2227
2482
  cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
@@ -2310,7 +2565,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2310
2565
  }
2311
2566
  if (!el && targetId) {
2312
2567
  try {
2313
- el = document.querySelector(`[data-ad-slot="${targetId}"]`) || document.querySelector(`#${targetId}`);
2568
+ for (const candidate of Array.from(document.querySelectorAll("[data-ad-slot]"))) {
2569
+ if (candidate.getAttribute("data-ad-slot") === targetId) {
2570
+ el = candidate;
2571
+ break;
2572
+ }
2573
+ }
2314
2574
  } catch {
2315
2575
  }
2316
2576
  }
@@ -2364,6 +2624,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2364
2624
  if (data.viewableDurationMs !== void 0) {
2365
2625
  protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
2366
2626
  }
2627
+ if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2628
+ protoEvent.bidderOutcomes = data.bidderOutcomes;
2629
+ }
2367
2630
  const metadata = { ...data.metadata };
2368
2631
  if (data.error !== void 0 && data.error !== null) {
2369
2632
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
@@ -2374,8 +2637,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2374
2637
  if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
2375
2638
  metadata.refresh_index = String(data.refreshIndex);
2376
2639
  }
2377
- if (Object.keys(metadata).length > 0) {
2378
- protoEvent.metadata = metadata;
2640
+ const boundedMetadata = boundMetadata(metadata);
2641
+ if (Object.keys(boundedMetadata).length > 0) {
2642
+ protoEvent.metadata = boundedMetadata;
2379
2643
  }
2380
2644
  this.queue.push(protoEvent);
2381
2645
  if (this.queue.length > MAX_QUEUE_SIZE) {
@@ -2413,7 +2677,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2413
2677
  // Shared by flush() and flushBeacon().
2414
2678
  drainBatch() {
2415
2679
  if (this.queue.length === 0 || !this.config.endpoint) return null;
2416
- let events = this.queue;
2417
2680
  const domain = typeof window !== "undefined" ? window.location.hostname : "";
2418
2681
  const buildBatch = (evts) => ({
2419
2682
  propertyId: this.config.propertyId,
@@ -2427,25 +2690,37 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2427
2690
  domain,
2428
2691
  events: evts
2429
2692
  });
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(
2693
+ for (; ; ) {
2694
+ if (this.queue.length === 0) return null;
2695
+ let events = this.queue;
2696
+ let encoded = TraceEventBatch.encode(
2436
2697
  buildBatch(events)
2437
2698
  ).finish();
2699
+ while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
2700
+ events = events.slice(0, Math.floor(events.length / 2));
2701
+ encoded = TraceEventBatch.encode(
2702
+ buildBatch(events)
2703
+ ).finish();
2704
+ }
2705
+ if (encoded.byteLength > MAX_PAYLOAD_BYTES) {
2706
+ const [dropped] = this.queue.splice(0, 1);
2707
+ this.log(
2708
+ "WARN",
2709
+ `Dropping oversized event ${dropped?.eventName || dropped?.eventId} (${encoded.byteLength} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte cap on its own)`
2710
+ );
2711
+ continue;
2712
+ }
2713
+ this.queue = this.queue.slice(events.length);
2714
+ return {
2715
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
2716
+ // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
2717
+ // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
2718
+ encoded,
2719
+ events
2720
+ };
2438
2721
  }
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
2722
  }
2448
- sendFetch(payload, useKeepalive = false, onDelivered) {
2723
+ sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
2449
2724
  const fetchOpts = {
2450
2725
  method: "POST",
2451
2726
  // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
@@ -2462,15 +2737,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2462
2737
  this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
2463
2738
  return;
2464
2739
  }
2465
- this.handleSendFailure(payload.events);
2740
+ if (!isReplay) this.handleSendFailure(payload.events);
2466
2741
  } else {
2467
- this.consecutiveSendFailures = 0;
2468
- this.nextSendAllowedAt = 0;
2742
+ if (!isReplay) {
2743
+ this.consecutiveSendFailures = 0;
2744
+ this.nextSendAllowedAt = 0;
2745
+ }
2469
2746
  if (onDelivered) onDelivered();
2470
2747
  }
2471
2748
  }).catch((err) => {
2472
2749
  this.log("ERROR", "Failed to send batch", err);
2473
- this.handleSendFailure(payload.events);
2750
+ if (!isReplay) this.handleSendFailure(payload.events);
2474
2751
  });
2475
2752
  const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2476
2753
  if (proc && typeof proc._tickCallback === "function") {
@@ -2526,16 +2803,23 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2526
2803
  this.schedulePersistedCleanup();
2527
2804
  }
2528
2805
  // --- Exit-batch persistence -----------------------------------------------
2806
+ // Key prefix for this instance's property. encodeURIComponent keeps the
2807
+ // property segment free of the "_" the timestamp suffix is parsed against,
2808
+ // so a property ID containing one cannot forge another property's prefix.
2809
+ pendingKeyPrefix() {
2810
+ return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
2811
+ }
2529
2812
  persistBatch(encoded) {
2530
2813
  try {
2531
2814
  if (typeof localStorage === "undefined") return null;
2815
+ if (!this.config.propertyId) return null;
2532
2816
  let pendingCount = 0;
2533
2817
  for (let i = 0; i < localStorage.length; i++) {
2534
2818
  const k = localStorage.key(i);
2535
2819
  if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
2536
2820
  }
2537
2821
  if (pendingCount >= MAX_PENDING_BATCHES) return null;
2538
- const key = `${PENDING_BATCH_KEY_PREFIX}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2822
+ const key = `${this.pendingKeyPrefix()}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2539
2823
  localStorage.setItem(key, bytesToBase64(encoded));
2540
2824
  this.persistedBatchKeys.push(key);
2541
2825
  return key;
@@ -2566,16 +2850,33 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2566
2850
  }, PENDING_BATCH_CLEANUP_DELAY_MS);
2567
2851
  }
2568
2852
  // 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.
2853
+ // confirm. Only this property's own keys are claimed: batches belonging to
2854
+ // another property sharing this origin are left for that property's next
2855
+ // pageview, since resending them here would file them under this property.
2856
+ // Keys are claimed (removed) before sending so concurrent tabs don't
2857
+ // double-send; the batch bytes carry their original pageview, session, and
2858
+ // event timestamps, so late rows attribute correctly.
2572
2859
  resendPersistedBatches() {
2573
2860
  try {
2574
2861
  if (typeof localStorage === "undefined" || !this.config.endpoint) return;
2862
+ if (!this.config.propertyId) return;
2863
+ const ownPrefix = this.pendingKeyPrefix();
2575
2864
  const keys = [];
2865
+ const legacyKeys = [];
2576
2866
  for (let i = 0; i < localStorage.length; i++) {
2577
2867
  const k = localStorage.key(i);
2578
- if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) keys.push(k);
2868
+ if (!k || !k.startsWith(PENDING_BATCH_KEY_PREFIX)) continue;
2869
+ if (k.startsWith(ownPrefix)) {
2870
+ keys.push(k);
2871
+ } else if (isLegacyPendingKey(k)) {
2872
+ legacyKeys.push(k);
2873
+ }
2874
+ }
2875
+ for (const key of legacyKeys) {
2876
+ try {
2877
+ localStorage.removeItem(key);
2878
+ } catch {
2879
+ }
2579
2880
  }
2580
2881
  for (const key of keys) {
2581
2882
  let value = null;
@@ -2586,7 +2887,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2586
2887
  continue;
2587
2888
  }
2588
2889
  if (!value) continue;
2589
- const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
2890
+ const ts = parseInt(key.slice(ownPrefix.length), 10);
2590
2891
  if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
2591
2892
  let encoded;
2592
2893
  try {
@@ -2596,11 +2897,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2596
2897
  }
2597
2898
  if (encoded.byteLength === 0) continue;
2598
2899
  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
- });
2900
+ this.sendFetch(
2901
+ {
2902
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
2903
+ encoded,
2904
+ events: []
2905
+ },
2906
+ false,
2907
+ void 0,
2908
+ true
2909
+ );
2604
2910
  }
2605
2911
  } catch {
2606
2912
  }
@@ -2625,11 +2931,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2625
2931
  }
2626
2932
  }
2627
2933
  };
2628
- function extractAnalyticsOptions(config) {
2934
+ function extractAnalyticsOptions(config, allowProviderless = true) {
2629
2935
  if (!config) return null;
2630
2936
  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;
2937
+ const matched = config.find((item) => item?.provider === "bidkernel") ?? (allowProviderless ? config.find(
2938
+ (item) => item && typeof item === "object" && item.provider === void 0
2939
+ ) : void 0);
2940
+ return matched ? extractAnalyticsOptions(matched, allowProviderless) : null;
2633
2941
  }
2634
2942
  if (typeof config === "object") {
2635
2943
  if (config.options && typeof config.options === "object") {
@@ -2671,18 +2979,27 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2671
2979
  win[pbjsGlobalName] = win[pbjsGlobalName] || {};
2672
2980
  const pbjs = win[pbjsGlobalName];
2673
2981
  const enableInstance = (options) => {
2674
- if (win._bidkernelPrebidAnalytics) {
2982
+ const previous = win._bidkernelPrebidAnalytics;
2983
+ const pinnedEndpoint = previous?.getEndpoint?.() || "";
2984
+ const merged = {
2985
+ pbjsGlobalName,
2986
+ ...defaults,
2987
+ ...options
2988
+ };
2989
+ if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
2990
+ console.warn(
2991
+ `[bidkernel] Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
2992
+ );
2993
+ merged.endpoint = pinnedEndpoint;
2994
+ }
2995
+ if (previous) {
2675
2996
  try {
2676
- win._bidkernelPrebidAnalytics.disable();
2997
+ previous.disable();
2677
2998
  } catch (e) {
2678
2999
  console.warn("[bidkernel] Failed to disable previous analytics instance:", e);
2679
3000
  }
2680
3001
  }
2681
- const analytics = new BidkernelPrebidAnalytics({
2682
- pbjsGlobalName,
2683
- ...defaults,
2684
- ...options
2685
- });
3002
+ const analytics = new BidkernelPrebidAnalytics(merged);
2686
3003
  analytics.enable();
2687
3004
  win._bidkernelPrebidAnalytics = analytics;
2688
3005
  return analytics;
@@ -2693,7 +3010,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2693
3010
  pbjs.adapterManager.registerAnalyticsAdapter({
2694
3011
  adapter: {
2695
3012
  enableAnalytics: (config) => {
2696
- const options = extractAnalyticsOptions(config);
3013
+ const options = extractAnalyticsOptions(config, !Array.isArray(config));
2697
3014
  if (options) {
2698
3015
  enableInstance(options);
2699
3016
  }
@@ -2759,5 +3076,6 @@ function getbidkernel(alias = "bidkernel") {
2759
3076
  PrebidEventDeduper,
2760
3077
  getPrebidEventKey,
2761
3078
  getbidkernel,
3079
+ isTrustedEndpoint,
2762
3080
  registerPrebidAnalytics
2763
3081
  });