@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.js CHANGED
@@ -30,7 +30,9 @@ __export(src_exports, {
30
30
  getbidkernel: () => getbidkernel,
31
31
  hookImaPrototype: () => hookImaPrototype,
32
32
  initImaInterception: () => initImaInterception,
33
+ isSessionActive: () => isSessionActive,
33
34
  isTrustedEndpoint: () => isTrustedEndpoint,
35
+ onSessionResume: () => onSessionResume,
34
36
  registerPrebidAnalytics: () => registerPrebidAnalytics
35
37
  });
36
38
  module.exports = __toCommonJS(src_exports);
@@ -354,6 +356,17 @@ var BidderOutcome = {
354
356
  BIDDER_OUTCOME_NO_RESPONSE: 5,
355
357
  UNRECOGNIZED: -1
356
358
  };
359
+ var FloorSource = {
360
+ /** FLOOR_SOURCE_UNSPECIFIED - module off, no decision */
361
+ FLOOR_SOURCE_UNSPECIFIED: 0,
362
+ /** FLOOR_SOURCE_STATIC - the publisher's rule, scaled by the treatment */
363
+ FLOOR_SOURCE_STATIC: 1,
364
+ /** FLOOR_SOURCE_POOL - the bid pool's standing bid plus margin */
365
+ FLOOR_SOURCE_POOL: 2,
366
+ /** FLOOR_SOURCE_NONE - module on, but neither term applied */
367
+ FLOOR_SOURCE_NONE: 3,
368
+ UNRECOGNIZED: -1
369
+ };
357
370
  function createBaseTraceEventBatch() {
358
371
  return {
359
372
  propertyId: new Uint8Array(0),
@@ -756,7 +769,15 @@ var TraceEvent_MetadataEntry = {
756
769
  }
757
770
  };
758
771
  function createBaseBidderOutcomeEntry() {
759
- return { adUnitCode: "", outcome: 0, bid: void 0, refreshIndex: 0 };
772
+ return {
773
+ adUnitCode: "",
774
+ outcome: 0,
775
+ bid: void 0,
776
+ refreshIndex: 0,
777
+ floorCents: void 0,
778
+ floorSource: 0,
779
+ floorTreatmentBp: void 0
780
+ };
760
781
  }
761
782
  var BidderOutcomeEntry = {
762
783
  encode(message, writer = new import_wire.BinaryWriter()) {
@@ -772,6 +793,15 @@ var BidderOutcomeEntry = {
772
793
  if (message.refreshIndex !== void 0 && message.refreshIndex !== 0) {
773
794
  writer.uint32(56).int32(message.refreshIndex);
774
795
  }
796
+ if (message.floorCents !== void 0) {
797
+ writer.uint32(64).int32(message.floorCents);
798
+ }
799
+ if (message.floorSource !== void 0 && message.floorSource !== 0) {
800
+ writer.uint32(72).int32(message.floorSource);
801
+ }
802
+ if (message.floorTreatmentBp !== void 0) {
803
+ writer.uint32(80).int32(message.floorTreatmentBp);
804
+ }
775
805
  return writer;
776
806
  },
777
807
  decode(input, length) {
@@ -809,6 +839,27 @@ var BidderOutcomeEntry = {
809
839
  message.refreshIndex = reader.int32();
810
840
  continue;
811
841
  }
842
+ case 8: {
843
+ if (tag !== 64) {
844
+ break;
845
+ }
846
+ message.floorCents = reader.int32();
847
+ continue;
848
+ }
849
+ case 9: {
850
+ if (tag !== 72) {
851
+ break;
852
+ }
853
+ message.floorSource = reader.int32();
854
+ continue;
855
+ }
856
+ case 10: {
857
+ if (tag !== 80) {
858
+ break;
859
+ }
860
+ message.floorTreatmentBp = reader.int32();
861
+ continue;
862
+ }
812
863
  }
813
864
  if ((tag & 7) === 4 || tag === 0) {
814
865
  break;
@@ -1008,6 +1059,12 @@ var CACHED_BID_TTL_MS = 30 * 60 * 1e3;
1008
1059
  var MAX_CACHED_BID_KEYS = 200;
1009
1060
  var MAX_IMPRESSION_KEYS = 1e3;
1010
1061
  var MAX_TRACKED_AUCTIONS = 50;
1062
+ var MAX_FLOOR_DECISIONS = 200;
1063
+ var FLOOR_SOURCE_PROTO = {
1064
+ static: FloorSource.FLOOR_SOURCE_STATIC,
1065
+ pool: FloorSource.FLOOR_SOURCE_POOL,
1066
+ none: FloorSource.FLOOR_SOURCE_NONE
1067
+ };
1011
1068
  var CLICK_DEDUP_MS = 1e3;
1012
1069
  var SESSION_KEY = "_bidkernel_session";
1013
1070
  var SESSION_TS_KEY = "_bidkernel_session_ts";
@@ -1083,49 +1140,83 @@ function sameEndpointOrigin(a, b) {
1083
1140
  }
1084
1141
  var SESSION_PERSIST_INTERVAL_MS = 60 * 1e3;
1085
1142
  var persistedSessionTs = 0;
1086
- function persistSessionTs(now) {
1143
+ var ACTIVITY_EVENTS = ["pointerdown", "pointermove", "keydown", "scroll", "wheel", "touchstart"];
1144
+ var activityListenersBound = false;
1145
+ var sessionResumeListeners = /* @__PURE__ */ new Set();
1146
+ function persistSessionTs(ts) {
1087
1147
  try {
1088
1148
  if (typeof localStorage !== "undefined") {
1089
- localStorage.setItem(SESSION_TS_KEY, now.toString());
1090
- persistedSessionTs = now;
1149
+ localStorage.setItem(SESSION_TS_KEY, ts.toString());
1150
+ persistedSessionTs = ts;
1091
1151
  }
1092
1152
  } catch {
1093
1153
  }
1094
1154
  }
1095
- function extendSession() {
1155
+ function isSessionActive(now = Date.now()) {
1156
+ return inMemorySessionTs > 0 && now - inMemorySessionTs <= THIRTY_MINUTES_MS;
1157
+ }
1158
+ function onSessionResume(listener) {
1159
+ bindActivityListeners();
1160
+ sessionResumeListeners.add(listener);
1161
+ return () => {
1162
+ sessionResumeListeners.delete(listener);
1163
+ };
1164
+ }
1165
+ function recordActivity() {
1096
1166
  const now = Date.now();
1167
+ const lapsed = !!inMemorySessionId && !isSessionActive(now);
1097
1168
  inMemorySessionTs = now;
1098
- if (!storageAllowed) return;
1169
+ if (lapsed) {
1170
+ inMemorySessionId = null;
1171
+ for (const listener of sessionResumeListeners) listener();
1172
+ return;
1173
+ }
1174
+ if (!storageAllowed || !inMemorySessionId || persistedSessionTs === 0) return;
1099
1175
  if (now - persistedSessionTs < SESSION_PERSIST_INTERVAL_MS) return;
1100
1176
  persistSessionTs(now);
1101
1177
  }
1178
+ function sessionNeedsSync() {
1179
+ return !inMemorySessionId || storageAllowed && persistedSessionTs === 0;
1180
+ }
1181
+ function bindActivityListeners() {
1182
+ if (activityListenersBound || typeof window === "undefined") return;
1183
+ activityListenersBound = true;
1184
+ const onActivity = () => recordActivity();
1185
+ for (const name of ACTIVITY_EVENTS) {
1186
+ window.addEventListener(name, onActivity, { passive: true, capture: true });
1187
+ }
1188
+ if (typeof document !== "undefined") {
1189
+ document.addEventListener("visibilitychange", () => {
1190
+ if (document.visibilityState === "visible") recordActivity();
1191
+ });
1192
+ }
1193
+ }
1102
1194
  function getOrCreateSessionId() {
1103
1195
  const now = Date.now();
1104
- const memoryFresh = !!inMemorySessionId && !!inMemorySessionTs && now - inMemorySessionTs <= THIRTY_MINUTES_MS;
1196
+ const memoryFresh = !!inMemorySessionId && isSessionActive(now);
1105
1197
  try {
1106
1198
  if (storageAllowed && typeof localStorage !== "undefined") {
1107
1199
  const storedId = localStorage.getItem(SESSION_KEY);
1108
1200
  const tsStr = localStorage.getItem(SESSION_TS_KEY);
1109
1201
  const storedTs = tsStr ? parseInt(tsStr, 10) || 0 : 0;
1110
1202
  if (storedId && storedTs && now - storedTs <= THIRTY_MINUTES_MS) {
1111
- persistSessionTs(now);
1112
- inMemorySessionId = storedId;
1113
- inMemorySessionTs = now;
1203
+ if (inMemorySessionId !== storedId || inMemorySessionTs < storedTs) {
1204
+ inMemorySessionId = storedId;
1205
+ inMemorySessionTs = Math.max(inMemorySessionTs, storedTs);
1206
+ }
1207
+ if (persistedSessionTs === 0) persistedSessionTs = storedTs;
1114
1208
  return storedId;
1115
1209
  }
1116
1210
  const newId = memoryFresh ? inMemorySessionId : generateUUID();
1211
+ if (!memoryFresh) inMemorySessionTs = now;
1117
1212
  localStorage.setItem(SESSION_KEY, newId);
1118
- persistSessionTs(now);
1213
+ persistSessionTs(inMemorySessionTs);
1119
1214
  inMemorySessionId = newId;
1120
- inMemorySessionTs = now;
1121
1215
  return newId;
1122
1216
  }
1123
1217
  } catch {
1124
1218
  }
1125
- if (inMemorySessionId && inMemorySessionTs && now - inMemorySessionTs <= THIRTY_MINUTES_MS) {
1126
- inMemorySessionTs = now;
1127
- return inMemorySessionId;
1128
- }
1219
+ if (memoryFresh) return inMemorySessionId;
1129
1220
  inMemorySessionId = generateUUID();
1130
1221
  inMemorySessionTs = now;
1131
1222
  return inMemorySessionId;
@@ -1284,7 +1375,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1284
1375
  videoDetachCleanups = /* @__PURE__ */ new Set();
1285
1376
  // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1286
1377
  auctionBidderOutcomes = /* @__PURE__ */ new Map();
1378
+ // Live Floors decisions: `${auctionId}:${adUnitCode}` -> decision (see recordFloorDecision)
1379
+ floorDecisions = /* @__PURE__ */ new Map();
1287
1380
  logger;
1381
+ // True when this instance owns its session id (none was supplied), so the
1382
+ // id follows the page-wide session as it lapses and restarts. An id the
1383
+ // integration supplied is stamped as given.
1384
+ managesSessionId;
1288
1385
  constructor(config) {
1289
1386
  if (config.storageAllowed !== void 0) {
1290
1387
  _BidkernelPrebidAnalytics.setStorageAllowed(config.storageAllowed);
@@ -1295,7 +1392,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1295
1392
  endpoint,
1296
1393
  propertyId: config.propertyId || "",
1297
1394
  pageviewId: config.pageviewId || generateUUID(),
1298
- sessionId: config.sessionId || getOrCreateSessionId(),
1395
+ sessionId: config.sessionId || "",
1299
1396
  userId: config.userId || "",
1300
1397
  deviceType: config.deviceType || "desktop",
1301
1398
  country: config.country || "",
@@ -1314,6 +1411,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1314
1411
  prefix: "[bidkernel][prebid-analytics]",
1315
1412
  logLevel: this.config.logLevel
1316
1413
  });
1414
+ recordActivity();
1415
+ this.managesSessionId = !config.sessionId;
1416
+ if (this.managesSessionId) this.config.sessionId = getOrCreateSessionId();
1317
1417
  if (requestedEndpoint && !endpoint) {
1318
1418
  this.log(
1319
1419
  "ERROR",
@@ -1362,6 +1462,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1362
1462
  if (typeof document !== "undefined") {
1363
1463
  document.addEventListener("visibilitychange", this.boundVisibilityChange);
1364
1464
  }
1465
+ bindActivityListeners();
1365
1466
  this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
1366
1467
  this.resendPersistedBatches();
1367
1468
  }
@@ -1471,6 +1572,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1471
1572
  this.slotEmittedImpressionKeys.clear();
1472
1573
  this.slotLastAuctionIds.clear();
1473
1574
  this.auctionBidderOutcomes.clear();
1575
+ this.floorDecisions.clear();
1474
1576
  if (this.flushTimer) {
1475
1577
  clearInterval(this.flushTimer);
1476
1578
  this.flushTimer = null;
@@ -2230,15 +2332,44 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2230
2332
  if (eventType === void 0) return;
2231
2333
  this.enqueue(eventType, eventName, data, level);
2232
2334
  }
2335
+ /**
2336
+ * Record the wrapper's Live Floors decision for an auction and ad unit,
2337
+ * before the auction runs. The decision is stamped on the auction's
2338
+ * outcome entries for the ad unit at auction end and on the win and
2339
+ * impression rows that follow, so every outcome can be split by floor,
2340
+ * source and treatment without a join. Integrations without the module
2341
+ * never call this and their rows carry no floor fields.
2342
+ */
2343
+ recordFloorDecision(auctionId, adUnitCode, decision) {
2344
+ if (!auctionId || !adUnitCode) return;
2345
+ const key = `${auctionId}:${adUnitCode}`;
2346
+ this.floorDecisions.delete(key);
2347
+ this.floorDecisions.set(key, {
2348
+ floorCents: decision.floorCents !== void 0 && Number.isFinite(decision.floorCents) ? Math.round(decision.floorCents) : void 0,
2349
+ source: decision.source,
2350
+ treatment: Number.isFinite(decision.treatment) ? decision.treatment : 1
2351
+ });
2352
+ while (this.floorDecisions.size > MAX_FLOOR_DECISIONS) {
2353
+ const oldest = this.floorDecisions.keys().next();
2354
+ if (oldest.done) break;
2355
+ this.floorDecisions.delete(oldest.value);
2356
+ }
2357
+ }
2358
+ floorDecisionFor(auctionId, adUnitCode) {
2359
+ return auctionId && adUnitCode ? this.floorDecisions.get(`${auctionId}:${adUnitCode}`) : void 0;
2360
+ }
2233
2361
  setUserId(userId) {
2234
2362
  this.config.userId = userId;
2235
2363
  }
2236
2364
  setSessionId(sessionId) {
2237
2365
  this.config.sessionId = sessionId;
2366
+ this.managesSessionId = !sessionId;
2367
+ if (this.managesSessionId) this.config.sessionId = getOrCreateSessionId();
2238
2368
  }
2239
2369
  navigate(pageviewId, pageUrl) {
2240
2370
  this.disable();
2241
2371
  this.slotRefreshIndices.clear();
2372
+ this.floorDecisions.clear();
2242
2373
  this.pendingThresholdListeners.clear();
2243
2374
  this.config.pageviewId = pageviewId || generateUUID();
2244
2375
  if (pageUrl) {
@@ -2252,7 +2383,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2252
2383
  this.pageUrl = "";
2253
2384
  }
2254
2385
  this.logger.resetTiming();
2255
- extendSession();
2386
+ recordActivity();
2256
2387
  this.enable();
2257
2388
  }
2258
2389
  handleAuctionInit(data) {
@@ -2591,7 +2722,9 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2591
2722
  enqueue(type, eventName, data, level) {
2592
2723
  if (!this.isEnabled) return;
2593
2724
  if (!this.shouldSample(type, level)) return;
2594
- extendSession();
2725
+ if (this.managesSessionId && sessionNeedsSync()) {
2726
+ this.config.sessionId = getOrCreateSessionId();
2727
+ }
2595
2728
  const protoEvent = {
2596
2729
  eventId: idToBytes(generateUUID()),
2597
2730
  timestampMs: Date.now(),
@@ -2626,13 +2759,32 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2626
2759
  }
2627
2760
  if (data.bidderOutcomes && Array.isArray(data.bidderOutcomes)) {
2628
2761
  protoEvent.bidderOutcomes = data.bidderOutcomes.map(
2629
- (entry) => ({
2630
- ...entry,
2631
- refreshIndex: entry.refreshIndex ?? this.auctionCycleIndex(entry.adUnitCode || "")
2632
- })
2762
+ (entry) => {
2763
+ const floor = this.floorDecisionFor(String(data.auctionId || ""), entry.adUnitCode || "");
2764
+ return {
2765
+ ...entry,
2766
+ refreshIndex: entry.refreshIndex ?? this.auctionCycleIndex(entry.adUnitCode || ""),
2767
+ ...floor ? {
2768
+ floorCents: floor.floorCents,
2769
+ floorSource: FLOOR_SOURCE_PROTO[floor.source],
2770
+ floorTreatmentBp: Math.round(floor.treatment * 1e4)
2771
+ } : {}
2772
+ };
2773
+ }
2633
2774
  );
2634
2775
  }
2635
2776
  const metadata = { ...data.metadata };
2777
+ if (type === TraceEventType.BID_WIN || type === TraceEventType.IMPRESSION) {
2778
+ const floor = this.floorDecisionFor(
2779
+ String(data.auctionId || ""),
2780
+ protoEvent.adUnitCode || ""
2781
+ );
2782
+ if (floor) {
2783
+ if (floor.floorCents !== void 0) metadata.floor_cents = String(floor.floorCents);
2784
+ metadata.floor_source = floor.source;
2785
+ metadata.floor_treatment = floor.treatment.toFixed(2);
2786
+ }
2787
+ }
2636
2788
  if (data.error !== void 0 && data.error !== null) {
2637
2789
  metadata.error = typeof data.error === "object" && data.error.message ? String(data.error.message) : String(data.error);
2638
2790
  }
@@ -3841,6 +3993,8 @@ function getbidkernel(alias = "bidkernel") {
3841
3993
  getbidkernel,
3842
3994
  hookImaPrototype,
3843
3995
  initImaInterception,
3996
+ isSessionActive,
3844
3997
  isTrustedEndpoint,
3998
+ onSessionResume,
3845
3999
  registerPrebidAnalytics
3846
4000
  });