@bidkernel/analytics 0.20.0 → 0.22.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
@@ -317,6 +317,17 @@ var BidderOutcome = {
317
317
  BIDDER_OUTCOME_NO_RESPONSE: 5,
318
318
  UNRECOGNIZED: -1
319
319
  };
320
+ var FloorSource = {
321
+ /** FLOOR_SOURCE_UNSPECIFIED - module off, no decision */
322
+ FLOOR_SOURCE_UNSPECIFIED: 0,
323
+ /** FLOOR_SOURCE_STATIC - the publisher's rule, scaled by the treatment */
324
+ FLOOR_SOURCE_STATIC: 1,
325
+ /** FLOOR_SOURCE_POOL - the bid pool's standing bid plus margin */
326
+ FLOOR_SOURCE_POOL: 2,
327
+ /** FLOOR_SOURCE_NONE - module on, but neither term applied */
328
+ FLOOR_SOURCE_NONE: 3,
329
+ UNRECOGNIZED: -1
330
+ };
320
331
  function createBaseTraceEventBatch() {
321
332
  return {
322
333
  propertyId: new Uint8Array(0),
@@ -719,7 +730,15 @@ var TraceEvent_MetadataEntry = {
719
730
  }
720
731
  };
721
732
  function createBaseBidderOutcomeEntry() {
722
- return { adUnitCode: "", outcome: 0, bid: void 0, refreshIndex: 0 };
733
+ return {
734
+ adUnitCode: "",
735
+ outcome: 0,
736
+ bid: void 0,
737
+ refreshIndex: 0,
738
+ floorCents: void 0,
739
+ floorSource: 0,
740
+ floorTreatmentBp: void 0
741
+ };
723
742
  }
724
743
  var BidderOutcomeEntry = {
725
744
  encode(message, writer = new BinaryWriter()) {
@@ -735,6 +754,15 @@ var BidderOutcomeEntry = {
735
754
  if (message.refreshIndex !== void 0 && message.refreshIndex !== 0) {
736
755
  writer.uint32(56).int32(message.refreshIndex);
737
756
  }
757
+ if (message.floorCents !== void 0) {
758
+ writer.uint32(64).int32(message.floorCents);
759
+ }
760
+ if (message.floorSource !== void 0 && message.floorSource !== 0) {
761
+ writer.uint32(72).int32(message.floorSource);
762
+ }
763
+ if (message.floorTreatmentBp !== void 0) {
764
+ writer.uint32(80).int32(message.floorTreatmentBp);
765
+ }
738
766
  return writer;
739
767
  },
740
768
  decode(input, length) {
@@ -772,6 +800,27 @@ var BidderOutcomeEntry = {
772
800
  message.refreshIndex = reader.int32();
773
801
  continue;
774
802
  }
803
+ case 8: {
804
+ if (tag !== 64) {
805
+ break;
806
+ }
807
+ message.floorCents = reader.int32();
808
+ continue;
809
+ }
810
+ case 9: {
811
+ if (tag !== 72) {
812
+ break;
813
+ }
814
+ message.floorSource = reader.int32();
815
+ continue;
816
+ }
817
+ case 10: {
818
+ if (tag !== 80) {
819
+ break;
820
+ }
821
+ message.floorTreatmentBp = reader.int32();
822
+ continue;
823
+ }
775
824
  }
776
825
  if ((tag & 7) === 4 || tag === 0) {
777
826
  break;
@@ -971,6 +1020,12 @@ var CACHED_BID_TTL_MS = 30 * 60 * 1e3;
971
1020
  var MAX_CACHED_BID_KEYS = 200;
972
1021
  var MAX_IMPRESSION_KEYS = 1e3;
973
1022
  var MAX_TRACKED_AUCTIONS = 50;
1023
+ var MAX_FLOOR_DECISIONS = 200;
1024
+ var FLOOR_SOURCE_PROTO = {
1025
+ static: FloorSource.FLOOR_SOURCE_STATIC,
1026
+ pool: FloorSource.FLOOR_SOURCE_POOL,
1027
+ none: FloorSource.FLOOR_SOURCE_NONE
1028
+ };
974
1029
  var CLICK_DEDUP_MS = 1e3;
975
1030
  var SESSION_KEY = "_bidkernel_session";
976
1031
  var SESSION_TS_KEY = "_bidkernel_session_ts";
@@ -1046,49 +1101,83 @@ function sameEndpointOrigin(a, b) {
1046
1101
  }
1047
1102
  var SESSION_PERSIST_INTERVAL_MS = 60 * 1e3;
1048
1103
  var persistedSessionTs = 0;
1049
- function persistSessionTs(now) {
1104
+ var ACTIVITY_EVENTS = ["pointerdown", "pointermove", "keydown", "scroll", "wheel", "touchstart"];
1105
+ var activityListenersBound = false;
1106
+ var sessionResumeListeners = /* @__PURE__ */ new Set();
1107
+ function persistSessionTs(ts) {
1050
1108
  try {
1051
1109
  if (typeof localStorage !== "undefined") {
1052
- localStorage.setItem(SESSION_TS_KEY, now.toString());
1053
- persistedSessionTs = now;
1110
+ localStorage.setItem(SESSION_TS_KEY, ts.toString());
1111
+ persistedSessionTs = ts;
1054
1112
  }
1055
1113
  } catch {
1056
1114
  }
1057
1115
  }
1058
- function extendSession() {
1116
+ function isSessionActive(now = Date.now()) {
1117
+ return inMemorySessionTs > 0 && now - inMemorySessionTs <= THIRTY_MINUTES_MS;
1118
+ }
1119
+ function onSessionResume(listener) {
1120
+ bindActivityListeners();
1121
+ sessionResumeListeners.add(listener);
1122
+ return () => {
1123
+ sessionResumeListeners.delete(listener);
1124
+ };
1125
+ }
1126
+ function recordActivity() {
1059
1127
  const now = Date.now();
1128
+ const lapsed = !!inMemorySessionId && !isSessionActive(now);
1060
1129
  inMemorySessionTs = now;
1061
- if (!storageAllowed) return;
1130
+ if (lapsed) {
1131
+ inMemorySessionId = null;
1132
+ for (const listener of sessionResumeListeners) listener();
1133
+ return;
1134
+ }
1135
+ if (!storageAllowed || !inMemorySessionId || persistedSessionTs === 0) return;
1062
1136
  if (now - persistedSessionTs < SESSION_PERSIST_INTERVAL_MS) return;
1063
1137
  persistSessionTs(now);
1064
1138
  }
1139
+ function sessionNeedsSync() {
1140
+ return !inMemorySessionId || storageAllowed && persistedSessionTs === 0;
1141
+ }
1142
+ function bindActivityListeners() {
1143
+ if (activityListenersBound || typeof window === "undefined") return;
1144
+ activityListenersBound = true;
1145
+ const onActivity = () => recordActivity();
1146
+ for (const name of ACTIVITY_EVENTS) {
1147
+ window.addEventListener(name, onActivity, { passive: true, capture: true });
1148
+ }
1149
+ if (typeof document !== "undefined") {
1150
+ document.addEventListener("visibilitychange", () => {
1151
+ if (document.visibilityState === "visible") recordActivity();
1152
+ });
1153
+ }
1154
+ }
1065
1155
  function getOrCreateSessionId() {
1066
1156
  const now = Date.now();
1067
- const memoryFresh = !!inMemorySessionId && !!inMemorySessionTs && now - inMemorySessionTs <= THIRTY_MINUTES_MS;
1157
+ const memoryFresh = !!inMemorySessionId && isSessionActive(now);
1068
1158
  try {
1069
1159
  if (storageAllowed && typeof localStorage !== "undefined") {
1070
1160
  const storedId = localStorage.getItem(SESSION_KEY);
1071
1161
  const tsStr = localStorage.getItem(SESSION_TS_KEY);
1072
1162
  const storedTs = tsStr ? parseInt(tsStr, 10) || 0 : 0;
1073
1163
  if (storedId && storedTs && now - storedTs <= THIRTY_MINUTES_MS) {
1074
- persistSessionTs(now);
1075
- inMemorySessionId = storedId;
1076
- inMemorySessionTs = now;
1164
+ if (inMemorySessionId !== storedId || inMemorySessionTs < storedTs) {
1165
+ inMemorySessionId = storedId;
1166
+ inMemorySessionTs = Math.max(inMemorySessionTs, storedTs);
1167
+ }
1168
+ if (persistedSessionTs === 0) persistedSessionTs = storedTs;
1077
1169
  return storedId;
1078
1170
  }
1079
1171
  const newId = memoryFresh ? inMemorySessionId : generateUUID();
1172
+ if (!memoryFresh) inMemorySessionTs = now;
1080
1173
  localStorage.setItem(SESSION_KEY, newId);
1081
- persistSessionTs(now);
1174
+ persistSessionTs(inMemorySessionTs);
1082
1175
  inMemorySessionId = newId;
1083
- inMemorySessionTs = now;
1084
1176
  return newId;
1085
1177
  }
1086
1178
  } catch {
1087
1179
  }
1088
- if (inMemorySessionId && inMemorySessionTs && now - inMemorySessionTs <= THIRTY_MINUTES_MS) {
1089
- inMemorySessionTs = now;
1090
- return inMemorySessionId;
1091
- }
1180
+ if (memoryFresh) return inMemorySessionId;
1092
1181
  inMemorySessionId = generateUUID();
1093
1182
  inMemorySessionTs = now;
1094
1183
  return inMemorySessionId;
@@ -1247,7 +1336,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1247
1336
  videoDetachCleanups = /* @__PURE__ */ new Set();
1248
1337
  // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1249
1338
  auctionBidderOutcomes = /* @__PURE__ */ new Map();
1339
+ // Live Floors decisions: `${auctionId}:${adUnitCode}` -> decision (see recordFloorDecision)
1340
+ floorDecisions = /* @__PURE__ */ new Map();
1250
1341
  logger;
1342
+ // True when this instance owns its session id (none was supplied), so the
1343
+ // id follows the page-wide session as it lapses and restarts. An id the
1344
+ // integration supplied is stamped as given.
1345
+ managesSessionId;
1251
1346
  constructor(config) {
1252
1347
  if (config.storageAllowed !== void 0) {
1253
1348
  _BidkernelPrebidAnalytics.setStorageAllowed(config.storageAllowed);
@@ -1258,7 +1353,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1258
1353
  endpoint,
1259
1354
  propertyId: config.propertyId || "",
1260
1355
  pageviewId: config.pageviewId || generateUUID(),
1261
- sessionId: config.sessionId || getOrCreateSessionId(),
1356
+ sessionId: config.sessionId || "",
1262
1357
  userId: config.userId || "",
1263
1358
  deviceType: config.deviceType || "desktop",
1264
1359
  country: config.country || "",
@@ -1277,6 +1372,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1277
1372
  prefix: "[bidkernel][prebid-analytics]",
1278
1373
  logLevel: this.config.logLevel
1279
1374
  });
1375
+ recordActivity();
1376
+ this.managesSessionId = !config.sessionId;
1377
+ if (this.managesSessionId) this.config.sessionId = getOrCreateSessionId();
1280
1378
  if (requestedEndpoint && !endpoint) {
1281
1379
  this.log(
1282
1380
  "ERROR",
@@ -1325,6 +1423,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1325
1423
  if (typeof document !== "undefined") {
1326
1424
  document.addEventListener("visibilitychange", this.boundVisibilityChange);
1327
1425
  }
1426
+ bindActivityListeners();
1328
1427
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
1329
1428
  this.resendPersistedBatches();
1330
1429
  }
@@ -1434,6 +1533,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1434
1533
  this.slotEmittedImpressionKeys.clear();
1435
1534
  this.slotLastAuctionIds.clear();
1436
1535
  this.auctionBidderOutcomes.clear();
1536
+ this.floorDecisions.clear();
1437
1537
  if (this.flushTimer) {
1438
1538
  clearInterval(this.flushTimer);
1439
1539
  this.flushTimer = null;
@@ -2193,15 +2293,44 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2193
2293
  if (eventType === void 0) return;
2194
2294
  this.enqueue(eventType, eventName, data, level);
2195
2295
  }
2296
+ /**
2297
+ * Record the wrapper's Live Floors decision for an auction and ad unit,
2298
+ * before the auction runs. The decision is stamped on the auction's
2299
+ * outcome entries for the ad unit at auction end and on the win and
2300
+ * impression rows that follow, so every outcome can be split by floor,
2301
+ * source and treatment without a join. Integrations without the module
2302
+ * never call this and their rows carry no floor fields.
2303
+ */
2304
+ recordFloorDecision(auctionId, adUnitCode, decision) {
2305
+ if (!auctionId || !adUnitCode) return;
2306
+ const key = `${auctionId}:${adUnitCode}`;
2307
+ this.floorDecisions.delete(key);
2308
+ this.floorDecisions.set(key, {
2309
+ floorCents: decision.floorCents !== void 0 && Number.isFinite(decision.floorCents) ? Math.round(decision.floorCents) : void 0,
2310
+ source: decision.source,
2311
+ treatment: Number.isFinite(decision.treatment) ? decision.treatment : 1
2312
+ });
2313
+ while (this.floorDecisions.size > MAX_FLOOR_DECISIONS) {
2314
+ const oldest = this.floorDecisions.keys().next();
2315
+ if (oldest.done) break;
2316
+ this.floorDecisions.delete(oldest.value);
2317
+ }
2318
+ }
2319
+ floorDecisionFor(auctionId, adUnitCode) {
2320
+ return auctionId && adUnitCode ? this.floorDecisions.get(`${auctionId}:${adUnitCode}`) : void 0;
2321
+ }
2196
2322
  setUserId(userId) {
2197
2323
  this.config.userId = userId;
2198
2324
  }
2199
2325
  setSessionId(sessionId) {
2200
2326
  this.config.sessionId = sessionId;
2327
+ this.managesSessionId = !sessionId;
2328
+ if (this.managesSessionId) this.config.sessionId = getOrCreateSessionId();
2201
2329
  }
2202
2330
  navigate(pageviewId, pageUrl) {
2203
2331
  this.disable();
2204
2332
  this.slotRefreshIndices.clear();
2333
+ this.floorDecisions.clear();
2205
2334
  this.pendingThresholdListeners.clear();
2206
2335
  this.config.pageviewId = pageviewId || generateUUID();
2207
2336
  if (pageUrl) {
@@ -2215,7 +2344,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2215
2344
  this.pageUrl = "";
2216
2345
  }
2217
2346
  this.logger.resetTiming();
2218
- extendSession();
2347
+ recordActivity();
2219
2348
  this.enable();
2220
2349
  }
2221
2350
  handleAuctionInit(data) {
@@ -2554,7 +2683,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2554
2683
  enqueue(type, eventName, data, level) {
2555
2684
  if (!this.isEnabled) return;
2556
2685
  if (!this.shouldSample(type, level)) return;
2557
- extendSession();
2686
+ if (this.managesSessionId && sessionNeedsSync()) {
2687
+ this.config.sessionId = getOrCreateSessionId();
2688
+ }
2558
2689
  const protoEvent = {
2559
2690
  eventId: idToBytes(generateUUID()),
2560
2691
  timestampMs: Date.now(),
@@ -2589,13 +2720,32 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2589
2720
  }
2590
2721
  if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2591
2722
  protoEvent.bidderOutcomes = data.bidderOutcomes.map(
2592
- (entry) => ({
2593
- ...entry,
2594
- refreshIndex: entry.refreshIndex ?? this.auctionCycleIndex(entry.adUnitCode || "")
2595
- })
2723
+ (entry) => {
2724
+ const floor = this.floorDecisionFor(String(data.auctionId || ""), entry.adUnitCode || "");
2725
+ return {
2726
+ ...entry,
2727
+ refreshIndex: entry.refreshIndex ?? this.auctionCycleIndex(entry.adUnitCode || ""),
2728
+ ...floor ? {
2729
+ floorCents: floor.floorCents,
2730
+ floorSource: FLOOR_SOURCE_PROTO[floor.source],
2731
+ floorTreatmentBp: Math.round(floor.treatment * 1e4)
2732
+ } : {}
2733
+ };
2734
+ }
2596
2735
  );
2597
2736
  }
2598
2737
  const metadata = { ...data.metadata };
2738
+ if (type === TraceEventType.BID_WIN || type === TraceEventType.IMPRESSION) {
2739
+ const floor = this.floorDecisionFor(
2740
+ String(data.auctionId || ""),
2741
+ protoEvent.adUnitCode || ""
2742
+ );
2743
+ if (floor) {
2744
+ if (floor.floorCents !== void 0) metadata.floor_cents = String(floor.floorCents);
2745
+ metadata.floor_source = floor.source;
2746
+ metadata.floor_treatment = floor.treatment.toFixed(2);
2747
+ }
2748
+ }
2599
2749
  if (data.error !== void 0 && data.error !== null) {
2600
2750
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
2601
2751
  }
@@ -3803,6 +3953,8 @@ export {
3803
3953
  getbidkernel,
3804
3954
  hookImaPrototype,
3805
3955
  initImaInterception,
3956
+ isSessionActive,
3806
3957
  isTrustedEndpoint,
3958
+ onSessionResume,
3807
3959
  registerPrebidAnalytics
3808
3960
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bidkernel/analytics",
3
- "version": "0.20.0",
3
+ "version": "0.22.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",