@bidkernel/analytics 0.6.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 };
@@ -859,10 +995,21 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
859
995
  slotViewabilityRecords = /* @__PURE__ */ new Map();
860
996
  elementToSlotId = /* @__PURE__ */ new Map();
861
997
  slotRefreshIndices = /* @__PURE__ */ new Map();
998
+ slotLastAuctionIds = /* @__PURE__ */ new Map();
862
999
  pendingThresholdListeners = /* @__PURE__ */ new Map();
1000
+ // Impression deduplication per slot and refresh cycle (strictly 1 impression per cycle)
1001
+ slotEmittedImpressionKeys = /* @__PURE__ */ new Set();
1002
+ // Winning bid cache from bidWon to marry with subsequent render triggers (adRenderSucceeded, video, etc.)
1003
+ cachedWinningBids = /* @__PURE__ */ new Map();
1004
+ // Active video player attachment cleanup routines
1005
+ videoDetachCleanups = /* @__PURE__ */ new Set();
1006
+ // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1007
+ auctionBidderOutcomes = /* @__PURE__ */ new Map();
863
1008
  constructor(config) {
1009
+ const requestedEndpoint = config.endpoint || "";
1010
+ const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
864
1011
  this.config = {
865
- endpoint: config.endpoint || "",
1012
+ endpoint,
866
1013
  propertyId: config.propertyId || "",
867
1014
  pageviewId: config.pageviewId || generateUUID(),
868
1015
  sessionId: config.sessionId || getOrCreateSessionId(),
@@ -878,6 +1025,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
878
1025
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
879
1026
  attachPbjsListeners: config.attachPbjsListeners ?? true
880
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
+ }
881
1034
  this.boundFlushBeacon = () => {
882
1035
  this.flushAllSlotsTimeInView();
883
1036
  this.flushBeacon();
@@ -1005,6 +1158,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1005
1158
  }
1006
1159
  this.slotViewabilityRecords.clear();
1007
1160
  this.elementToSlotId.clear();
1161
+ for (const cleanup of this.videoDetachCleanups) {
1162
+ try {
1163
+ cleanup();
1164
+ } catch {
1165
+ }
1166
+ }
1167
+ this.videoDetachCleanups.clear();
1168
+ this.cachedWinningBids.clear();
1169
+ this.slotEmittedImpressionKeys.clear();
1170
+ this.slotLastAuctionIds.clear();
1171
+ this.auctionBidderOutcomes.clear();
1008
1172
  if (this.flushTimer) {
1009
1173
  clearInterval(this.flushTimer);
1010
1174
  this.flushTimer = null;
@@ -1327,6 +1491,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1327
1491
  if (options?.bid !== void 0) existing.bidPayload = options.bid;
1328
1492
  if (options?.metadata !== void 0) existing.metadata = options.metadata;
1329
1493
  }
1494
+ if (options?.auctionId) {
1495
+ this.slotLastAuctionIds.set(resolvedSlotId, options.auctionId);
1496
+ }
1330
1497
  if (el) {
1331
1498
  if (existing.element && existing.element !== el && this.intersectionObserver) {
1332
1499
  this.intersectionObserver.unobserve(existing.element);
@@ -1339,6 +1506,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1339
1506
  } else {
1340
1507
  const initialRefreshIndex = options?.refreshIndex ?? this.slotRefreshIndices.get(resolvedSlotId) ?? 0;
1341
1508
  this.slotRefreshIndices.set(resolvedSlotId, initialRefreshIndex);
1509
+ if (options?.auctionId) {
1510
+ this.slotLastAuctionIds.set(resolvedSlotId, options.auctionId);
1511
+ }
1342
1512
  const listeners = this.pendingThresholdListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
1343
1513
  this.pendingThresholdListeners.delete(resolvedSlotId);
1344
1514
  const record = {
@@ -1394,7 +1564,551 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1394
1564
  this.unobserveSlot(slotId);
1395
1565
  this.slotViewabilityRecords.delete(slotId);
1396
1566
  this.slotRefreshIndices.delete(slotId);
1567
+ this.slotLastAuctionIds.delete(slotId);
1397
1568
  this.pendingThresholdListeners.delete(slotId);
1569
+ const prefix = `${escapeKeyPart(slotId)}:`;
1570
+ for (const key of Array.from(this.slotEmittedImpressionKeys)) {
1571
+ if (key === slotId || key.startsWith(prefix)) {
1572
+ this.slotEmittedImpressionKeys.delete(key);
1573
+ }
1574
+ }
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;
1642
+ }
1643
+ hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1644
+ const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
1645
+ const baseSlotKey = `${escapeKeyPart(slotId)}:${rIndex}`;
1646
+ if (creativeId) {
1647
+ if (auctionId) {
1648
+ return this.slotEmittedImpressionKeys.has(
1649
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
1650
+ ) || this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1651
+ }
1652
+ return this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1653
+ }
1654
+ if (auctionId) {
1655
+ return this.slotEmittedImpressionKeys.has(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1656
+ }
1657
+ return this.slotEmittedImpressionKeys.has(baseSlotKey);
1658
+ }
1659
+ markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
1660
+ const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
1661
+ this.addImpressionKey(baseSlotKey);
1662
+ if (creativeId) {
1663
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
1664
+ }
1665
+ if (auctionId) {
1666
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
1667
+ if (creativeId) {
1668
+ this.addImpressionKey(
1669
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
1670
+ );
1671
+ }
1672
+ }
1673
+ }
1674
+ recordImpression(slotId, options) {
1675
+ if (!this.isEnabled) return false;
1676
+ const resolvedSlotId = slotId || options?.adUnitCode || "";
1677
+ const adUnitCode = options?.adUnitCode || resolvedSlotId;
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);
1679
+ const rawBid = options?.bid || cached?.rawBid || {};
1680
+ const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
1681
+ const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
1682
+ const creativeId = rawBid.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || "";
1683
+ const mediaType = options?.mediaType || rawBid.mediaType || cached?.bidTrace?.mediaType || "banner";
1684
+ const targetSlot = resolvedSlotId || adUnitCode;
1685
+ const rec = (resolvedSlotId ? this.slotViewabilityRecords.get(resolvedSlotId) : void 0) || (adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0);
1686
+ const lastAuctionId = targetSlot ? this.slotLastAuctionIds.get(targetSlot) || (rec ? rec.auctionId : void 0) : void 0;
1687
+ const isDifferentAuction = Boolean(
1688
+ targetSlot && auctionId && lastAuctionId && lastAuctionId !== auctionId && options?.refreshIndex === void 0
1689
+ );
1690
+ const bidTrace = {
1691
+ bidder: rawBid.bidderCode || rawBid.bidder || cached?.bidTrace?.bidder || "",
1692
+ cpm: Number.isFinite(rawBid.originalCpm) ? rawBid.originalCpm : Number.isFinite(rawBid.cpm) ? rawBid.cpm : cached?.bidTrace?.cpm ?? 0,
1693
+ currency: rawBid.originalCurrency ?? rawBid.currency ?? cached?.bidTrace?.currency ?? "USD",
1694
+ ...parseBidDimensions(rawBid).width ? parseBidDimensions(rawBid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(rawBid),
1695
+ dealId: rawBid.dealId || cached?.bidTrace?.dealId || "",
1696
+ mediaType,
1697
+ latencyMs: Number.isFinite(rawBid.timeToRespond) ? rawBid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
1698
+ advertiserDomain: rawBid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
1699
+ creativeId: rawBid.creativeId || cached?.bidTrace?.creativeId || ""
1700
+ };
1701
+ if (isDifferentAuction && targetSlot) {
1702
+ this.flushSlotTimeInView(targetSlot);
1703
+ const nextRefreshIndex = (this.slotRefreshIndices.get(targetSlot) ?? (rec ? rec.refreshIndex : 0)) + 1;
1704
+ this.slotRefreshIndices.set(targetSlot, nextRefreshIndex);
1705
+ this.slotLastAuctionIds.set(targetSlot, auctionId);
1706
+ if (rec) {
1707
+ rec.refreshIndex = nextRefreshIndex;
1708
+ rec.auctionId = auctionId;
1709
+ rec.transactionId = transactionId;
1710
+ rec.bidPayload = bidTrace;
1711
+ rec.viewableFired = false;
1712
+ rec.accumulatedTimeInViewMs = 0;
1713
+ }
1714
+ this.enqueue(TraceEventType.REFRESH, "refresh", {
1715
+ auctionId,
1716
+ transactionId,
1717
+ adUnitCode: targetSlot,
1718
+ bid: bidTrace,
1719
+ metadata: {
1720
+ ...options?.metadata,
1721
+ refresh_index: String(nextRefreshIndex)
1722
+ }
1723
+ });
1724
+ } else if (targetSlot) {
1725
+ if (auctionId) {
1726
+ this.slotLastAuctionIds.set(targetSlot, auctionId);
1727
+ }
1728
+ if (!this.slotRefreshIndices.has(targetSlot)) {
1729
+ this.slotRefreshIndices.set(targetSlot, 0);
1730
+ }
1731
+ }
1732
+ const currentRefreshIndex = options?.refreshIndex ?? (resolvedSlotId ? this.slotRefreshIndices.get(resolvedSlotId) ?? (adUnitCode ? this.slotRefreshIndices.get(adUnitCode) : void 0) ?? (rec ? rec.refreshIndex : 0) : 0);
1733
+ const alreadyEmitted = resolvedSlotId && this.hasImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId) || adUnitCode && adUnitCode !== resolvedSlotId && this.hasImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
1734
+ if (alreadyEmitted) {
1735
+ this.log(
1736
+ "DEBUG",
1737
+ `Impression already emitted for slot ${resolvedSlotId || adUnitCode} in cycle ${currentRefreshIndex}`
1738
+ );
1739
+ return false;
1740
+ }
1741
+ if (resolvedSlotId)
1742
+ this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId);
1743
+ if (adUnitCode && adUnitCode !== resolvedSlotId)
1744
+ this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
1745
+ this.enqueue(TraceEventType.IMPRESSION, "impression", {
1746
+ auctionId,
1747
+ transactionId,
1748
+ adUnitCode,
1749
+ bid: bidTrace,
1750
+ metadata: {
1751
+ ...options?.metadata,
1752
+ refresh_index: String(currentRefreshIndex)
1753
+ }
1754
+ });
1755
+ if (rec) {
1756
+ if (!rec.bidPayload && bidTrace) rec.bidPayload = bidTrace;
1757
+ if (!rec.auctionId && auctionId) rec.auctionId = auctionId;
1758
+ if (!rec.transactionId && transactionId) rec.transactionId = transactionId;
1759
+ }
1760
+ return true;
1761
+ }
1762
+ /**
1763
+ * Bridges Google IMA SDK AdsManager events to Bidkernel analytics.
1764
+ * Defers IMPRESSION emission until AdEvent.STARTED (or IMPRESSION),
1765
+ * tracks milestones (FIRST_QUARTILE, MIDPOINT, THIRD_QUARTILE, COMPLETE),
1766
+ * and handles AD_ERROR.
1767
+ */
1768
+ attachImaAdsManager(adsManager, options) {
1769
+ if (!adsManager || typeof adsManager.addEventListener !== "function") {
1770
+ this.log("WARN", "Invalid AdsManager passed to attachImaAdsManager");
1771
+ return () => {
1772
+ };
1773
+ }
1774
+ const slotId = options?.slotId || options?.adUnitCode || "video";
1775
+ const adUnitCode = options?.adUnitCode || slotId;
1776
+ const auctionId = options?.auctionId || "";
1777
+ const transactionId = options?.transactionId || "";
1778
+ const getWinningBid = () => {
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);
1780
+ };
1781
+ const onAdStartedOrImpression = (event) => {
1782
+ this.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
1783
+ const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
1784
+ const adData = {};
1785
+ if (ad) {
1786
+ adData.creativeId = (typeof ad.getCreativeId === "function" ? ad.getCreativeId() : ad.creativeId) || "";
1787
+ adData.adId = (typeof ad.getAdId === "function" ? ad.getAdId() : ad.id) || "";
1788
+ adData.title = (typeof ad.getTitle === "function" ? ad.getTitle() : ad.title) || "";
1789
+ adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
1790
+ adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
1791
+ }
1792
+ const cached = getWinningBid();
1793
+ const mergedBid = {
1794
+ ...cached?.rawBid || options?.bid,
1795
+ mediaType: "video",
1796
+ creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
1797
+ };
1798
+ const emitted = this.recordImpression(slotId, {
1799
+ adUnitCode,
1800
+ auctionId: auctionId || cached?.auctionId,
1801
+ transactionId: transactionId || cached?.transactionId,
1802
+ mediaType: "video",
1803
+ bid: mergedBid,
1804
+ metadata: {
1805
+ ...options?.metadata,
1806
+ media_type: "video",
1807
+ ...adData.title ? { ad_title: adData.title } : {},
1808
+ ...Number.isFinite(adData.duration) ? { ad_duration_s: String(adData.duration) } : {}
1809
+ }
1810
+ });
1811
+ if (emitted && options?.onImpression) {
1812
+ try {
1813
+ options.onImpression(slotId, { ad, bid: mergedBid });
1814
+ } catch (e) {
1815
+ this.log("ERROR", "Error in onImpression callback", e);
1816
+ }
1817
+ }
1818
+ };
1819
+ const onMilestone = (milestone) => {
1820
+ this.log("DEBUG", `IMA AdEvent milestone: ${milestone}`);
1821
+ if (options?.onMilestone) {
1822
+ try {
1823
+ options.onMilestone(milestone, slotId);
1824
+ } catch (e) {
1825
+ this.log("ERROR", "Error in onMilestone callback", e);
1826
+ }
1827
+ }
1828
+ };
1829
+ const onAdClick = (event) => {
1830
+ this.log("DEBUG", "IMA AdEvent.CLICK received", event);
1831
+ const cached = getWinningBid();
1832
+ this.enqueue(TraceEventType.CLICK, "click", {
1833
+ auctionId: auctionId || cached?.auctionId,
1834
+ transactionId: transactionId || cached?.transactionId,
1835
+ adUnitCode,
1836
+ bid: cached?.bidTrace || options?.bid,
1837
+ metadata: {
1838
+ ...options?.metadata,
1839
+ media_type: "video"
1840
+ }
1841
+ });
1842
+ };
1843
+ const onAdError = (event) => {
1844
+ this.log("WARN", "IMA AdErrorEvent received", event);
1845
+ const err = typeof event?.getError === "function" ? event.getError() : event?.error || event;
1846
+ const msg = (err && typeof err.getMessage === "function" ? err.getMessage() : err?.message) || String(err || "IMA ad error");
1847
+ const code = (err && typeof err.getErrorCode === "function" ? err.getErrorCode() : err?.code) || "";
1848
+ const cached = getWinningBid();
1849
+ this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
1850
+ auctionId: auctionId || cached?.auctionId,
1851
+ transactionId: transactionId || cached?.transactionId,
1852
+ adUnitCode,
1853
+ bid: cached?.bidTrace || options?.bid,
1854
+ metadata: {
1855
+ ...options?.metadata,
1856
+ reason: "ima_ad_error",
1857
+ message: msg,
1858
+ ...code ? { ima_error_code: String(code) } : {}
1859
+ },
1860
+ error: typeof err === "object" ? err : new Error(msg)
1861
+ });
1862
+ if (options?.onError) {
1863
+ try {
1864
+ options.onError(err);
1865
+ } catch (e) {
1866
+ this.log("ERROR", "Error in onError callback", e);
1867
+ }
1868
+ }
1869
+ };
1870
+ const googleIma = typeof window !== "undefined" ? window.google?.ima : void 0;
1871
+ const adEventType = googleIma?.AdEvent?.Type || {};
1872
+ const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
1873
+ const listeners = [
1874
+ { type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
1875
+ { type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
1876
+ {
1877
+ type: adEventType.FIRST_QUARTILE || "firstQuartile",
1878
+ handler: () => onMilestone("firstQuartile")
1879
+ },
1880
+ { type: adEventType.MIDPOINT || "midpoint", handler: () => onMilestone("midpoint") },
1881
+ {
1882
+ type: adEventType.THIRD_QUARTILE || "thirdQuartile",
1883
+ handler: () => onMilestone("thirdQuartile")
1884
+ },
1885
+ { type: adEventType.COMPLETE || "complete", handler: () => onMilestone("complete") },
1886
+ { type: adEventType.CLICK || "click", handler: onAdClick },
1887
+ { type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
1888
+ ];
1889
+ for (const { type, handler } of listeners) {
1890
+ try {
1891
+ adsManager.addEventListener(type, handler);
1892
+ } catch {
1893
+ }
1894
+ }
1895
+ const cleanup = () => {
1896
+ for (const { type, handler } of listeners) {
1897
+ try {
1898
+ adsManager.removeEventListener(type, handler);
1899
+ } catch {
1900
+ }
1901
+ }
1902
+ this.videoDetachCleanups.delete(cleanup);
1903
+ };
1904
+ this.videoDetachCleanups.add(cleanup);
1905
+ return cleanup;
1906
+ }
1907
+ /**
1908
+ * Attaches render hooks and viewability tracking to a video player.
1909
+ * Supports HTML5 <video> elements, container elements, or Google IMA AdsManager.
1910
+ */
1911
+ attachVideoPlayer(target, options) {
1912
+ if (!target) {
1913
+ this.log("WARN", "attachVideoPlayer called with null/undefined target");
1914
+ return () => {
1915
+ };
1916
+ }
1917
+ let el = null;
1918
+ if (typeof target === "string") {
1919
+ if (typeof document !== "undefined") {
1920
+ try {
1921
+ el = document.querySelector(target) || document.getElementById(target);
1922
+ } catch {
1923
+ el = document.getElementById(target);
1924
+ }
1925
+ }
1926
+ } else if (typeof HTMLElement !== "undefined" && target instanceof HTMLElement) {
1927
+ el = target;
1928
+ } else if (target && typeof target === "object" && target.nodeType === 1) {
1929
+ el = target;
1930
+ } else if (
1931
+ // Check if target is an IMA AdsManager
1932
+ typeof target.addEventListener === "function" && (typeof target.init === "function" || typeof target.start === "function" || typeof target.getCuePoints === "function" || typeof target.getVolume === "function" || target.__imaAdsManager)
1933
+ ) {
1934
+ return this.attachImaAdsManager(target, options);
1935
+ } else if (target && typeof target.addEventListener === "function") {
1936
+ el = target;
1937
+ }
1938
+ const slotId = options?.slotId || options?.adUnitCode || (el ? el.id : "") || "video";
1939
+ const adUnitCode = options?.adUnitCode || slotId;
1940
+ const auctionId = options?.auctionId || "";
1941
+ const transactionId = options?.transactionId || "";
1942
+ const getWinningBid = () => {
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);
1944
+ };
1945
+ let videoEl = null;
1946
+ if (el) {
1947
+ if (el.tagName && el.tagName.toLowerCase() === "video") {
1948
+ videoEl = el;
1949
+ } else {
1950
+ videoEl = el.querySelector("video");
1951
+ }
1952
+ }
1953
+ const quartilesFired = {
1954
+ q1: false,
1955
+ q2: false,
1956
+ q3: false,
1957
+ q4: false
1958
+ };
1959
+ const triggerPlaybackImpression = (activeVideo) => {
1960
+ const vEl = activeVideo || videoEl || (el?.querySelector ? el.querySelector("video") : null);
1961
+ const cached = getWinningBid();
1962
+ const mergedBid = {
1963
+ ...cached?.rawBid || options?.bid,
1964
+ mediaType: "video"
1965
+ };
1966
+ const emitted = this.recordImpression(slotId, {
1967
+ adUnitCode,
1968
+ auctionId: auctionId || cached?.auctionId,
1969
+ transactionId: transactionId || cached?.transactionId,
1970
+ mediaType: "video",
1971
+ bid: mergedBid,
1972
+ metadata: {
1973
+ ...options?.metadata,
1974
+ media_type: "video",
1975
+ ...vEl && Number.isFinite(vEl.duration) ? { video_duration_s: String(Math.round(vEl.duration)) } : {}
1976
+ }
1977
+ });
1978
+ if (emitted && options?.onImpression) {
1979
+ try {
1980
+ options.onImpression(slotId, { element: vEl || el, bid: mergedBid });
1981
+ } catch (e) {
1982
+ this.log("ERROR", "Error in onImpression callback", e);
1983
+ }
1984
+ }
1985
+ };
1986
+ const handlePlaying = (e) => {
1987
+ this.log("DEBUG", "HTML5 video playing event received");
1988
+ const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
1989
+ triggerPlaybackImpression(targetVideo);
1990
+ };
1991
+ const handleTimeUpdate = (e) => {
1992
+ const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
1993
+ if (targetVideo && targetVideo.currentTime > 0) {
1994
+ triggerPlaybackImpression(targetVideo);
1995
+ if (Number.isFinite(targetVideo.duration) && targetVideo.duration > 0) {
1996
+ const progress = targetVideo.currentTime / targetVideo.duration;
1997
+ if (progress >= 0.25 && !quartilesFired.q1) {
1998
+ quartilesFired.q1 = true;
1999
+ options?.onMilestone?.("firstQuartile", slotId);
2000
+ }
2001
+ if (progress >= 0.5 && !quartilesFired.q2) {
2002
+ quartilesFired.q2 = true;
2003
+ options?.onMilestone?.("midpoint", slotId);
2004
+ }
2005
+ if (progress >= 0.75 && !quartilesFired.q3) {
2006
+ quartilesFired.q3 = true;
2007
+ options?.onMilestone?.("thirdQuartile", slotId);
2008
+ }
2009
+ }
2010
+ }
2011
+ };
2012
+ const handleEnded = () => {
2013
+ if (!quartilesFired.q4) {
2014
+ quartilesFired.q4 = true;
2015
+ options?.onMilestone?.("complete", slotId);
2016
+ }
2017
+ };
2018
+ const handleError = (e) => {
2019
+ const targetVideo = e?.target instanceof HTMLVideoElement ? e.target : videoEl;
2020
+ const err = targetVideo?.error || videoEl?.error;
2021
+ const cached = getWinningBid();
2022
+ const msg = err ? `HTML5 video error code ${err.code}: ${err.message}` : "Video playback error";
2023
+ this.log("WARN", msg);
2024
+ this.enqueue(TraceEventType.AD_RENDER_FAILED, "adRenderFailed", {
2025
+ auctionId: auctionId || cached?.auctionId,
2026
+ transactionId: transactionId || cached?.transactionId,
2027
+ adUnitCode,
2028
+ bid: cached?.bidTrace || options?.bid,
2029
+ metadata: {
2030
+ ...options?.metadata,
2031
+ reason: "html5_video_error",
2032
+ message: msg,
2033
+ ...err?.code ? { video_error_code: String(err.code) } : {}
2034
+ },
2035
+ error: err ? new Error(msg) : void 0
2036
+ });
2037
+ if (options?.onError) {
2038
+ try {
2039
+ options.onError(err);
2040
+ } catch (e2) {
2041
+ this.log("ERROR", "Error in onError callback", e2);
2042
+ }
2043
+ }
2044
+ };
2045
+ const attachListenersToVideo = (targetVideo) => {
2046
+ targetVideo.addEventListener("playing", handlePlaying);
2047
+ targetVideo.addEventListener("play", handlePlaying);
2048
+ targetVideo.addEventListener("timeupdate", handleTimeUpdate);
2049
+ targetVideo.addEventListener("ended", handleEnded);
2050
+ targetVideo.addEventListener("error", handleError);
2051
+ };
2052
+ const removeListenersFromVideo = (targetVideo) => {
2053
+ targetVideo.removeEventListener("playing", handlePlaying);
2054
+ targetVideo.removeEventListener("play", handlePlaying);
2055
+ targetVideo.removeEventListener("timeupdate", handleTimeUpdate);
2056
+ targetVideo.removeEventListener("ended", handleEnded);
2057
+ targetVideo.removeEventListener("error", handleError);
2058
+ };
2059
+ let mutationObserver = null;
2060
+ if (el && el !== videoEl) {
2061
+ el.addEventListener("playing", handlePlaying, true);
2062
+ el.addEventListener("play", handlePlaying, true);
2063
+ el.addEventListener("timeupdate", handleTimeUpdate, true);
2064
+ el.addEventListener("ended", handleEnded, true);
2065
+ el.addEventListener("error", handleError, true);
2066
+ }
2067
+ if (videoEl) {
2068
+ attachListenersToVideo(videoEl);
2069
+ }
2070
+ if (el && el.tagName?.toLowerCase() !== "video" && typeof MutationObserver !== "undefined") {
2071
+ mutationObserver = new MutationObserver(() => {
2072
+ const found = el.querySelector("video");
2073
+ if (found && found !== videoEl) {
2074
+ if (videoEl) removeListenersFromVideo(videoEl);
2075
+ videoEl = found;
2076
+ if (videoEl) attachListenersToVideo(videoEl);
2077
+ }
2078
+ });
2079
+ mutationObserver.observe(el, { childList: true, subtree: true });
2080
+ }
2081
+ const cachedInitial = getWinningBid();
2082
+ if (options?.trackViewability !== false && el) {
2083
+ this.observeSlot(el, slotId, {
2084
+ adUnitCode,
2085
+ auctionId: auctionId || cachedInitial?.auctionId,
2086
+ transactionId: transactionId || cachedInitial?.transactionId,
2087
+ mediaType: "video",
2088
+ bid: cachedInitial?.bidTrace || options?.bid,
2089
+ metadata: options?.metadata,
2090
+ emitRefreshEvent: false
2091
+ });
2092
+ }
2093
+ const cleanup = () => {
2094
+ if (mutationObserver) {
2095
+ mutationObserver.disconnect();
2096
+ mutationObserver = null;
2097
+ }
2098
+ if (el && el !== videoEl) {
2099
+ el.removeEventListener("playing", handlePlaying, true);
2100
+ el.removeEventListener("play", handlePlaying, true);
2101
+ el.removeEventListener("timeupdate", handleTimeUpdate, true);
2102
+ el.removeEventListener("ended", handleEnded, true);
2103
+ el.removeEventListener("error", handleError, true);
2104
+ }
2105
+ if (videoEl) {
2106
+ removeListenersFromVideo(videoEl);
2107
+ }
2108
+ this.videoDetachCleanups.delete(cleanup);
2109
+ };
2110
+ this.videoDetachCleanups.add(cleanup);
2111
+ return cleanup;
1398
2112
  }
1399
2113
  onTimeInViewThreshold(slotId, thresholdMs, callback) {
1400
2114
  const listener = {
@@ -1462,6 +2176,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1462
2176
  refreshIndex: record.refreshIndex
1463
2177
  };
1464
2178
  }
2179
+ /** The validated ingest endpoint in use, or "" when none passed validation. */
2180
+ getEndpoint() {
2181
+ return this.config.endpoint;
2182
+ }
1465
2183
  getRefreshIndex(slotId) {
1466
2184
  return this.slotRefreshIndices.get(slotId) ?? 0;
1467
2185
  }
@@ -1502,17 +2220,29 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1502
2220
  handleAuctionInit(data) {
1503
2221
  if (this.isDuplicate("auctionInit", data)) return;
1504
2222
  this.log("DEBUG", "auctionInit", data);
2223
+ const auctionId = data.auctionId || "";
2224
+ if (auctionId) {
2225
+ this.auctionBidderOutcomes.delete(auctionId);
2226
+ this.getOrCreateAuctionOutcomes(auctionId);
2227
+ }
1505
2228
  this.enqueue(TraceEventType.AUCTION_START, "auctionStart", {
1506
- auctionId: data.auctionId || "",
2229
+ auctionId,
1507
2230
  transactionId: data.transactionId || ""
1508
2231
  });
1509
2232
  }
1510
2233
  handleAuctionEnd(data) {
1511
2234
  if (this.isDuplicate("auctionEnd", data)) return;
1512
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
+ }
1513
2242
  this.enqueue(TraceEventType.AUCTION_END, "auctionEnd", {
1514
- auctionId: data.auctionId || "",
1515
- transactionId: data.transactionId || ""
2243
+ auctionId,
2244
+ transactionId: data.transactionId || "",
2245
+ bidderOutcomes
1516
2246
  });
1517
2247
  }
1518
2248
  handleBidRequested(data) {
@@ -1520,121 +2250,170 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1520
2250
  this.log("DEBUG", "bidRequested", data);
1521
2251
  const auctionId = data.auctionId || "";
1522
2252
  const bidder = data.bidderCode || data.bidder || "";
2253
+ if (!auctionId || !bidder) return;
2254
+ const auctionMap = this.getOrCreateAuctionOutcomes(auctionId);
1523
2255
  if (Array.isArray(data.bids)) {
1524
2256
  data.bids.forEach((bid) => {
2257
+ const adUnitCode = bid.adUnitCode || "";
1525
2258
  const mediaTypes = Object.keys(bid.mediaTypes || {});
1526
- const gpid = bid.ortb2Imp?.ext?.gpid || bid.gpid || data.gpid || "";
1527
- const transactionId = bid.transactionId || bid.ortb2Imp?.id || data.transactionId || "";
1528
- if (mediaTypes.length === 0) {
1529
- const mediaType = bid.mediaType || "banner";
1530
- const { width, height } = parseSize(bid.sizes || bid.playerSize);
1531
- this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
1532
- auctionId,
1533
- transactionId,
1534
- adUnitCode: bid.adUnitCode || "",
1535
- gpid,
1536
- bid: {
1537
- bidder,
1538
- mediaType,
1539
- width,
1540
- height
1541
- }
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
1542
2268
  });
1543
- return;
1544
2269
  }
1545
- mediaTypes.forEach((mediaType) => {
1546
- let rawSize = null;
1547
- if (mediaType === "banner") {
1548
- rawSize = bid.mediaTypes?.banner?.sizes || bid.sizes;
1549
- } else if (mediaType === "video") {
1550
- rawSize = bid.mediaTypes?.video?.playerSize || bid.mediaTypes?.video?.sizes || bid.playerSize;
1551
- } else {
1552
- rawSize = bid.mediaTypes?.[mediaType]?.sizes;
1553
- }
1554
- const { width, height } = parseSize(rawSize);
1555
- this.enqueue(TraceEventType.BID_REQUEST, "bidRequest", {
1556
- auctionId,
1557
- transactionId,
1558
- adUnitCode: bid.adUnitCode || "",
1559
- gpid,
1560
- bid: {
1561
- bidder,
1562
- mediaType,
1563
- width,
1564
- height
1565
- }
1566
- });
1567
- });
1568
2270
  });
1569
2271
  }
1570
2272
  }
1571
2273
  handleBidResponse(data) {
1572
2274
  if (this.isDuplicate("bidResponse", data)) return;
1573
2275
  this.log("DEBUG", "bidResponse", data);
1574
- this.enqueue(TraceEventType.BID_RESPONSE, "bidResponse", {
1575
- auctionId: data.auctionId || "",
1576
- transactionId: data.transactionId || "",
1577
- adUnitCode: data.adUnitCode || "",
1578
- bid: {
1579
- bidder: data.bidderCode || data.bidder || "",
1580
- cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
1581
- currency: data.originalCurrency ?? data.currency ?? "USD",
1582
- ...parseBidDimensions(data),
1583
- dealId: data.dealId || "",
1584
- mediaType: data.mediaType || "banner",
1585
- latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
1586
- advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
1587
- 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
+ });
1588
2299
  }
1589
- });
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
+ }
1590
2319
  }
1591
2320
  handleBidTimeout(data) {
1592
2321
  if (this.isDuplicate("bidTimeout", data)) return;
1593
2322
  this.log("DEBUG", "bidTimeout", data);
1594
2323
  const items = Array.isArray(data) ? data : data ? [data] : [];
1595
2324
  for (const t of items) {
1596
- this.enqueue(TraceEventType.BID_TIMEOUT, "bidTimeout", {
1597
- auctionId: t.auctionId || "",
1598
- transactionId: t.transactionId || "",
1599
- adUnitCode: t.adUnitCode || "",
1600
- bid: {
1601
- bidder: t.bidderCode || t.bidder || "",
1602
- 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
+ });
1603
2344
  }
1604
- });
2345
+ }
1605
2346
  }
1606
2347
  }
1607
2348
  handleBidWon(data) {
1608
2349
  if (this.isDuplicate("bidWon", data)) return;
1609
2350
  this.log("DEBUG", "bidWon", data);
1610
- this.enqueue(TraceEventType.BID_WIN, "bidWon", {
1611
- auctionId: data.auctionId || "",
1612
- transactionId: data.transactionId || "",
1613
- adUnitCode: data.adUnitCode || "",
1614
- bid: {
1615
- bidder: data.bidderCode || data.bidder || "",
1616
- cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
1617
- currency: data.originalCurrency ?? data.currency ?? "USD",
1618
- ...parseBidDimensions(data),
1619
- dealId: data.dealId || "",
1620
- mediaType: data.mediaType || "banner",
1621
- latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
1622
- advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
1623
- creativeId: data.creativeId || ""
2351
+ const auctionId = data.auctionId || "";
2352
+ const transactionId = data.transactionId || "";
2353
+ const adUnitCode = data.adUnitCode || data.adId || "";
2354
+ const bidTrace = {
2355
+ bidder: data.bidderCode || data.bidder || "",
2356
+ cpm: Number.isFinite(data.originalCpm) ? data.originalCpm : Number.isFinite(data.cpm) ? data.cpm : 0,
2357
+ currency: data.originalCurrency ?? data.currency ?? "USD",
2358
+ ...parseBidDimensions(data),
2359
+ dealId: data.dealId || "",
2360
+ mediaType: data.mediaType || "banner",
2361
+ latencyMs: Number.isFinite(data.timeToRespond) ? data.timeToRespond : 0,
2362
+ advertiserDomain: data.meta?.advertiserDomains?.[0] || "",
2363
+ creativeId: data.creativeId || ""
2364
+ };
2365
+ if (adUnitCode) {
2366
+ const cachedEntry = {
2367
+ bidTrace,
2368
+ auctionId,
2369
+ transactionId,
2370
+ adUnitCode,
2371
+ rawBid: data,
2372
+ timestamp: Date.now()
2373
+ };
2374
+ this.cachedWinningBids.set(adUnitCode, cachedEntry);
2375
+ if (data.adId && data.adId !== adUnitCode) {
2376
+ this.cachedWinningBids.set(data.adId, cachedEntry);
1624
2377
  }
2378
+ if (auctionId) {
2379
+ this.cachedWinningBids.set(`${auctionId}:${adUnitCode}`, cachedEntry);
2380
+ if (data.adId && data.adId !== adUnitCode) {
2381
+ this.cachedWinningBids.set(`${auctionId}:${data.adId}`, cachedEntry);
2382
+ }
2383
+ }
2384
+ this.pruneCachedWinningBids();
2385
+ }
2386
+ this.enqueue(TraceEventType.BID_WIN, "bidWon", {
2387
+ auctionId,
2388
+ transactionId,
2389
+ adUnitCode,
2390
+ bid: bidTrace
1625
2391
  });
1626
2392
  }
1627
2393
  handleNoBid(data) {
1628
2394
  if (this.isDuplicate("noBid", data)) return;
1629
2395
  this.log("DEBUG", "noBid", data);
1630
- this.enqueue(TraceEventType.NO_BID, "noBid", {
1631
- auctionId: data.auctionId || "",
1632
- transactionId: data.transactionId || "",
1633
- adUnitCode: data.adUnitCode || "",
1634
- bid: {
1635
- 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
+ });
1636
2415
  }
1637
- });
2416
+ }
1638
2417
  }
1639
2418
  handleAdRenderFailed(data) {
1640
2419
  if (this.isDuplicate("adRenderFailed", data)) return;
@@ -1660,44 +2439,81 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1660
2439
  if (this.isDuplicate("adRenderSucceeded", data)) return;
1661
2440
  this.log("DEBUG", "adRenderSucceeded", data);
1662
2441
  const bid = data.bid || data || {};
1663
- const adUnitCode = data.adUnitCode || bid.adUnitCode || "";
2442
+ const adUnitCode = data.adUnitCode || bid.adUnitCode || data.adId || bid.adId || "";
1664
2443
  const auctionId = bid.auctionId || data.auctionId || "";
1665
2444
  const transactionId = bid.transactionId || data.transactionId || "";
1666
2445
  const mediaType = bid.mediaType || "banner";
2446
+ const slotKey = data.adUnitCode || bid.adUnitCode || "";
2447
+ const altKey = data.adId || bid.adId || "";
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);
1667
2449
  const bidTrace = {
1668
- bidder: bid.bidderCode || bid.bidder || "",
1669
- cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : 0,
1670
- currency: bid.originalCurrency ?? bid.currency ?? "USD",
1671
- ...parseBidDimensions(bid),
1672
- dealId: bid.dealId || "",
1673
- mediaType,
1674
- latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : 0,
1675
- advertiserDomain: bid.meta?.advertiserDomains?.[0] || "",
1676
- creativeId: bid.creativeId || ""
2450
+ bidder: bid.bidderCode || bid.bidder || cached?.bidTrace?.bidder || "",
2451
+ cpm: Number.isFinite(bid.originalCpm) ? bid.originalCpm : Number.isFinite(bid.cpm) ? bid.cpm : cached?.bidTrace?.cpm ?? 0,
2452
+ currency: bid.originalCurrency ?? bid.currency ?? cached?.bidTrace?.currency ?? "USD",
2453
+ ...parseBidDimensions(bid).width ? parseBidDimensions(bid) : cached?.bidTrace ? { width: cached.bidTrace.width, height: cached.bidTrace.height } : parseBidDimensions(bid),
2454
+ dealId: bid.dealId || cached?.bidTrace?.dealId || "",
2455
+ mediaType: mediaType || cached?.bidTrace?.mediaType || "banner",
2456
+ latencyMs: Number.isFinite(bid.timeToRespond) ? bid.timeToRespond : cached?.bidTrace?.latencyMs ?? 0,
2457
+ advertiserDomain: bid.meta?.advertiserDomains?.[0] || cached?.bidTrace?.advertiserDomain || "",
2458
+ creativeId: bid.creativeId || cached?.bidTrace?.creativeId || ""
1677
2459
  };
1678
- const isRefresh = adUnitCode && (this.slotViewabilityRecords.has(adUnitCode) || this.slotRefreshIndices.has(adUnitCode) && this.slotRefreshIndices.get(adUnitCode) > 0);
2460
+ const resolvedAuctionId = auctionId || cached?.auctionId || "";
2461
+ const resolvedTransactionId = transactionId || cached?.transactionId || "";
2462
+ const record = adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0;
2463
+ const isDifferentAuction = record && resolvedAuctionId && record.auctionId && record.auctionId !== resolvedAuctionId;
2464
+ const isRefresh = Boolean(adUnitCode && record && isDifferentAuction);
1679
2465
  if (isRefresh) {
1680
2466
  this.flushSlotTimeInView(adUnitCode);
1681
- const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? 0) + 1;
2467
+ const nextRefreshIndex = (this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0)) + 1;
1682
2468
  this.slotRefreshIndices.set(adUnitCode, nextRefreshIndex);
2469
+ this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
2470
+ if (record) {
2471
+ record.refreshIndex = nextRefreshIndex;
2472
+ record.auctionId = resolvedAuctionId;
2473
+ record.transactionId = resolvedTransactionId;
2474
+ record.bidPayload = bidTrace;
2475
+ record.viewableFired = false;
2476
+ record.accumulatedTimeInViewMs = 0;
2477
+ }
1683
2478
  this.enqueue(TraceEventType.REFRESH, "refresh", {
1684
- auctionId,
1685
- transactionId,
2479
+ auctionId: resolvedAuctionId,
2480
+ transactionId: resolvedTransactionId,
1686
2481
  adUnitCode,
1687
2482
  bid: bidTrace,
1688
2483
  metadata: { refresh_index: String(nextRefreshIndex) }
1689
2484
  });
1690
- } else if (adUnitCode && !this.slotRefreshIndices.has(adUnitCode)) {
1691
- this.slotRefreshIndices.set(adUnitCode, 0);
2485
+ } else if (adUnitCode) {
2486
+ if (resolvedAuctionId) {
2487
+ this.slotLastAuctionIds.set(adUnitCode, resolvedAuctionId);
2488
+ }
2489
+ if (!this.slotRefreshIndices.has(adUnitCode)) {
2490
+ this.slotRefreshIndices.set(adUnitCode, 0);
2491
+ }
1692
2492
  }
1693
- const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? 0 : 0;
1694
- this.enqueue(TraceEventType.IMPRESSION, "impression", {
1695
- auctionId,
1696
- transactionId,
2493
+ const currentRefreshIndex = adUnitCode ? this.slotRefreshIndices.get(adUnitCode) ?? (record ? record.refreshIndex : 0) : 0;
2494
+ const alreadyEmitted = adUnitCode ? this.hasImpressionEmitted(
1697
2495
  adUnitCode,
1698
- bid: bidTrace,
1699
- metadata: { refresh_index: String(currentRefreshIndex) }
1700
- });
2496
+ currentRefreshIndex,
2497
+ resolvedAuctionId,
2498
+ bidTrace.creativeId
2499
+ ) : false;
2500
+ if (!alreadyEmitted) {
2501
+ if (adUnitCode) {
2502
+ this.markImpressionEmitted(
2503
+ adUnitCode,
2504
+ currentRefreshIndex,
2505
+ resolvedAuctionId,
2506
+ bidTrace.creativeId
2507
+ );
2508
+ }
2509
+ this.enqueue(TraceEventType.IMPRESSION, "impression", {
2510
+ auctionId: resolvedAuctionId,
2511
+ transactionId: resolvedTransactionId,
2512
+ adUnitCode,
2513
+ bid: bidTrace,
2514
+ metadata: { refresh_index: String(currentRefreshIndex) }
2515
+ });
2516
+ }
1701
2517
  if (this.config.viewabilityEnabled && typeof document !== "undefined") {
1702
2518
  let el = null;
1703
2519
  const targetId = adUnitCode || data.adId || bid.adId;
@@ -1718,15 +2534,20 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1718
2534
  }
1719
2535
  if (!el && targetId) {
1720
2536
  try {
1721
- 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
+ }
1722
2543
  } catch {
1723
2544
  }
1724
2545
  }
1725
2546
  if (el) {
1726
2547
  this.observeSlot(el, adUnitCode || targetId, {
1727
2548
  adUnitCode,
1728
- auctionId,
1729
- transactionId,
2549
+ auctionId: resolvedAuctionId,
2550
+ transactionId: resolvedTransactionId,
1730
2551
  mediaType,
1731
2552
  bid: bidTrace,
1732
2553
  refreshIndex: currentRefreshIndex,
@@ -1772,6 +2593,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1772
2593
  if (data.viewableDurationMs !== void 0) {
1773
2594
  protoEvent.viewableDurationMs = Number.isFinite(data.viewableDurationMs) ? data.viewableDurationMs : 0;
1774
2595
  }
2596
+ if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2597
+ protoEvent.bidderOutcomes = data.bidderOutcomes;
2598
+ }
1775
2599
  const metadata = { ...data.metadata };
1776
2600
  if (data.error !== void 0 && data.error !== null) {
1777
2601
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
@@ -1782,8 +2606,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1782
2606
  if (data.refreshIndex !== void 0 && metadata.refresh_index === void 0) {
1783
2607
  metadata.refresh_index = String(data.refreshIndex);
1784
2608
  }
1785
- if (Object.keys(metadata).length > 0) {
1786
- protoEvent.metadata = metadata;
2609
+ const boundedMetadata = boundMetadata(metadata);
2610
+ if (Object.keys(boundedMetadata).length > 0) {
2611
+ protoEvent.metadata = boundedMetadata;
1787
2612
  }
1788
2613
  this.queue.push(protoEvent);
1789
2614
  if (this.queue.length > MAX_QUEUE_SIZE) {
@@ -1821,7 +2646,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1821
2646
  // Shared by flush() and flushBeacon().
1822
2647
  drainBatch() {
1823
2648
  if (this.queue.length === 0 || !this.config.endpoint) return null;
1824
- let events = this.queue;
1825
2649
  const domain = typeof window !== "undefined" ? window.location.hostname : "";
1826
2650
  const buildBatch = (evts) => ({
1827
2651
  propertyId: this.config.propertyId,
@@ -1835,25 +2659,37 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1835
2659
  domain,
1836
2660
  events: evts
1837
2661
  });
1838
- let encoded = TraceEventBatch.encode(
1839
- buildBatch(events)
1840
- ).finish();
1841
- while (events.length > 1 && encoded.byteLength > MAX_PAYLOAD_BYTES) {
1842
- events = events.slice(0, Math.floor(events.length / 2));
1843
- encoded = TraceEventBatch.encode(
2662
+ for (; ; ) {
2663
+ if (this.queue.length === 0) return null;
2664
+ let events = this.queue;
2665
+ let encoded = TraceEventBatch.encode(
1844
2666
  buildBatch(events)
1845
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
+ };
1846
2690
  }
1847
- this.queue = this.queue.slice(events.length);
1848
- return {
1849
- url: `${this.config.endpoint}/${this.config.propertyId}`,
1850
- // protobufjs types finish() as Uint8Array<ArrayBufferLike>; the buffer is
1851
- // always a plain ArrayBuffer, so narrow for fetch/Blob compatibility.
1852
- encoded,
1853
- events
1854
- };
1855
2691
  }
1856
- sendFetch(payload, useKeepalive = false, onDelivered) {
2692
+ sendFetch(payload, useKeepalive = false, onDelivered, isReplay = false) {
1857
2693
  const fetchOpts = {
1858
2694
  method: "POST",
1859
2695
  // Safelisted content type: no CORS preflight (see SAFE_CONTENT_TYPE).
@@ -1870,15 +2706,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1870
2706
  this.log("WARN", `Dropping batch due to non-retryable client error HTTP ${res.status}`);
1871
2707
  return;
1872
2708
  }
1873
- this.handleSendFailure(payload.events);
2709
+ if (!isReplay) this.handleSendFailure(payload.events);
1874
2710
  } else {
1875
- this.consecutiveSendFailures = 0;
1876
- this.nextSendAllowedAt = 0;
2711
+ if (!isReplay) {
2712
+ this.consecutiveSendFailures = 0;
2713
+ this.nextSendAllowedAt = 0;
2714
+ }
1877
2715
  if (onDelivered) onDelivered();
1878
2716
  }
1879
2717
  }).catch((err) => {
1880
2718
  this.log("ERROR", "Failed to send batch", err);
1881
- this.handleSendFailure(payload.events);
2719
+ if (!isReplay) this.handleSendFailure(payload.events);
1882
2720
  });
1883
2721
  const proc = typeof globalThis !== "undefined" ? globalThis.process : void 0;
1884
2722
  if (proc && typeof proc._tickCallback === "function") {
@@ -1934,16 +2772,23 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1934
2772
  this.schedulePersistedCleanup();
1935
2773
  }
1936
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
+ }
1937
2781
  persistBatch(encoded) {
1938
2782
  try {
1939
2783
  if (typeof localStorage === "undefined") return null;
2784
+ if (!this.config.propertyId) return null;
1940
2785
  let pendingCount = 0;
1941
2786
  for (let i = 0; i < localStorage.length; i++) {
1942
2787
  const k = localStorage.key(i);
1943
2788
  if (k && k.startsWith(PENDING_BATCH_KEY_PREFIX)) pendingCount++;
1944
2789
  }
1945
2790
  if (pendingCount >= MAX_PENDING_BATCHES) return null;
1946
- 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)}`;
1947
2792
  localStorage.setItem(key, bytesToBase64(encoded));
1948
2793
  this.persistedBatchKeys.push(key);
1949
2794
  return key;
@@ -1968,22 +2813,39 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1968
2813
  if (this.persistedCleanupTimer) clearTimeout(this.persistedCleanupTimer);
1969
2814
  this.persistedCleanupTimer = setTimeout(() => {
1970
2815
  this.persistedCleanupTimer = null;
1971
- for (const key of [...this.persistedBatchKeys]) {
2816
+ for (const key of Array.from(this.persistedBatchKeys)) {
1972
2817
  this.removePersistedBatch(key);
1973
2818
  }
1974
2819
  }, PENDING_BATCH_CLEANUP_DELAY_MS);
1975
2820
  }
1976
2821
  // Resend batches a previous pageview persisted at exit but could not
1977
- // confirm. Keys are claimed (removed) before sending so concurrent tabs
1978
- // don't double-send; the batch bytes carry their original pageview, session,
1979
- // 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.
1980
2828
  resendPersistedBatches() {
1981
2829
  try {
1982
2830
  if (typeof localStorage === "undefined" || !this.config.endpoint) return;
2831
+ if (!this.config.propertyId) return;
2832
+ const ownPrefix = this.pendingKeyPrefix();
1983
2833
  const keys = [];
2834
+ const legacyKeys = [];
1984
2835
  for (let i = 0; i < localStorage.length; i++) {
1985
2836
  const k = localStorage.key(i);
1986
- 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
+ }
1987
2849
  }
1988
2850
  for (const key of keys) {
1989
2851
  let value = null;
@@ -1994,7 +2856,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1994
2856
  continue;
1995
2857
  }
1996
2858
  if (!value) continue;
1997
- const ts = parseInt(key.slice(PENDING_BATCH_KEY_PREFIX.length), 10);
2859
+ const ts = parseInt(key.slice(ownPrefix.length), 10);
1998
2860
  if (!Number.isFinite(ts) || Date.now() - ts > PENDING_BATCH_MAX_AGE_MS) continue;
1999
2861
  let encoded;
2000
2862
  try {
@@ -2004,11 +2866,16 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2004
2866
  }
2005
2867
  if (encoded.byteLength === 0) continue;
2006
2868
  this.log("DEBUG", `Resending persisted exit batch (${encoded.byteLength} bytes)`);
2007
- this.sendFetch({
2008
- url: `${this.config.endpoint}/${this.config.propertyId}`,
2009
- encoded,
2010
- events: []
2011
- });
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
+ );
2012
2879
  }
2013
2880
  } catch {
2014
2881
  }
@@ -2033,11 +2900,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2033
2900
  }
2034
2901
  }
2035
2902
  };
2036
- function extractAnalyticsOptions(config) {
2903
+ function extractAnalyticsOptions(config, allowProviderless = true) {
2037
2904
  if (!config) return null;
2038
2905
  if (Array.isArray(config)) {
2039
- const matched = config.find((item) => item?.provider === "bidkernel") ?? config.find((item) => item && typeof item === "object" && item.provider === void 0);
2040
- 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;
2041
2910
  }
2042
2911
  if (typeof config === "object") {
2043
2912
  if (config.options && typeof config.options === "object") {
@@ -2079,18 +2948,27 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2079
2948
  win[pbjsGlobalName] = win[pbjsGlobalName] || {};
2080
2949
  const pbjs = win[pbjsGlobalName];
2081
2950
  const enableInstance = (options) => {
2082
- 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) {
2083
2965
  try {
2084
- win._bidkernelPrebidAnalytics.disable();
2966
+ previous.disable();
2085
2967
  } catch (e) {
2086
2968
  console.warn("[bidkernel] Failed to disable previous analytics instance:", e);
2087
2969
  }
2088
2970
  }
2089
- const analytics = new BidkernelPrebidAnalytics({
2090
- pbjsGlobalName,
2091
- ...defaults,
2092
- ...options
2093
- });
2971
+ const analytics = new BidkernelPrebidAnalytics(merged);
2094
2972
  analytics.enable();
2095
2973
  win._bidkernelPrebidAnalytics = analytics;
2096
2974
  return analytics;
@@ -2101,7 +2979,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
2101
2979
  pbjs.adapterManager.registerAnalyticsAdapter({
2102
2980
  adapter: {
2103
2981
  enableAnalytics: (config) => {
2104
- const options = extractAnalyticsOptions(config);
2982
+ const options = extractAnalyticsOptions(config, !Array.isArray(config));
2105
2983
  if (options) {
2106
2984
  enableInstance(options);
2107
2985
  }
@@ -2138,12 +3016,34 @@ function getbidkernel(alias = "bidkernel") {
2138
3016
  }
2139
3017
  const win = window;
2140
3018
  win[alias] = win[alias] || { q: [] };
2141
- return win[alias];
3019
+ const sdk = win[alias];
3020
+ if (!sdk.attachVideoPlayer) {
3021
+ sdk.attachVideoPlayer = (target, options) => {
3022
+ const inst = win._bidkernelPrebidAnalytics;
3023
+ return inst?.attachVideoPlayer ? inst.attachVideoPlayer(target, options) : () => {
3024
+ };
3025
+ };
3026
+ }
3027
+ if (!sdk.attachImaAdsManager) {
3028
+ sdk.attachImaAdsManager = (adsManager, options) => {
3029
+ const inst = win._bidkernelPrebidAnalytics;
3030
+ return inst?.attachImaAdsManager ? inst.attachImaAdsManager(adsManager, options) : () => {
3031
+ };
3032
+ };
3033
+ }
3034
+ if (!sdk.recordImpression) {
3035
+ sdk.recordImpression = (slotId, options) => {
3036
+ const inst = win._bidkernelPrebidAnalytics;
3037
+ return inst?.recordImpression ? inst.recordImpression(slotId, options) : false;
3038
+ };
3039
+ }
3040
+ return sdk;
2142
3041
  }
2143
3042
  export {
2144
3043
  BidkernelPrebidAnalytics,
2145
3044
  PrebidEventDeduper,
2146
3045
  getPrebidEventKey,
2147
3046
  getbidkernel,
3047
+ isTrustedEndpoint,
2148
3048
  registerPrebidAnalytics
2149
3049
  };