@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.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 };
@@ -867,9 +1003,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
867
1003
  cachedWinningBids = /* @__PURE__ */ new Map();
868
1004
  // Active video player attachment cleanup routines
869
1005
  videoDetachCleanups = /* @__PURE__ */ new Set();
1006
+ // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1007
+ auctionBidderOutcomes = /* @__PURE__ */ new Map();
870
1008
  constructor(config) {
1009
+ const requestedEndpoint = config.endpoint || "";
1010
+ const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
871
1011
  this.config = {
872
- endpoint: config.endpoint || "",
1012
+ endpoint,
873
1013
  propertyId: config.propertyId || "",
874
1014
  pageviewId: config.pageviewId || generateUUID(),
875
1015
  sessionId: config.sessionId || getOrCreateSessionId(),
@@ -885,6 +1025,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
885
1025
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
886
1026
  attachPbjsListeners: config.attachPbjsListeners ?? true
887
1027
  };
1028
+ if (requestedEndpoint && !endpoint) {
1029
+ this.log(
1030
+ "ERROR",
1031
+ `Rejected untrusted analytics endpoint ${requestedEndpoint}: must be an absolute https:// URL without embedded credentials. Events will not be transmitted.`
1032
+ );
1033
+ }
888
1034
  this.boundFlushBeacon = () => {
889
1035
  this.flushAllSlotsTimeInView();
890
1036
  this.flushBeacon();
@@ -1022,6 +1168,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1022
1168
  this.cachedWinningBids.clear();
1023
1169
  this.slotEmittedImpressionKeys.clear();
1024
1170
  this.slotLastAuctionIds.clear();
1171
+ this.auctionBidderOutcomes.clear();
1025
1172
  if (this.flushTimer) {
1026
1173
  clearInterval(this.flushTimer);
1027
1174
  this.flushTimer = null;
@@ -1425,7 +1572,73 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1425
1572
  this.slotEmittedImpressionKeys.delete(key);
1426
1573
  }
1427
1574
  }
1428
- this.cachedWinningBids.delete(slotId);
1575
+ const cached = this.cachedWinningBids.get(slotId);
1576
+ if (cached) this.deleteCachedBidEntry(cached);
1577
+ }
1578
+ // --- Bounded caches -------------------------------------------------------
1579
+ /**
1580
+ * Reads a cached winning bid, treating an entry past CACHED_BID_TTL_MS as
1581
+ * absent and dropping it. Every read goes through here so a stale bid can
1582
+ * never be married to a much later render.
1583
+ */
1584
+ getCachedBid(key) {
1585
+ const entry = this.cachedWinningBids.get(key);
1586
+ if (!entry) return void 0;
1587
+ if (Date.now() - entry.timestamp > CACHED_BID_TTL_MS) {
1588
+ this.deleteCachedBidEntry(entry);
1589
+ return void 0;
1590
+ }
1591
+ return entry;
1592
+ }
1593
+ /** Removes every alias pointing at one entry. Aliases are stored by identity. */
1594
+ deleteCachedBidEntry(entry) {
1595
+ for (const [key, value] of Array.from(this.cachedWinningBids)) {
1596
+ if (value === entry) this.cachedWinningBids.delete(key);
1597
+ }
1598
+ }
1599
+ /**
1600
+ * Drops expired entries, then evicts oldest-first until the key cap holds.
1601
+ * Map iterates in insertion order and one win's aliases are written
1602
+ * consecutively, so eviction takes a whole bid's aliases together.
1603
+ */
1604
+ pruneCachedWinningBids() {
1605
+ const now = Date.now();
1606
+ for (const [key, entry] of Array.from(this.cachedWinningBids)) {
1607
+ if (now - entry.timestamp > CACHED_BID_TTL_MS) this.cachedWinningBids.delete(key);
1608
+ }
1609
+ while (this.cachedWinningBids.size > MAX_CACHED_BID_KEYS) {
1610
+ const oldest = this.cachedWinningBids.keys().next();
1611
+ if (oldest.done) break;
1612
+ this.cachedWinningBids.delete(oldest.value);
1613
+ }
1614
+ }
1615
+ /** Records a dedup key, evicting oldest-first at the cap. */
1616
+ addImpressionKey(key) {
1617
+ if (this.slotEmittedImpressionKeys.has(key)) return;
1618
+ while (this.slotEmittedImpressionKeys.size >= MAX_IMPRESSION_KEYS) {
1619
+ const oldest = this.slotEmittedImpressionKeys.values().next();
1620
+ if (oldest.done) break;
1621
+ this.slotEmittedImpressionKeys.delete(oldest.value);
1622
+ }
1623
+ this.slotEmittedImpressionKeys.add(key);
1624
+ }
1625
+ /**
1626
+ * The bidder-outcome map for an auction, creating it if absent. Evicts the
1627
+ * oldest auction at the cap: without this, any auction whose auctionEnd never
1628
+ * fires (abandoned, navigated away from mid-flight) leaks its map forever.
1629
+ */
1630
+ getOrCreateAuctionOutcomes(auctionId) {
1631
+ let auctionMap = this.auctionBidderOutcomes.get(auctionId);
1632
+ if (!auctionMap) {
1633
+ auctionMap = /* @__PURE__ */ new Map();
1634
+ this.auctionBidderOutcomes.set(auctionId, auctionMap);
1635
+ }
1636
+ while (this.auctionBidderOutcomes.size > MAX_TRACKED_AUCTIONS) {
1637
+ const oldest = this.auctionBidderOutcomes.keys().next();
1638
+ if (oldest.done || oldest.value === auctionId) break;
1639
+ this.auctionBidderOutcomes.delete(oldest.value);
1640
+ }
1641
+ return auctionMap;
1429
1642
  }
1430
1643
  hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1431
1644
  const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
@@ -1445,14 +1658,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1445
1658
  }
1446
1659
  markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1447
1660
  const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
1448
- this.slotEmittedImpressionKeys.add(baseSlotKey);
1661
+ this.addImpressionKey(baseSlotKey);
1449
1662
  if (creativeId) {
1450
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1663
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1451
1664
  }
1452
1665
  if (auctionId) {
1453
- this.slotEmittedImpressionKeys.add(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1666
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1454
1667
  if (creativeId) {
1455
- this.slotEmittedImpressionKeys.add(
1668
+ this.addImpressionKey(
1456
1669
  `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
1457
1670
  );
1458
1671
  }
@@ -1462,7 +1675,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1462
1675
  if (!this.isEnabled) return false;
1463
1676
  const resolvedSlotId = slotId || options?.adUnitCode || "";
1464
1677
  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);
1678
+ 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
1679
  const rawBid = options?.bid || cached?.rawBid || {};
1467
1680
  const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
1468
1681
  const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
@@ -1563,7 +1776,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1563
1776
  const auctionId = options?.auctionId || "";
1564
1777
  const transactionId = options?.transactionId || "";
1565
1778
  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);
1779
+ 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);
1567
1780
  };
1568
1781
  const onAdStartedOrImpression = (event) => {
1569
1782
  this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
@@ -1727,7 +1940,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1727
1940
  const auctionId = options?.auctionId || "";
1728
1941
  const transactionId = options?.transactionId || "";
1729
1942
  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);
1943
+ 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
1944
  };
1732
1945
  let videoEl = null;
1733
1946
  if (el) {
@@ -1963,6 +2176,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1963
2176
  refreshIndex: record.refreshIndex
1964
2177
  };
1965
2178
  }
2179
+ /** The validated ingest endpoint in use, or "" when none passed validation. */
2180
+ getEndpoint() {
2181
+ return this.config.endpoint;
2182
+ }
1966
2183
  getRefreshIndex(slotId) {
1967
2184
  return this.slotRefreshIndices.get(slotId) ?? 0;
1968
2185
  }
@@ -2003,17 +2220,29 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2003
2220
  handleAuctionInit(data) {
2004
2221
  if (this.isDuplicate("auctionInit", data)) return;
2005
2222
  this.log("DEBUG", "auctionInit", data);
2223
+ const auctionId = data.auctionId || "";
2224
+ if (auctionId) {
2225
+ this.auctionBidderOutcomes.delete(auctionId);
2226
+ this.getOrCreateAuctionOutcomes(auctionId);
2227
+ }
2006
2228
  this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
2007
- auctionId: data.auctionId || "",
2229
+ auctionId,
2008
2230
  transactionId: data.transactionId || ""
2009
2231
  });
2010
2232
  }
2011
2233
  handleAuctionEnd(data) {
2012
2234
  if (this.isDuplicate("auctionEnd", data)) return;
2013
2235
  this.log("DEBUG", "auctionEnd", data);
2236
+ const auctionId = data.auctionId || "";
2237
+ const outcomesMap = auctionId ? this.auctionBidderOutcomes.get(auctionId) : void 0;
2238
+ const bidderOutcomes = outcomesMap ? Array.from(outcomesMap.values()) : [];
2239
+ if (auctionId) {
2240
+ this.auctionBidderOutcomes.delete(auctionId);
2241
+ }
2014
2242
  this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
2015
- auctionId: data.auctionId || "",
2016
- transactionId: data.transactionId || ""
2243
+ auctionId,
2244
+ transactionId: data.transactionId || "",
2245
+ bidderOutcomes
2017
2246
  });
2018
2247
  }
2019
2248
  handleBidRequested(data) {
@@ -2021,88 +2250,99 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2021
2250
  this.log("DEBUG", "bidRequested", data);
2022
2251
  const auctionId = data.auctionId || "";
2023
2252
  const bidder = data.bidderCode || data.bidder || "";
2253
+ if (!auctionId || !bidder) return;
2254
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2024
2255
  if (Array.isArray(data.bids)) {
2025
2256
  data.bids.forEach((bid) => {
2257
+ const adUnitCode = bid.adUnitCode || "";
2026
2258
  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
- }
2259
+ const mediaType = mediaTypes.length > 0 ? mediaTypes[0] : bid.mediaType || "banner";
2260
+ const key = `${bidder}:${adUnitCode}`;
2261
+ if (!auctionMap.has(key)) {
2262
+ auctionMap.set(key, {
2263
+ bidder,
2264
+ adUnitCode,
2265
+ mediaType,
2266
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE,
2267
+ latencyMs: 0
2043
2268
  });
2044
- return;
2045
2269
  }
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
2270
  });
2070
2271
  }
2071
2272
  }
2072
2273
  handleBidResponse(data) {
2073
2274
  if (this.isDuplicate("bidResponse", data)) return;
2074
2275
  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 || ""
2276
+ const auctionId = data.auctionId || "";
2277
+ const bidder = data.bidderCode || data.bidder || "";
2278
+ const adUnitCode = data.adUnitCode || "";
2279
+ const cpm = Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0;
2280
+ const latencyMs = Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0;
2281
+ const mediaType = data.mediaType || "banner";
2282
+ if (auctionId && bidder) {
2283
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2284
+ const key = `${bidder}:${adUnitCode}`;
2285
+ const entry = auctionMap.get(key);
2286
+ const outcome = cpm > 0 ? BidderOutcome.BIDDER_OUTCOME_BID : BidderOutcome.BIDDER_OUTCOME_NO_BID;
2287
+ if (entry) {
2288
+ entry.outcome = outcome;
2289
+ entry.latencyMs = latencyMs;
2290
+ if (mediaType) entry.mediaType = mediaType;
2291
+ } else {
2292
+ auctionMap.set(key, {
2293
+ bidder,
2294
+ adUnitCode,
2295
+ mediaType,
2296
+ outcome,
2297
+ latencyMs
2298
+ });
2089
2299
  }
2090
- });
2300
+ }
2301
+ if (cpm > 0) {
2302
+ this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
2303
+ auctionId,
2304
+ transactionId: data.transactionId || "",
2305
+ adUnitCode,
2306
+ bid: {
2307
+ bidder,
2308
+ cpm,
2309
+ currency: data.originalCurrency ?? data.currency ?? "USD",
2310
+ ...parseBidDimensions(data),
2311
+ dealId: data.dealId || "",
2312
+ mediaType,
2313
+ latencyMs,
2314
+ advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2315
+ creativeId: data.creativeId || ""
2316
+ }
2317
+ });
2318
+ }
2091
2319
  }
2092
2320
  handleBidTimeout(data) {
2093
2321
  if (this.isDuplicate("bidTimeout", data)) return;
2094
2322
  this.log("DEBUG", "bidTimeout", data);
2095
2323
  const items = Array.isArray(data) ? data : data ? [data] : [];
2096
2324
  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
2325
+ const auctionId = t.auctionId || "";
2326
+ const bidder = t.bidderCode || t.bidder || "";
2327
+ const adUnitCode = t.adUnitCode || "";
2328
+ const latencyMs = Number.isFinite(t.timeout) ? t.timeout : 0;
2329
+ if (auctionId && bidder) {
2330
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2331
+ const key = `${bidder}:${adUnitCode}`;
2332
+ const entry = auctionMap.get(key);
2333
+ if (entry) {
2334
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_TIMEOUT;
2335
+ if (latencyMs > 0) entry.latencyMs = latencyMs;
2336
+ } else {
2337
+ auctionMap.set(key, {
2338
+ bidder,
2339
+ adUnitCode,
2340
+ mediaType: "banner",
2341
+ outcome: BidderOutcome.BIDDER_OUTCOME_TIMEOUT,
2342
+ latencyMs
2343
+ });
2104
2344
  }
2105
- });
2345
+ }
2106
2346
  }
2107
2347
  }
2108
2348
  handleBidWon(data) {
@@ -2141,6 +2381,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2141
2381
  this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
2142
2382
  }
2143
2383
  }
2384
+ this.pruneCachedWinningBids();
2144
2385
  }
2145
2386
  this.enqueue(TraceEventType.BID_WIN, "bidWon", {
2146
2387
  auctionId,
@@ -2152,14 +2393,27 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2152
2393
  handleNoBid(data) {
2153
2394
  if (this.isDuplicate("noBid", data)) return;
2154
2395
  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 || ""
2396
+ const auctionId = data.auctionId || "";
2397
+ const bidder = data.bidderCode || data.bidder || "";
2398
+ const adUnitCode = data.adUnitCode || "";
2399
+ if (auctionId && bidder) {
2400
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
2401
+ const key = `${bidder}:${adUnitCode}`;
2402
+ const entry = auctionMap.get(key);
2403
+ if (entry) {
2404
+ if (entry.outcome === BidderOutcome.BIDDER_OUTCOME_NO_RESPONSE) {
2405
+ entry.outcome = BidderOutcome.BIDDER_OUTCOME_NO_BID;
2406
+ }
2407
+ } else {
2408
+ auctionMap.set(key, {
2409
+ bidder,
2410
+ adUnitCode,
2411
+ mediaType: "banner",
2412
+ outcome: BidderOutcome.BIDDER_OUTCOME_NO_BID,
2413
+ latencyMs: 0
2414
+ });
2161
2415
  }
2162
- });
2416
+ }
2163
2417
  }
2164
2418
  handleAdRenderFailed(data) {
2165
2419
  if (this.isDuplicate("adRenderFailed", data)) return;
@@ -2191,7 +2445,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2191
2445
  const mediaType = bid.mediaType || "banner";
2192
2446
  const slotKey = data.adUnitCode || bid.adUnitCode || "";
2193
2447
  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);
2448
+ 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);
2195
2449
  const bidTrace = {
2196
2450
  bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
2197
2451
  cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
@@ -2280,7 +2534,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2280
2534
  }
2281
2535
  if (!el && targetId) {
2282
2536
  try {
2283
- el = document.querySelector(`[data-ad-slot="${targetId}"]`) || document.querySelector(`#${targetId}`);
2537
+ for (const candidate of Array.from(document.querySelectorAll("[data-ad-slot]"))) {
2538
+ if (candidate.getAttribute("data-ad-slot") === targetId) {
2539
+ el = candidate;
2540
+ break;
2541
+ }
2542
+ }
2284
2543
  } catch {
2285
2544
  }
2286
2545
  }
@@ -2334,6 +2593,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2334
2593
  if (data.viewableDurationMs !== void 0) {
2335
2594
  protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
2336
2595
  }
2596
+ if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2597
+ protoEvent.bidderOutcomes = data.bidderOutcomes;
2598
+ }
2337
2599
  const metadata = { ...data.metadata };
2338
2600
  if (data.error !== void 0 && data.error !== null) {
2339
2601
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
@@ -2344,8 +2606,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2344
2606
  if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
2345
2607
  metadata.refresh_index = String(data.refreshIndex);
2346
2608
  }
2347
- if (Object.keys(metadata).length > 0) {
2348
- protoEvent.metadata = metadata;
2609
+ const boundedMetadata = boundMetadata(metadata);
2610
+ if (Object.keys(boundedMetadata).length > 0) {
2611
+ protoEvent.metadata = boundedMetadata;
2349
2612
  }
2350
2613
  this.queue.push(protoEvent);
2351
2614
  if (this.queue.length > MAX_QUEUE_SIZE) {
@@ -2383,7 +2646,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2383
2646
  // Shared by flush() and flushBeacon().
2384
2647
  drainBatch() {
2385
2648
  if (this.queue.length === 0 || !this.config.endpoint) return null;
2386
- let events = this.queue;
2387
2649
  const domain = typeof window !== "undefined" ? window.location.hostname : "";
2388
2650
  const buildBatch = (evts) => ({
2389
2651
  propertyId: this.config.propertyId,
@@ -2397,25 +2659,37 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2397
2659
  domain,
2398
2660
  events: evts
2399
2661
  });
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(
2662
+ for (; ; ) {
2663
+ if (this.queue.length === 0) return null;
2664
+ let events = this.queue;
2665
+ let encoded = TraceEventBatch.encode(
2406
2666
  buildBatch(events)
2407
2667
  ).finish();
2668
+ while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
2669
+ events = events.slice(0, Math.floor(events.length / 2));
2670
+ encoded = TraceEventBatch.encode(
2671
+ buildBatch(events)
2672
+ ).finish();
2673
+ }
2674
+ if (encoded.byteLength > MAX_PAYLOAD_BYTES) {
2675
+ const [dropped] = this.queue.splice(0, 1);
2676
+ this.log(
2677
+ "WARN",
2678
+ `Dropping oversized event ${dropped?.eventName || dropped?.eventId} (${encoded.byteLength} bytes exceeds the ${MAX_PAYLOAD_BYTES}-byte cap on its own)`
2679
+ );
2680
+ continue;
2681
+ }
2682
+ this.queue = this.queue.slice(events.length);
2683
+ return {
2684
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
2685
+ // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
2686
+ // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
2687
+ encoded,
2688
+ events
2689
+ };
2408
2690
  }
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
2691
  }
2418
- sendFetch(payload, useKeepalive = false, onDelivered) {
2692
+ sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
2419
2693
  const fetchOpts = {
2420
2694
  method: "POST",
2421
2695
  // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
@@ -2432,15 +2706,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2432
2706
  this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
2433
2707
  return;
2434
2708
  }
2435
- this.handleSendFailure(payload.events);
2709
+ if (!isReplay) this.handleSendFailure(payload.events);
2436
2710
  } else {
2437
- this.consecutiveSendFailures = 0;
2438
- this.nextSendAllowedAt = 0;
2711
+ if (!isReplay) {
2712
+ this.consecutiveSendFailures = 0;
2713
+ this.nextSendAllowedAt = 0;
2714
+ }
2439
2715
  if (onDelivered) onDelivered();
2440
2716
  }
2441
2717
  }).catch((err) => {
2442
2718
  this.log("ERROR", "Failed to send batch", err);
2443
- this.handleSendFailure(payload.events);
2719
+ if (!isReplay) this.handleSendFailure(payload.events);
2444
2720
  });
2445
2721
  const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
2446
2722
  if (proc && typeof proc._tickCallback === "function") {
@@ -2496,16 +2772,23 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2496
2772
  this.schedulePersistedCleanup();
2497
2773
  }
2498
2774
  // --- Exit-batch persistence -----------------------------------------------
2775
+ // Key prefix for this instance's property. encodeURIComponent keeps the
2776
+ // property segment free of the "_" the timestamp suffix is parsed against,
2777
+ // so a property ID containing one cannot forge another property's prefix.
2778
+ pendingKeyPrefix() {
2779
+ return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
2780
+ }
2499
2781
  persistBatch(encoded) {
2500
2782
  try {
2501
2783
  if (typeof localStorage === "undefined") return null;
2784
+ if (!this.config.propertyId) return null;
2502
2785
  let pendingCount = 0;
2503
2786
  for (let i = 0; i < localStorage.length; i++) {
2504
2787
  const k = localStorage.key(i);
2505
2788
  if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
2506
2789
  }
2507
2790
  if (pendingCount >= MAX_PENDING_BATCHES) return null;
2508
- const key = `${PENDING_BATCH_KEY_PREFIX}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2791
+ const key = `${this.pendingKeyPrefix()}${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
2509
2792
  localStorage.setItem(key, bytesToBase64(encoded));
2510
2793
  this.persistedBatchKeys.push(key);
2511
2794
  return key;
@@ -2536,16 +2819,33 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2536
2819
  }, PENDING_BATCH_CLEANUP_DELAY_MS);
2537
2820
  }
2538
2821
  // 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.
2822
+ // confirm. Only this property's own keys are claimed: batches belonging to
2823
+ // another property sharing this origin are left for that property's next
2824
+ // pageview, since resending them here would file them under this property.
2825
+ // Keys are claimed (removed) before sending so concurrent tabs don't
2826
+ // double-send; the batch bytes carry their original pageview, session, and
2827
+ // event timestamps, so late rows attribute correctly.
2542
2828
  resendPersistedBatches() {
2543
2829
  try {
2544
2830
  if (typeof localStorage === "undefined" || !this.config.endpoint) return;
2831
+ if (!this.config.propertyId) return;
2832
+ const ownPrefix = this.pendingKeyPrefix();
2545
2833
  const keys = [];
2834
+ const legacyKeys = [];
2546
2835
  for (let i = 0; i < localStorage.length; i++) {
2547
2836
  const k = localStorage.key(i);
2548
- if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) keys.push(k);
2837
+ if (!k || !k.startsWith(PENDING_BATCH_KEY_PREFIX)) continue;
2838
+ if (k.startsWith(ownPrefix)) {
2839
+ keys.push(k);
2840
+ } else if (isLegacyPendingKey(k)) {
2841
+ legacyKeys.push(k);
2842
+ }
2843
+ }
2844
+ for (const key of legacyKeys) {
2845
+ try {
2846
+ localStorage.removeItem(key);
2847
+ } catch {
2848
+ }
2549
2849
  }
2550
2850
  for (const key of keys) {
2551
2851
  let value = null;
@@ -2556,7 +2856,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2556
2856
  continue;
2557
2857
  }
2558
2858
  if (!value) continue;
2559
- const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
2859
+ const ts = parseInt(key.slice(ownPrefix.length), 10);
2560
2860
  if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
2561
2861
  let encoded;
2562
2862
  try {
@@ -2566,11 +2866,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2566
2866
  }
2567
2867
  if (encoded.byteLength === 0) continue;
2568
2868
  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
- });
2869
+ this.sendFetch(
2870
+ {
2871
+ url: `${this.config.endpoint}/${this.config.propertyId}`,
2872
+ encoded,
2873
+ events: []
2874
+ },
2875
+ false,
2876
+ void 0,
2877
+ true
2878
+ );
2574
2879
  }
2575
2880
  } catch {
2576
2881
  }
@@ -2595,11 +2900,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2595
2900
  }
2596
2901
  }
2597
2902
  };
2598
- function extractAnalyticsOptions(config) {
2903
+ function extractAnalyticsOptions(config, allowProviderless = true) {
2599
2904
  if (!config) return null;
2600
2905
  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;
2906
+ const matched = config.find((item) => item?.provider === "bidkernel") ?? (allowProviderless ? config.find(
2907
+ (item) => item && typeof item === "object" && item.provider === void 0
2908
+ ) : void 0);
2909
+ return matched ? extractAnalyticsOptions(matched, allowProviderless) : null;
2603
2910
  }
2604
2911
  if (typeof config === "object") {
2605
2912
  if (config.options && typeof config.options === "object") {
@@ -2641,18 +2948,27 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2641
2948
  win[pbjsGlobalName] = win[pbjsGlobalName] || {};
2642
2949
  const pbjs = win[pbjsGlobalName];
2643
2950
  const enableInstance = (options) => {
2644
- if (win._bidkernelPrebidAnalytics) {
2951
+ const previous = win._bidkernelPrebidAnalytics;
2952
+ const pinnedEndpoint = previous?.getEndpoint?.() || "";
2953
+ const merged = {
2954
+ pbjsGlobalName,
2955
+ ...defaults,
2956
+ ...options
2957
+ };
2958
+ if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
2959
+ console.warn(
2960
+ `[bidkernel] Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
2961
+ );
2962
+ merged.endpoint = pinnedEndpoint;
2963
+ }
2964
+ if (previous) {
2645
2965
  try {
2646
- win._bidkernelPrebidAnalytics.disable();
2966
+ previous.disable();
2647
2967
  } catch (e) {
2648
2968
  console.warn("[bidkernel] Failed to disable previous analytics instance:", e);
2649
2969
  }
2650
2970
  }
2651
- const analytics = new BidkernelPrebidAnalytics({
2652
- pbjsGlobalName,
2653
- ...defaults,
2654
- ...options
2655
- });
2971
+ const analytics = new BidkernelPrebidAnalytics(merged);
2656
2972
  analytics.enable();
2657
2973
  win._bidkernelPrebidAnalytics = analytics;
2658
2974
  return analytics;
@@ -2663,7 +2979,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2663
2979
  pbjs.adapterManager.registerAnalyticsAdapter({
2664
2980
  adapter: {
2665
2981
  enableAnalytics: (config) => {
2666
- const options = extractAnalyticsOptions(config);
2982
+ const options = extractAnalyticsOptions(config, !Array.isArray(config));
2667
2983
  if (options) {
2668
2984
  enableInstance(options);
2669
2985
  }
@@ -2728,5 +3044,6 @@ export {
2728
3044
  PrebidEventDeduper,
2729
3045
  getPrebidEventKey,
2730
3046
  getbidkernel,
3047
+ isTrustedEndpoint,
2731
3048
  registerPrebidAnalytics
2732
3049
  };