@bidkernel/analytics 0.11.0 → 0.16.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
@@ -1042,7 +1042,8 @@ var EVENT_NAME_TO_TYPE = {
1042
1042
  timeInView: TraceEventType.TIME_IN_VIEW,
1043
1043
  viewable: TraceEventType.VIEWABLE
1044
1044
  };
1045
- var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
1045
+ var REVENUE_FLUSH_DELAY_MS = 3e3;
1046
+ var REVENUE_FLUSH_TYPES = /* @__PURE__ */ new Set([
1046
1047
  TraceEventType.IMPRESSION,
1047
1048
  TraceEventType.BID_WIN,
1048
1049
  TraceEventType.CLICK
@@ -1051,11 +1052,13 @@ var CACHED_BID_TTL_MS = 30 * 60 * 1e3;
1051
1052
  var MAX_CACHED_BID_KEYS = 200;
1052
1053
  var MAX_IMPRESSION_KEYS = 1e3;
1053
1054
  var MAX_TRACKED_AUCTIONS = 50;
1055
+ var CLICK_DEDUP_MS = 1e3;
1054
1056
  var SESSION_KEY = "_bidkernel_session";
1055
1057
  var SESSION_TS_KEY = "_bidkernel_session_ts";
1056
1058
  var THIRTY_MINUTES_MS = 30 * 60 * 1e3;
1057
1059
  var inMemorySessionId = null;
1058
1060
  var inMemorySessionTs = 0;
1061
+ var storageAllowed = true;
1059
1062
  function generateUUID() {
1060
1063
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
1061
1064
  return crypto.randomUUID();
@@ -1125,6 +1128,7 @@ function sameEndpointOrigin(a, b) {
1125
1128
  function extendSession() {
1126
1129
  const now = Date.now();
1127
1130
  inMemorySessionTs = now;
1131
+ if (!storageAllowed) return;
1128
1132
  try {
1129
1133
  if (typeof localStorage !== "undefined") {
1130
1134
  localStorage.setItem(SESSION_TS_KEY, now.toString());
@@ -1134,18 +1138,23 @@ function extendSession() {
1134
1138
  }
1135
1139
  function getOrCreateSessionId() {
1136
1140
  const now = Date.now();
1141
+ const memoryFresh = !!inMemorySessionId && !!inMemorySessionTs && now - inMemorySessionTs <= THIRTY_MINUTES_MS;
1137
1142
  try {
1138
- if (typeof localStorage !== "undefined") {
1143
+ if (storageAllowed && typeof localStorage !== "undefined") {
1139
1144
  const storedId = localStorage.getItem(SESSION_KEY);
1140
1145
  const tsStr = localStorage.getItem(SESSION_TS_KEY);
1141
1146
  const storedTs = tsStr ? parseInt(tsStr, 10) || 0 : 0;
1142
1147
  if (storedId && storedTs && now - storedTs <= THIRTY_MINUTES_MS) {
1143
1148
  localStorage.setItem(SESSION_TS_KEY, now.toString());
1149
+ inMemorySessionId = storedId;
1150
+ inMemorySessionTs = now;
1144
1151
  return storedId;
1145
1152
  }
1146
- const newId = generateUUID();
1153
+ const newId = memoryFresh ? inMemorySessionId : generateUUID();
1147
1154
  localStorage.setItem(SESSION_KEY, newId);
1148
1155
  localStorage.setItem(SESSION_TS_KEY, now.toString());
1156
+ inMemorySessionId = newId;
1157
+ inMemorySessionTs = now;
1149
1158
  return newId;
1150
1159
  }
1151
1160
  } catch {
@@ -1511,6 +1520,17 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1511
1520
  static initImaInterception() {
1512
1521
  initImaInterception();
1513
1522
  }
1523
+ /**
1524
+ * Allows or forbids localStorage writes for every instance on the page.
1525
+ * Collection and delivery are unaffected: only the persisted session id,
1526
+ * its timestamp and exit batches are gated.
1527
+ */
1528
+ static setStorageAllowed(allowed) {
1529
+ storageAllowed = allowed;
1530
+ }
1531
+ static isStorageAllowed() {
1532
+ return storageAllowed;
1533
+ }
1514
1534
  config;
1515
1535
  queue = [];
1516
1536
  errorCount = 0;
@@ -1526,7 +1546,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1526
1546
  consecutiveSendFailures = 0;
1527
1547
  nextSendAllowedAt = 0;
1528
1548
  replayedEventCount = 0;
1529
- immediateFlushScheduled = false;
1549
+ revenueFlushTimer = null;
1530
1550
  // localStorage keys of exit batches this instance persisted, so a page that
1531
1551
  // survives its own pagehide/hidden (bfcache restore, tab re-focus) can
1532
1552
  // remove them instead of leaving them for a duplicate resend.
@@ -1539,6 +1559,12 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1539
1559
  slotRefreshIndices = /* @__PURE__ */ new Map();
1540
1560
  slotLastAuctionIds = /* @__PURE__ */ new Map();
1541
1561
  pendingThresholdListeners = /* @__PURE__ */ new Map();
1562
+ pendingViewableListeners = /* @__PURE__ */ new Map();
1563
+ // Element a subscriber named for a slot (a sticky bar or interstitial that
1564
+ // renders outside the slot element); wins over document.getElementById.
1565
+ slotElements = /* @__PURE__ */ new Map();
1566
+ clickDetachCleanups = /* @__PURE__ */ new Set();
1567
+ lastClickAt = /* @__PURE__ */ new Map();
1542
1568
  // Impression deduplication per slot and refresh cycle (strictly 1 impression per cycle)
1543
1569
  slotEmittedImpressionKeys = /* @__PURE__ */ new Set();
1544
1570
  // Winning bid cache from bidWon to marry with subsequent render triggers (adRenderSucceeded, video, etc.)
@@ -1549,6 +1575,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1549
1575
  auctionBidderOutcomes = /* @__PURE__ */ new Map();
1550
1576
  logger;
1551
1577
  constructor(config) {
1578
+ if (config.storageAllowed !== void 0) {
1579
+ _BidkernelPrebidAnalytics.setStorageAllowed(config.storageAllowed);
1580
+ }
1552
1581
  const requestedEndpoint = config.endpoint || "";
1553
1582
  const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
1554
1583
  this.config = {
@@ -1564,9 +1593,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1564
1593
  warningsEnabled: config.warningsEnabled ?? true,
1565
1594
  errorsEnabled: config.errorsEnabled ?? true,
1566
1595
  viewabilityEnabled: config.viewabilityEnabled ?? true,
1596
+ storageAllowed: config.storageAllowed ?? true,
1567
1597
  logLevel: config.logLevel || "INFO",
1568
1598
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
1569
- attachPbjsListeners: config.attachPbjsListeners ?? true
1599
+ attachPbjsListeners: config.attachPbjsListeners ?? true,
1600
+ revenueFlushDelayMs: Number.isFinite(config.revenueFlushDelayMs) && config.revenueFlushDelayMs >= 0 ? config.revenueFlushDelayMs : REVENUE_FLUSH_DELAY_MS
1570
1601
  };
1571
1602
  this.logger = createLogger({
1572
1603
  prefix: "[bidkernel][prebid-analytics]",
@@ -1605,65 +1636,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1605
1636
  }
1606
1637
  }
1607
1638
  if (this.config.attachPbjsListeners) {
1608
- const win = typeof window !== "undefined" ? window : {};
1609
- const pbjs = win[this.config.pbjsGlobalName] || {};
1610
- if (typeof pbjs.onEvent === "function") {
1611
- this.log("DEBUG", "Attaching event listeners to pbjs");
1612
- const events = [
1613
- ["auctionInit", this.handleAuctionInit.bind(this)],
1614
- ["auctionEnd", this.handleAuctionEnd.bind(this)],
1615
- ["bidRequested", this.handleBidRequested.bind(this)],
1616
- ["bidResponse", this.handleBidResponse.bind(this)],
1617
- ["bidTimeout", this.handleBidTimeout.bind(this)],
1618
- ["bidWon", this.handleBidWon.bind(this)],
1619
- ["noBid", this.handleNoBid.bind(this)],
1620
- ["adRenderFailed", this.handleAdRenderFailed.bind(this)],
1621
- ["adRenderSucceeded", this.handleAdRenderSucceeded.bind(this)]
1622
- ];
1623
- for (const [name, fn] of events) {
1624
- pbjs.onEvent(name, fn);
1625
- this.boundPbjsHandlers.push({ event: name, handler: fn });
1626
- }
1627
- } else {
1628
- this.log("WARN", "pbjs.onEvent is not defined. Prebid analytics will not function.");
1629
- }
1630
- if (typeof pbjs.getEvents === "function") {
1631
- try {
1632
- const pastEvents = pbjs.getEvents();
1633
- if (Array.isArray(pastEvents)) {
1634
- const newPastEvents = pastEvents.slice(this.replayedEventCount);
1635
- this.replayedEventCount = pastEvents.length;
1636
- if (newPastEvents.length > 0) {
1637
- this.log(
1638
- "DEBUG",
1639
- `Replaying ${newPastEvents.length} historical events from pbjs.getEvents()`
1640
- );
1641
- const handlerMap = {
1642
- auctionInit: this.handleAuctionInit.bind(this),
1643
- auctionEnd: this.handleAuctionEnd.bind(this),
1644
- bidRequested: this.handleBidRequested.bind(this),
1645
- bidResponse: this.handleBidResponse.bind(this),
1646
- bidTimeout: this.handleBidTimeout.bind(this),
1647
- bidWon: this.handleBidWon.bind(this),
1648
- noBid: this.handleNoBid.bind(this),
1649
- adRenderFailed: this.handleAdRenderFailed.bind(this),
1650
- adRenderSucceeded: this.handleAdRenderSucceeded.bind(this)
1651
- };
1652
- for (const ev of newPastEvents) {
1653
- if (!ev) continue;
1654
- const eventType = ev.eventType || ev.event || ev.name;
1655
- const args = ev.args !== void 0 ? ev.args : ev.data !== void 0 ? ev.data : ev;
1656
- const handler = handlerMap[eventType];
1657
- if (handler) {
1658
- handler(args);
1659
- }
1660
- }
1661
- }
1662
- }
1663
- } catch (e) {
1664
- this.log("WARN", "Failed to replay historical events from pbjs.getEvents()", e);
1665
- }
1666
- }
1639
+ this.attachPbjs();
1667
1640
  }
1668
1641
  if (typeof window !== "undefined") {
1669
1642
  if (!this.pageUrl) {
@@ -1686,6 +1659,68 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1686
1659
  }
1687
1660
  }
1688
1661
  }
1662
+ /**
1663
+ * Binds the Prebid event handlers on the configured global and replays
1664
+ * pbjs.getEvents() history. enable() calls this when attachPbjsListeners is
1665
+ * true. An integration that loads Prebid lazily constructs the instance with
1666
+ * attachPbjsListeners=false and calls this once pbjs exists; disable() (and
1667
+ * therefore navigate()) drops the handlers, so call it again after either.
1668
+ * Idempotent. Returns true when handlers are bound after the call, false
1669
+ * when the instance is disabled or pbjs.onEvent is not available yet.
1670
+ */
1671
+ attachPbjs() {
1672
+ if (!this.isEnabled) return false;
1673
+ if (this.boundPbjsHandlers.length > 0) return true;
1674
+ const win = typeof window !== "undefined" ? window : {};
1675
+ const pbjs = win[this.config.pbjsGlobalName] || {};
1676
+ if (typeof pbjs.onEvent !== "function") {
1677
+ this.log("WARN", "pbjs.onEvent is not defined. Prebid analytics will not function.");
1678
+ return false;
1679
+ }
1680
+ this.log("DEBUG", "Attaching event listeners to pbjs");
1681
+ const handlerMap = {
1682
+ auctionInit: this.handleAuctionInit.bind(this),
1683
+ auctionEnd: this.handleAuctionEnd.bind(this),
1684
+ bidRequested: this.handleBidRequested.bind(this),
1685
+ bidResponse: this.handleBidResponse.bind(this),
1686
+ bidTimeout: this.handleBidTimeout.bind(this),
1687
+ bidWon: this.handleBidWon.bind(this),
1688
+ noBid: this.handleNoBid.bind(this),
1689
+ adRenderFailed: this.handleAdRenderFailed.bind(this),
1690
+ adRenderSucceeded: this.handleAdRenderSucceeded.bind(this)
1691
+ };
1692
+ for (const [name, fn] of Object.entries(handlerMap)) {
1693
+ pbjs.onEvent(name, fn);
1694
+ this.boundPbjsHandlers.push({ event: name, handler: fn });
1695
+ }
1696
+ if (typeof pbjs.getEvents === "function") {
1697
+ try {
1698
+ const pastEvents = pbjs.getEvents();
1699
+ if (Array.isArray(pastEvents)) {
1700
+ const newPastEvents = pastEvents.slice(this.replayedEventCount);
1701
+ this.replayedEventCount = pastEvents.length;
1702
+ if (newPastEvents.length > 0) {
1703
+ this.log(
1704
+ "DEBUG",
1705
+ `Replaying ${newPastEvents.length} historical events from pbjs.getEvents()`
1706
+ );
1707
+ for (const ev of newPastEvents) {
1708
+ if (!ev) continue;
1709
+ const eventType = ev.eventType || ev.event || ev.name;
1710
+ const args = ev.args !== void 0 ? ev.args : ev.data !== void 0 ? ev.data : ev;
1711
+ const handler = handlerMap[eventType];
1712
+ if (handler) {
1713
+ handler(args);
1714
+ }
1715
+ }
1716
+ }
1717
+ }
1718
+ } catch (e) {
1719
+ this.log("WARN", "Failed to replay historical events from pbjs.getEvents()", e);
1720
+ }
1721
+ }
1722
+ return true;
1723
+ }
1689
1724
  disable() {
1690
1725
  if (!this.isEnabled) return;
1691
1726
  this.flushAllSlotsTimeInView();
@@ -1717,6 +1752,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1717
1752
  }
1718
1753
  }
1719
1754
  this.videoDetachCleanups.clear();
1755
+ for (const cleanup of Array.from(this.clickDetachCleanups)) {
1756
+ try {
1757
+ cleanup();
1758
+ } catch {
1759
+ }
1760
+ }
1761
+ this.clickDetachCleanups.clear();
1762
+ this.slotElements.clear();
1720
1763
  this.cachedWinningBids.clear();
1721
1764
  this.slotEmittedImpressionKeys.clear();
1722
1765
  this.slotLastAuctionIds.clear();
@@ -1725,6 +1768,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1725
1768
  clearInterval(this.flushTimer);
1726
1769
  this.flushTimer = null;
1727
1770
  }
1771
+ if (this.revenueFlushTimer) {
1772
+ clearTimeout(this.revenueFlushTimer);
1773
+ this.revenueFlushTimer = null;
1774
+ }
1728
1775
  if (this.persistedCleanupTimer) {
1729
1776
  clearTimeout(this.persistedCleanupTimer);
1730
1777
  this.persistedCleanupTimer = null;
@@ -1874,6 +1921,105 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1874
1921
  }
1875
1922
  });
1876
1923
  }
1924
+ this.notifyViewable(record);
1925
+ }
1926
+ notifyViewable(record) {
1927
+ if (!record.viewableFired) return;
1928
+ for (const listener of record.viewableListeners) {
1929
+ if (listener.firedForIndex === record.refreshIndex) continue;
1930
+ listener.firedForIndex = record.refreshIndex;
1931
+ try {
1932
+ listener.callback(record.slotId);
1933
+ } catch (e) {
1934
+ this.log("ERROR", "Error in viewable listener callback", e);
1935
+ }
1936
+ }
1937
+ }
1938
+ /**
1939
+ * Calls back once per render cycle when the slot has met the IAB viewable
1940
+ * standard this adapter measures (immediately if it already has). Works
1941
+ * before the slot is observed: the listener waits for observeSlot. Returns
1942
+ * an unsubscribe function.
1943
+ */
1944
+ /**
1945
+ * Names the element to measure for a slot when the creative renders
1946
+ * somewhere other than the slot element (a sticky bar, an interstitial).
1947
+ * Applies to the current cycle at once and to every later render.
1948
+ */
1949
+ setSlotElement(slotId, element) {
1950
+ this.slotElements.set(slotId, element);
1951
+ const record = this.slotViewabilityRecords.get(slotId);
1952
+ if (record && record.element !== element) {
1953
+ this.observeSlot(element, slotId, {
1954
+ refreshIndex: record.refreshIndex,
1955
+ emitRefreshEvent: false
1956
+ });
1957
+ }
1958
+ }
1959
+ /**
1960
+ * Records a click on the slot's current creative, attributed to the cached
1961
+ * winning bid. Repeats within one second are one click. Returns whether a
1962
+ * CLICK was recorded.
1963
+ */
1964
+ recordClick(slotId, options) {
1965
+ if (!this.isEnabled || !slotId) return false;
1966
+ const now = Date.now();
1967
+ if (now - (this.lastClickAt.get(slotId) || 0) < CLICK_DEDUP_MS) return false;
1968
+ this.lastClickAt.set(slotId, now);
1969
+ const cached = this.getCachedBid(slotId);
1970
+ this.enqueue(TraceEventType.CLICK, "click", {
1971
+ auctionId: cached?.auctionId || "",
1972
+ transactionId: cached?.transactionId || "",
1973
+ adUnitCode: slotId,
1974
+ bid: cached?.bidTrace,
1975
+ metadata: {
1976
+ ...options?.metadata,
1977
+ refresh_index: String(this.getRefreshIndex(slotId))
1978
+ }
1979
+ });
1980
+ return true;
1981
+ }
1982
+ /**
1983
+ * Detects clicks into a cross-origin creative frame: a click inside an
1984
+ * iframe moves focus into it and blurs the window. Returns a detach
1985
+ * function; every tracker is detached on disable().
1986
+ */
1987
+ attachClickTracker(element, slotId) {
1988
+ if (typeof window === "undefined") return () => {
1989
+ };
1990
+ const onBlur = () => {
1991
+ setTimeout(() => {
1992
+ const active = document.activeElement;
1993
+ if (active && (active === element || element.contains(active))) {
1994
+ this.recordClick(slotId, { metadata: { source: "blur" } });
1995
+ window.focus();
1996
+ }
1997
+ }, 0);
1998
+ };
1999
+ window.addEventListener("blur", onBlur);
2000
+ const cleanup = () => {
2001
+ window.removeEventListener("blur", onBlur);
2002
+ this.clickDetachCleanups.delete(cleanup);
2003
+ };
2004
+ this.clickDetachCleanups.add(cleanup);
2005
+ return cleanup;
2006
+ }
2007
+ onViewable(slotId, callback) {
2008
+ const listener = { callback, firedForIndex: -1 };
2009
+ const record = this.slotViewabilityRecords.get(slotId);
2010
+ if (record) {
2011
+ record.viewableListeners.add(listener);
2012
+ this.notifyViewable(record);
2013
+ } else {
2014
+ if (!this.pendingViewableListeners.has(slotId)) {
2015
+ this.pendingViewableListeners.set(slotId, /* @__PURE__ */ new Set());
2016
+ }
2017
+ this.pendingViewableListeners.get(slotId).add(listener);
2018
+ }
2019
+ return () => {
2020
+ this.slotViewabilityRecords.get(slotId)?.viewableListeners.delete(listener);
2021
+ this.pendingViewableListeners.get(slotId)?.delete(listener);
2022
+ };
1877
2023
  }
1878
2024
  scheduleThresholdTimers(record) {
1879
2025
  if (!record.inView || typeof document !== "undefined" && document.visibilityState === "hidden") {
@@ -2063,6 +2209,8 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2063
2209
  }
2064
2210
  const listeners = this.pendingThresholdListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
2065
2211
  this.pendingThresholdListeners.delete(resolvedSlotId);
2212
+ const viewableListeners = this.pendingViewableListeners.get(resolvedSlotId) || /* @__PURE__ */ new Set();
2213
+ this.pendingViewableListeners.delete(resolvedSlotId);
2066
2214
  const record = {
2067
2215
  slotId: resolvedSlotId,
2068
2216
  element: el,
@@ -2079,7 +2227,8 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2079
2227
  viewableFired: false,
2080
2228
  dwellTimer: null,
2081
2229
  dwellStartedAt: null,
2082
- thresholdListeners: listeners
2230
+ thresholdListeners: listeners,
2231
+ viewableListeners
2083
2232
  };
2084
2233
  this.slotViewabilityRecords.set(resolvedSlotId, record);
2085
2234
  if (el) {
@@ -2104,6 +2253,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2104
2253
  }
2105
2254
  }
2106
2255
  record.thresholdListeners = /* @__PURE__ */ new Set();
2256
+ record.viewableListeners = /* @__PURE__ */ new Set();
2107
2257
  if (record.element && this.intersectionObserver) {
2108
2258
  this.intersectionObserver.unobserve(record.element);
2109
2259
  this.elementToSlotId.delete(record.element);
@@ -2118,6 +2268,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2118
2268
  this.slotRefreshIndices.delete(slotId);
2119
2269
  this.slotLastAuctionIds.delete(slotId);
2120
2270
  this.pendingThresholdListeners.delete(slotId);
2271
+ this.pendingViewableListeners.delete(slotId);
2121
2272
  const prefix = `${escapeKeyPart(slotId)}:`;
2122
2273
  for (const key of Array.from(this.slotEmittedImpressionKeys)) {
2123
2274
  if (key === slotId || key.startsWith(prefix)) {
@@ -2392,19 +2543,21 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2392
2543
  if (!adsManager.__bidkernelAttachedInstances) {
2393
2544
  try {
2394
2545
  Object.defineProperty(adsManager, "__bidkernelAttachedInstances", {
2395
- value: /* @__PURE__ */ new Set(),
2546
+ value: /* @__PURE__ */ new Map(),
2396
2547
  configurable: true,
2397
2548
  writable: true
2398
2549
  });
2399
2550
  } catch {
2400
- adsManager.__bidkernelAttachedInstances = /* @__PURE__ */ new Set();
2551
+ adsManager.__bidkernelAttachedInstances = /* @__PURE__ */ new Map();
2401
2552
  }
2402
2553
  }
2403
- if (adsManager.__bidkernelAttachedInstances.has(this)) {
2404
- return () => {
2554
+ const attached = adsManager.__bidkernelAttachedInstances;
2555
+ const previous = attached.get(this);
2556
+ if (previous) {
2557
+ if (!options) return () => {
2405
2558
  };
2559
+ previous();
2406
2560
  }
2407
- adsManager.__bidkernelAttachedInstances.add(this);
2408
2561
  const slotId = options?.slotId || options?.adUnitCode || "video";
2409
2562
  const adUnitCode = options?.adUnitCode || slotId;
2410
2563
  const auctionId = options?.auctionId || "";
@@ -2552,7 +2705,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2552
2705
  }
2553
2706
  }
2554
2707
  const cleanup = () => {
2555
- adsManager.__bidkernelAttachedInstances?.delete(this);
2708
+ attached.delete(this);
2556
2709
  for (const { type, handler } of listeners) {
2557
2710
  try {
2558
2711
  adsManager.removeEventListener(type, handler);
@@ -2561,6 +2714,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2561
2714
  }
2562
2715
  this.videoDetachCleanups.delete(cleanup);
2563
2716
  };
2717
+ attached.set(this, cleanup);
2564
2718
  this.videoDetachCleanups.add(cleanup);
2565
2719
  return cleanup;
2566
2720
  }
@@ -3188,9 +3342,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
3188
3342
  );
3189
3343
  }
3190
3344
  if (this.config.viewabilityEnabled && typeof document !== "undefined") {
3191
- let el = null;
3345
+ let el = adUnitCode && this.slotElements.get(adUnitCode) || null;
3192
3346
  const targetId = adUnitCode || data.adId || bid.adId;
3193
- if (targetId) {
3347
+ if (!el && targetId) {
3194
3348
  el = document.getElementById(targetId);
3195
3349
  }
3196
3350
  if (!el && typeof window !== "undefined" && window.googletag?.pubads) {
@@ -3289,12 +3443,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
3289
3443
  }
3290
3444
  if (this.queue.length >= BATCH_SIZE) {
3291
3445
  this.flush();
3292
- } else if (IMMEDIATE_FLUSH_TYPES.has(type) && !this.immediateFlushScheduled) {
3293
- this.immediateFlushScheduled = true;
3294
- setTimeout(() => {
3295
- this.immediateFlushScheduled = false;
3446
+ } else if (REVENUE_FLUSH_TYPES.has(type) && this.revenueFlushTimer === null) {
3447
+ this.revenueFlushTimer = setTimeout(() => {
3448
+ this.revenueFlushTimer = null;
3296
3449
  this.flush();
3297
- }, 0);
3450
+ }, this.config.revenueFlushDelayMs);
3298
3451
  }
3299
3452
  }
3300
3453
  shouldSample(type, level) {
@@ -3452,6 +3605,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
3452
3605
  return `${PENDING_BATCH_KEY_PREFIX}${encodeURIComponent(this.config.propertyId)}_`;
3453
3606
  }
3454
3607
  persistBatch(encoded) {
3608
+ if (!storageAllowed) return null;
3455
3609
  try {
3456
3610
  if (typeof localStorage === "undefined") return null;
3457
3611
  if (!this.config.propertyId) return null;
@@ -3703,6 +3857,7 @@ function getbidkernel(alias = "bidkernel") {
3703
3857
  export {
3704
3858
  BidkernelPrebidAnalytics,
3705
3859
  PrebidEventDeduper,
3860
+ getOrCreateSessionId,
3706
3861
  getPrebidEventKey,
3707
3862
  getbidkernel,
3708
3863
  hookImaPrototype,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bidkernel/analytics",
3
- "version": "0.11.0",
3
+ "version": "0.16.0",
4
4
  "description": "Bidkernel auction analytics: a standalone Prebid.js analytics adapter, plus TypeScript bindings for the Bidkernel ad SDK.",
5
5
  "keywords": [
6
6
  "advertising",