@bidkernel/analytics 0.23.0 → 0.26.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
@@ -1277,11 +1277,26 @@ function parseBidDimensions(bid) {
1277
1277
  height: Number.isFinite(bid?.height) ? bid.height : 0
1278
1278
  };
1279
1279
  }
1280
+ var defaultLogger = createLogger({
1281
+ prefix: "[analytics]",
1282
+ logLevel: "INFO"
1283
+ });
1280
1284
  var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1281
1285
  static activeInstances = /* @__PURE__ */ new Set();
1286
+ static allInstances = /* @__PURE__ */ new Set();
1282
1287
  static getActiveInstances() {
1283
1288
  return _BidkernelPrebidAnalytics.activeInstances;
1284
1289
  }
1290
+ static setLogLevel(level) {
1291
+ const norm = normalizeLogLevel(level);
1292
+ defaultLogger.setLevel(norm);
1293
+ for (const inst of _BidkernelPrebidAnalytics.allInstances) {
1294
+ inst.setLogLevel(norm);
1295
+ }
1296
+ }
1297
+ static getLogLevel() {
1298
+ return defaultLogger.getLevel();
1299
+ }
1285
1300
  /**
1286
1301
  * Allows or forbids localStorage writes for every instance on the page.
1287
1302
  * Collection and delivery are unaffected: only the persisted session id,
@@ -1320,6 +1335,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1320
1335
  slotViewabilityRecords = /* @__PURE__ */ new Map();
1321
1336
  elementToSlotId = /* @__PURE__ */ new Map();
1322
1337
  slotRefreshIndices = /* @__PURE__ */ new Map();
1338
+ slotAdSessionCounters = /* @__PURE__ */ new Map();
1323
1339
  slotLastAuctionIds = /* @__PURE__ */ new Map();
1324
1340
  pendingThresholdListeners = /* @__PURE__ */ new Map();
1325
1341
  pendingViewableListeners = /* @__PURE__ */ new Map();
@@ -1363,13 +1379,14 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1363
1379
  errorsEnabled: config.errorsEnabled ?? true,
1364
1380
  viewabilityEnabled: config.viewabilityEnabled ?? true,
1365
1381
  storageAllowed: config.storageAllowed ?? true,
1366
- logLevel: config.logLevel || "INFO",
1382
+ logLevel: config.logLevel ? normalizeLogLevel(config.logLevel) : defaultLogger.getLevel(),
1367
1383
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
1368
1384
  attachPbjsListeners: config.attachPbjsListeners ?? true,
1369
1385
  revenueFlushDelayMs: Number.isFinite(config.revenueFlushDelayMs) && config.revenueFlushDelayMs >= 0 ? config.revenueFlushDelayMs : REVENUE_FLUSH_DELAY_MS
1370
1386
  };
1387
+ _BidkernelPrebidAnalytics.allInstances.add(this);
1371
1388
  this.logger = createLogger({
1372
- prefix: "[bidkernel][prebid-analytics]",
1389
+ prefix: "[analytics]",
1373
1390
  logLevel: this.config.logLevel
1374
1391
  });
1375
1392
  recordActivity();
@@ -1387,6 +1404,15 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1387
1404
  };
1388
1405
  this.boundVisibilityChange = () => this.handleVisibilityChange();
1389
1406
  }
1407
+ setLogLevel(level) {
1408
+ const norm = normalizeLogLevel(level);
1409
+ this.config.logLevel = norm;
1410
+ this.logger.setLevel(norm);
1411
+ defaultLogger.setLevel(norm);
1412
+ }
1413
+ getLogLevel() {
1414
+ return this.logger.getLevel();
1415
+ }
1390
1416
  enable() {
1391
1417
  if (this.isEnabled) return;
1392
1418
  this.isEnabled = true;
@@ -2023,6 +2049,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2023
2049
  this.unobserveSlot(slotId);
2024
2050
  this.slotViewabilityRecords.delete(slotId);
2025
2051
  this.slotRefreshIndices.delete(slotId);
2052
+ this.slotAdSessionCounters.delete(slotId);
2026
2053
  this.slotLastAuctionIds.delete(slotId);
2027
2054
  this.pendingThresholdListeners.delete(slotId);
2028
2055
  this.pendingViewableListeners.delete(slotId);
@@ -2100,9 +2127,41 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2100
2127
  }
2101
2128
  return auctionMap;
2102
2129
  }
2103
- hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
2130
+ nextSlotAdSession(slotId) {
2131
+ const next = (this.slotAdSessionCounters.get(slotId) ?? 0) + 1;
2132
+ this.slotAdSessionCounters.set(slotId, next);
2133
+ return `ad-session-${next}`;
2134
+ }
2135
+ hasImpressionEmitted(slotId, refreshIndex, auctionId, creativeId, adId) {
2104
2136
  const rIndex = refreshIndex ?? this.slotRefreshIndices.get(slotId) ?? 0;
2105
2137
  const baseSlotKey = `${escapeKeyPart(slotId)}:${rIndex}`;
2138
+ if (adId) {
2139
+ if (auctionId) {
2140
+ if (this.slotEmittedImpressionKeys.has(
2141
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:ad:${escapeKeyPart(adId)}`
2142
+ ) || this.slotEmittedImpressionKeys.has(`${baseSlotKey}:ad:${escapeKeyPart(adId)}`) || // Compat: pre-0.26.0 adapters stored keys without :ad: prefix; release once all in-flight keys expire.
2143
+ this.slotEmittedImpressionKeys.has(
2144
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(adId)}`
2145
+ ) || this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(adId)}`)) {
2146
+ return true;
2147
+ }
2148
+ } else if (this.slotEmittedImpressionKeys.has(`${baseSlotKey}:ad:${escapeKeyPart(adId)}`) || // Compat: pre-0.26.0 adapters stored keys without :ad: prefix; release once all in-flight keys expire.
2149
+ this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(adId)}`)) {
2150
+ return true;
2151
+ }
2152
+ if (creativeId && creativeId !== adId) {
2153
+ if (auctionId) {
2154
+ if (this.slotEmittedImpressionKeys.has(
2155
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
2156
+ ) || this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`)) {
2157
+ return true;
2158
+ }
2159
+ } else if (this.slotEmittedImpressionKeys.has(`${baseSlotKey}:${escapeKeyPart(creativeId)}`)) {
2160
+ return true;
2161
+ }
2162
+ }
2163
+ return false;
2164
+ }
2106
2165
  if (creativeId) {
2107
2166
  if (auctionId) {
2108
2167
  return this.slotEmittedImpressionKeys.has(
@@ -2116,18 +2175,37 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2116
2175
  }
2117
2176
  return this.slotEmittedImpressionKeys.has(baseSlotKey);
2118
2177
  }
2119
- markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId) {
2178
+ markImpressionEmitted(slotId, refreshIndex, auctionId, creativeId, adId) {
2120
2179
  const baseSlotKey = `${escapeKeyPart(slotId)}:${refreshIndex}`;
2121
- this.addImpressionKey(baseSlotKey);
2122
- if (creativeId) {
2123
- this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
2124
- }
2125
- if (auctionId) {
2126
- this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
2127
- if (creativeId) {
2128
- this.addImpressionKey(
2129
- `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
2130
- );
2180
+ const hasIdentity = Boolean(adId || creativeId);
2181
+ if (hasIdentity) {
2182
+ if (adId) {
2183
+ this.addImpressionKey(`${baseSlotKey}:ad:${escapeKeyPart(adId)}`);
2184
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(adId)}`);
2185
+ if (auctionId) {
2186
+ this.addImpressionKey(
2187
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:ad:${escapeKeyPart(adId)}`
2188
+ );
2189
+ this.addImpressionKey(
2190
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(adId)}`
2191
+ );
2192
+ }
2193
+ }
2194
+ if (creativeId && creativeId !== adId) {
2195
+ this.addImpressionKey(`${baseSlotKey}:${escapeKeyPart(creativeId)}`);
2196
+ if (auctionId) {
2197
+ this.addImpressionKey(
2198
+ `${baseSlotKey}:auc:${escapeKeyPart(auctionId)}:${escapeKeyPart(creativeId)}`
2199
+ );
2200
+ }
2201
+ }
2202
+ if (auctionId) {
2203
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
2204
+ }
2205
+ } else {
2206
+ this.addImpressionKey(baseSlotKey);
2207
+ if (auctionId) {
2208
+ this.addImpressionKey(`${baseSlotKey}:auc:${escapeKeyPart(auctionId)}`);
2131
2209
  }
2132
2210
  }
2133
2211
  }
@@ -2140,6 +2218,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2140
2218
  const auctionId = options?.auctionId || rawBid.auctionId || cached?.auctionId || "";
2141
2219
  const transactionId = options?.transactionId || rawBid.transactionId || cached?.transactionId || "";
2142
2220
  const creativeId = rawBid.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || "";
2221
+ const adId = options?.adId || rawBid.adId || options?.bid?.adId || "";
2143
2222
  const mediaType = options?.mediaType || rawBid.mediaType || cached?.bidTrace?.mediaType || "banner";
2144
2223
  const targetSlot = resolvedSlotId || adUnitCode;
2145
2224
  const rec = (resolvedSlotId ? this.slotViewabilityRecords.get(resolvedSlotId) : void 0) || (adUnitCode ? this.slotViewabilityRecords.get(adUnitCode) : void 0);
@@ -2180,7 +2259,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2180
2259
  }
2181
2260
  }
2182
2261
  const currentRefreshIndex = options?.refreshIndex ?? (resolvedSlotId ? this.slotRefreshIndices.get(resolvedSlotId) ?? (adUnitCode ? this.slotRefreshIndices.get(adUnitCode) : void 0) ?? (rec ? rec.refreshIndex : 0) : 0);
2183
- const alreadyEmitted = resolvedSlotId && this.hasImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId) || adUnitCode && adUnitCode !== resolvedSlotId && this.hasImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
2262
+ const alreadyEmitted = resolvedSlotId && this.hasImpressionEmitted(
2263
+ resolvedSlotId,
2264
+ currentRefreshIndex,
2265
+ auctionId,
2266
+ creativeId,
2267
+ adId
2268
+ ) || adUnitCode && adUnitCode !== resolvedSlotId && this.hasImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId, adId);
2184
2269
  if (alreadyEmitted) {
2185
2270
  this.log(
2186
2271
  "DEBUG",
@@ -2189,9 +2274,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2189
2274
  return false;
2190
2275
  }
2191
2276
  if (resolvedSlotId)
2192
- this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId);
2277
+ this.markImpressionEmitted(resolvedSlotId, currentRefreshIndex, auctionId, creativeId, adId);
2193
2278
  if (adUnitCode && adUnitCode !== resolvedSlotId)
2194
- this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId);
2279
+ this.markImpressionEmitted(adUnitCode, currentRefreshIndex, auctionId, creativeId, adId);
2195
2280
  this.enqueue(TraceEventType.IMPRESSION, "impression", {
2196
2281
  auctionId,
2197
2282
  transactionId,
@@ -2330,6 +2415,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2330
2415
  navigate(pageviewId, pageUrl) {
2331
2416
  this.disable();
2332
2417
  this.slotRefreshIndices.clear();
2418
+ this.slotAdSessionCounters.clear();
2333
2419
  this.floorDecisions.clear();
2334
2420
  this.pendingThresholdListeners.clear();
2335
2421
  this.config.pageviewId = pageviewId || generateUUID();
@@ -3033,10 +3119,6 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
3033
3119
  this.logger.log(level, msg, ...args);
3034
3120
  }
3035
3121
  };
3036
- var defaultLogger = createLogger({
3037
- prefix: "[bidkernel]",
3038
- logLevel: "INFO"
3039
- });
3040
3122
  function extractAnalyticsOptions(config, allowProviderless = true) {
3041
3123
  if (!config) return null;
3042
3124
  if (Array.isArray(config)) {
@@ -3559,9 +3641,29 @@ function attachImaAdsManager(analytics, adsManager, options) {
3559
3641
  return cached;
3560
3642
  };
3561
3643
  let adStarted = false;
3644
+ let activeAdSession = null;
3645
+ const mintAdSession = (initialAd) => {
3646
+ const nextSession = typeof self.nextSlotAdSession === "function" ? self.nextSlotAdSession(slotId) : `ad-session-${(self.slotAdSessionCounters?.get(slotId) ?? 0) + 1}`;
3647
+ if (self.slotAdSessionCounters && typeof self.nextSlotAdSession !== "function") {
3648
+ const c = (self.slotAdSessionCounters.get(slotId) ?? 0) + 1;
3649
+ self.slotAdSessionCounters.set(slotId, c);
3650
+ }
3651
+ const adId = (typeof initialAd?.getAdId === "function" ? initialAd.getAdId() : initialAd?.adId ?? initialAd?.id) || "";
3652
+ const creativeId = (typeof initialAd?.getCreativeId === "function" ? initialAd.getCreativeId() : initialAd?.creativeId) || "";
3653
+ activeAdSession = {
3654
+ sessionId: nextSession,
3655
+ adId: adId || void 0,
3656
+ creativeId: creativeId || void 0
3657
+ };
3658
+ return activeAdSession;
3659
+ };
3660
+ const onAdLoaded = (event) => {
3661
+ const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
3662
+ mintAdSession(ad);
3663
+ self.log("DEBUG", "IMA AdEvent.LOADED received", { slotId, activeAdSession });
3664
+ };
3562
3665
  const onAdStartedOrImpression = (event) => {
3563
3666
  adStarted = true;
3564
- self.log("DEBUG", "IMA AdEvent.STARTED / IMPRESSION received", event);
3565
3667
  const ad = typeof event?.getAd === "function" ? event.getAd() : event?.ad;
3566
3668
  const adData = {};
3567
3669
  if (ad) {
@@ -3571,15 +3673,28 @@ function attachImaAdsManager(analytics, adsManager, options) {
3571
3673
  adData.duration = typeof ad.getDuration === "function" ? ad.getDuration() : ad.duration;
3572
3674
  adData.advertiserName = typeof ad.getAdvertiserName === "function" ? ad.getAdvertiserName() : "";
3573
3675
  }
3676
+ if (!activeAdSession || adData.adId && activeAdSession.adId && activeAdSession.adId !== adData.adId) {
3677
+ mintAdSession(ad);
3678
+ } else {
3679
+ if (!activeAdSession.adId && adData.adId) {
3680
+ activeAdSession.adId = adData.adId;
3681
+ }
3682
+ if (!activeAdSession.creativeId && adData.creativeId) {
3683
+ activeAdSession.creativeId = adData.creativeId;
3684
+ }
3685
+ }
3686
+ const session = activeAdSession || mintAdSession(ad);
3574
3687
  const cached = getWinningBid(adData);
3575
3688
  const resolvedSlotId = options?.slotId && options.slotId !== "video" ? options.slotId : options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || options?.slotId || options?.adUnitCode || "video";
3576
3689
  const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || resolvedSlotId;
3577
3690
  const resolvedAuctionId = auctionId || cached?.auctionId || "";
3578
3691
  const resolvedTransactionId = transactionId || cached?.transactionId || "";
3692
+ const creativeId = session.creativeId || adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || "";
3693
+ const stableAdIdentity = session.adId || adData.adId || creativeId || session.sessionId;
3579
3694
  const mergedBid = {
3580
3695
  ...cached?.rawBid || options?.bid,
3581
3696
  mediaType: "video",
3582
- creativeId: adData.creativeId || cached?.bidTrace?.creativeId || options?.bid?.creativeId || ""
3697
+ creativeId
3583
3698
  };
3584
3699
  const emitted = self.recordImpression(resolvedSlotId, {
3585
3700
  adUnitCode: resolvedAdUnitCode,
@@ -3587,6 +3702,7 @@ function attachImaAdsManager(analytics, adsManager, options) {
3587
3702
  transactionId: resolvedTransactionId,
3588
3703
  mediaType: "video",
3589
3704
  bid: mergedBid,
3705
+ adId: stableAdIdentity,
3590
3706
  metadata: {
3591
3707
  ...options?.metadata,
3592
3708
  media_type: "video",
@@ -3594,6 +3710,16 @@ function attachImaAdsManager(analytics, adsManager, options) {
3594
3710
  ...Number.isFinite(adData.duration) ? { ad_duration_s: String(adData.duration) } : {}
3595
3711
  }
3596
3712
  });
3713
+ let rawType = typeof event?.type === "string" ? event.type : typeof event?.getType === "function" ? event.getType() : typeof event === "string" ? event : "";
3714
+ if (rawType.toLowerCase().startsWith("adevent.")) {
3715
+ rawType = rawType.slice(8);
3716
+ }
3717
+ const eventName = rawType ? rawType.toUpperCase() : "STARTED/IMPRESSION";
3718
+ self.log(
3719
+ "DEBUG",
3720
+ `IMA AdEvent.${eventName} received${emitted ? "" : " (second event for this ad, already counted)"}`,
3721
+ event
3722
+ );
3597
3723
  if (emitted && options?.onImpression) {
3598
3724
  try {
3599
3725
  options.onImpression(resolvedSlotId, { ad, bid: mergedBid });
@@ -3628,6 +3754,7 @@ function attachImaAdsManager(analytics, adsManager, options) {
3628
3754
  });
3629
3755
  };
3630
3756
  const onAdError = (event) => {
3757
+ activeAdSession = null;
3631
3758
  const cached = getWinningBid();
3632
3759
  const resolvedAdUnitCode = options?.adUnitCode && options.adUnitCode !== "video" ? options.adUnitCode : cached?.adUnitCode || slotId;
3633
3760
  const err = recordImaAdError(analytics, event, {
@@ -3650,6 +3777,7 @@ function attachImaAdsManager(analytics, adsManager, options) {
3650
3777
  const adEventType = googleIma?.AdEvent?.Type || {};
3651
3778
  const adErrorEventType = googleIma?.AdErrorEvent?.Type || {};
3652
3779
  const rawListeners = [
3780
+ { type: adEventType.LOADED || "loaded", handler: onAdLoaded },
3653
3781
  { type: adEventType.STARTED || "started", handler: onAdStartedOrImpression },
3654
3782
  { type: adEventType.IMPRESSION || "impression", handler: onAdStartedOrImpression },
3655
3783
  {
@@ -3661,7 +3789,19 @@ function attachImaAdsManager(analytics, adsManager, options) {
3661
3789
  type: adEventType.THIRD_QUARTILE || "thirdQuartile",
3662
3790
  handler: () => onMilestone("thirdQuartile")
3663
3791
  },
3664
- { type: adEventType.COMPLETE || "complete", handler: () => onMilestone("complete") },
3792
+ {
3793
+ type: adEventType.COMPLETE || "complete",
3794
+ handler: () => {
3795
+ activeAdSession = null;
3796
+ onMilestone("complete");
3797
+ }
3798
+ },
3799
+ {
3800
+ type: adEventType.ALL_ADS_COMPLETED || "allAdsCompleted",
3801
+ handler: () => {
3802
+ activeAdSession = null;
3803
+ }
3804
+ },
3665
3805
  { type: adEventType.CLICK || "click", handler: onAdClick },
3666
3806
  { type: adErrorEventType.AD_ERROR || "adError", handler: onAdError }
3667
3807
  ];
@@ -3680,6 +3820,7 @@ function attachImaAdsManager(analytics, adsManager, options) {
3680
3820
  }
3681
3821
  }
3682
3822
  const cleanup = () => {
3823
+ activeAdSession = null;
3683
3824
  attached.delete(analytics);
3684
3825
  for (const { type, handler } of listeners) {
3685
3826
  try {
@@ -3946,6 +4087,8 @@ function normalizeImaEventName(name) {
3946
4087
  return "volumeChanged";
3947
4088
  case "loaded":
3948
4089
  return "loaded";
4090
+ case "alladscompleted":
4091
+ return "allAdsCompleted";
3949
4092
  case "aderror":
3950
4093
  case "error":
3951
4094
  return "adError";
@@ -4060,6 +4203,7 @@ export {
4060
4203
  BidkernelPrebidAnalytics,
4061
4204
  CACHED_BID_TTL_MS,
4062
4205
  ImaAdsManagerStub,
4206
+ LogLevel,
4063
4207
  PrebidEventDeduper,
4064
4208
  attachImaAdsManager,
4065
4209
  attachVideoPlayer,
@@ -4071,6 +4215,7 @@ export {
4071
4215
  isSessionActive,
4072
4216
  isTrustedEndpoint,
4073
4217
  normalizeImaEventName,
4218
+ normalizeLogLevel,
4074
4219
  onSessionResume,
4075
4220
  registerPrebidAnalytics
4076
4221
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bidkernel/analytics",
3
- "version": "0.23.0",
3
+ "version": "0.26.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",