@bidkernel/analytics 0.10.0 → 0.12.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
@@ -1,3 +1,289 @@
1
+ // ../logger/src/index.ts
2
+ var LogLevel = {
3
+ DEBUG: "DEBUG",
4
+ INFO: "INFO",
5
+ WARN: "WARN",
6
+ ERROR: "ERROR"
7
+ };
8
+ var LOG_LEVEL_MAP = {
9
+ [LogLevel.DEBUG]: 10,
10
+ [LogLevel.INFO]: 20,
11
+ [LogLevel.WARN]: 30,
12
+ [LogLevel.ERROR]: 40
13
+ };
14
+ var COLORS = {
15
+ badge: "color: #2dd4bf; font-weight: bold",
16
+ // teal - [ads] badge
17
+ badgeWarn: "background: #f59e0b; color: #fff; padding: 2px 4px; border-radius: 3px; font-weight: bold",
18
+ // amber - [ads] badge (warning)
19
+ badgeError: "background: #ef4444; color: #fff; padding: 2px 4px; border-radius: 3px; font-weight: bold",
20
+ // red - [ads] badge (error)
21
+ dim: "color: #6b7280",
22
+ // gray - timestamp
23
+ event: "color: #38bdf8; font-weight: bold",
24
+ // blue - event name
25
+ eventWarn: "color: #f59e0b; font-weight: bold",
26
+ // amber - event name (warning)
27
+ eventError: "color: #ef4444; font-weight: bold",
28
+ // red - event name (error)
29
+ reset: "color: inherit"
30
+ // back to default
31
+ };
32
+ var logCounter = 0;
33
+ var localGlobalListeners = /* @__PURE__ */ new Set();
34
+ function getGlobalListeners() {
35
+ if (typeof window !== "undefined") {
36
+ const win = window;
37
+ if (!win.__bidkernelLogListeners) {
38
+ win.__bidkernelLogListeners = /* @__PURE__ */ new Set();
39
+ }
40
+ return win.__bidkernelLogListeners;
41
+ }
42
+ return localGlobalListeners;
43
+ }
44
+ function formatTime(ms) {
45
+ if (ms < 0 || !Number.isFinite(ms)) ms = 0;
46
+ if (ms < 1e3) return `${ms.toFixed(2)}ms`;
47
+ const s = ms / 1e3;
48
+ if (s < 60) return `${s.toFixed(2)}s`;
49
+ const m = Math.floor(s / 60);
50
+ if (m < 60) return `${m}m ${(s % 60).toFixed(2)}s`;
51
+ const h = Math.floor(m / 60);
52
+ return `${h}h ${m % 60}m ${(s % 60).toFixed(2)}s`;
53
+ }
54
+ function normalizeLogLevel(level) {
55
+ if (typeof level === "string") {
56
+ const upper = level.toUpperCase();
57
+ if (upper === "DEBUG" || upper === "INFO" || upper === "WARN" || upper === "ERROR") {
58
+ return upper;
59
+ }
60
+ }
61
+ return LogLevel.INFO;
62
+ }
63
+ var Logger = class _Logger {
64
+ _prefix;
65
+ _level;
66
+ _loadedAt;
67
+ _now;
68
+ _console;
69
+ _customFormatTime;
70
+ _listeners = /* @__PURE__ */ new Set();
71
+ constructor(options = {}) {
72
+ this._prefix = options.prefix ?? "[bidkernel]";
73
+ this._level = normalizeLogLevel(options.logLevel ?? LogLevel.INFO);
74
+ this._now = options.now;
75
+ this._console = options.console;
76
+ this._customFormatTime = options.formatTime;
77
+ if (options.onLog) {
78
+ this._listeners.add(options.onLog);
79
+ }
80
+ this._loadedAt = options.loadedAt ?? (this._now ? this._now() : typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now());
81
+ }
82
+ get prefix() {
83
+ return this._prefix;
84
+ }
85
+ set prefix(val) {
86
+ this._prefix = val;
87
+ }
88
+ get logLevel() {
89
+ return this._level;
90
+ }
91
+ set logLevel(val) {
92
+ this._level = normalizeLogLevel(val);
93
+ }
94
+ getLevel() {
95
+ return this._level;
96
+ }
97
+ setLevel(level) {
98
+ this._level = normalizeLogLevel(level);
99
+ }
100
+ getPrefix() {
101
+ return this._prefix;
102
+ }
103
+ setPrefix(prefix) {
104
+ this._prefix = prefix;
105
+ }
106
+ resetTiming(loadedAt) {
107
+ this._loadedAt = loadedAt ?? (this._now ? this._now() : typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now());
108
+ }
109
+ getLoadedAt() {
110
+ return this._loadedAt;
111
+ }
112
+ addListener(listener) {
113
+ this._listeners.add(listener);
114
+ return () => {
115
+ this._listeners.delete(listener);
116
+ };
117
+ }
118
+ removeListener(listener) {
119
+ this._listeners.delete(listener);
120
+ }
121
+ isEnabled(level) {
122
+ const target = normalizeLogLevel(level);
123
+ return (LOG_LEVEL_MAP[target] ?? 20) >= (LOG_LEVEL_MAP[this._level] ?? 20);
124
+ }
125
+ formatTime(ms) {
126
+ if (this._customFormatTime) {
127
+ return this._customFormatTime(ms);
128
+ }
129
+ return formatTime(ms);
130
+ }
131
+ _getConsole() {
132
+ if (this._console) return this._console;
133
+ if (typeof console !== "undefined") return console;
134
+ return void 0;
135
+ }
136
+ _getElapsed() {
137
+ const current = this._now ? this._now() : typeof performance !== "undefined" && typeof performance.now === "function" ? performance.now() : Date.now();
138
+ return Math.max(0, current - this._loadedAt);
139
+ }
140
+ _dispatchRecord(record) {
141
+ for (const l of this._listeners) {
142
+ try {
143
+ l(record);
144
+ } catch {
145
+ }
146
+ }
147
+ const globalListeners = getGlobalListeners();
148
+ for (const gl of globalListeners) {
149
+ if (!this._listeners.has(gl)) {
150
+ try {
151
+ gl(record);
152
+ } catch {
153
+ }
154
+ }
155
+ }
156
+ if (typeof window !== "undefined" && typeof window.dispatchEvent === "function") {
157
+ try {
158
+ window.dispatchEvent(new CustomEvent("__bidkernel_log__", { detail: record }));
159
+ } catch {
160
+ }
161
+ }
162
+ }
163
+ log(level, nameOrMsg, ...args) {
164
+ const normLevel = normalizeLogLevel(level);
165
+ if (!this.isEnabled(normLevel)) {
166
+ return;
167
+ }
168
+ const elapsed = this._getElapsed();
169
+ const formattedTimeStr = this.formatTime(elapsed);
170
+ const nowEpoch = Date.now();
171
+ const recordId = `bk_log_${nowEpoch}_${++logCounter}`;
172
+ const record = {
173
+ id: recordId,
174
+ timestamp: nowEpoch,
175
+ elapsedMs: elapsed,
176
+ level: normLevel,
177
+ prefix: this._prefix,
178
+ message: nameOrMsg,
179
+ args,
180
+ data: args.length === 1 ? args[0] : args.length > 1 ? args : void 0
181
+ };
182
+ this._dispatchRecord(record);
183
+ const c = this._getConsole();
184
+ if (!c) return;
185
+ let badgeStyle = COLORS.badge;
186
+ let eventStyle = COLORS.event;
187
+ if (normLevel === LogLevel.WARN) {
188
+ badgeStyle = COLORS.badgeWarn;
189
+ eventStyle = COLORS.eventWarn;
190
+ } else if (normLevel === LogLevel.ERROR) {
191
+ badgeStyle = COLORS.badgeError;
192
+ eventStyle = COLORS.eventError;
193
+ }
194
+ const fmt = `%c${this._prefix}%c ${formattedTimeStr} %c${nameOrMsg}%c`;
195
+ const styles = [badgeStyle, COLORS.dim, eventStyle, COLORS.reset];
196
+ const callArgs = [fmt, ...styles];
197
+ if (args.length > 0) {
198
+ callArgs.push(...args);
199
+ }
200
+ switch (normLevel) {
201
+ case LogLevel.DEBUG:
202
+ if (typeof c.log === "function") {
203
+ c.log(...callArgs);
204
+ } else if (typeof c.debug === "function") {
205
+ c.debug(...callArgs);
206
+ }
207
+ break;
208
+ case LogLevel.INFO:
209
+ if (typeof c.info === "function") {
210
+ c.info(...callArgs);
211
+ } else if (typeof c.log === "function") {
212
+ c.log(...callArgs);
213
+ }
214
+ break;
215
+ case LogLevel.WARN:
216
+ if (typeof c.warn === "function") {
217
+ c.warn(...callArgs);
218
+ } else if (typeof c.log === "function") {
219
+ c.log(...callArgs);
220
+ }
221
+ break;
222
+ case LogLevel.ERROR:
223
+ if (typeof c.error === "function") {
224
+ c.error(...callArgs);
225
+ } else if (typeof c.log === "function") {
226
+ c.log(...callArgs);
227
+ }
228
+ break;
229
+ default:
230
+ if (typeof c.log === "function") {
231
+ c.log(...callArgs);
232
+ }
233
+ break;
234
+ }
235
+ }
236
+ debug(nameOrMsg, ...args) {
237
+ this.log(LogLevel.DEBUG, nameOrMsg, ...args);
238
+ }
239
+ info(nameOrMsg, ...args) {
240
+ this.log(LogLevel.INFO, nameOrMsg, ...args);
241
+ }
242
+ warn(nameOrMsg, ...args) {
243
+ this.log(LogLevel.WARN, nameOrMsg, ...args);
244
+ }
245
+ error(nameOrMsg, ...args) {
246
+ this.log(LogLevel.ERROR, nameOrMsg, ...args);
247
+ }
248
+ logEvent(event) {
249
+ if (!event) return;
250
+ if (event.data !== void 0) {
251
+ this.log(event.level, event.eventName, event.data);
252
+ } else {
253
+ this.log(event.level, event.eventName);
254
+ }
255
+ }
256
+ child(subNamespace, options = {}) {
257
+ let childPrefix = this._prefix;
258
+ if (subNamespace) {
259
+ const formattedSub = subNamespace.startsWith("[") && subNamespace.endsWith("]") ? subNamespace : `[${subNamespace}]`;
260
+ childPrefix = `${this._prefix}${formattedSub}`;
261
+ }
262
+ return new _Logger({
263
+ prefix: childPrefix,
264
+ logLevel: options.logLevel ?? this._level,
265
+ loadedAt: options.loadedAt ?? this._loadedAt,
266
+ now: options.now ?? this._now,
267
+ console: options.console ?? this._console,
268
+ formatTime: options.formatTime ?? this._customFormatTime,
269
+ ...options
270
+ });
271
+ }
272
+ withPrefix(prefix) {
273
+ return new _Logger({
274
+ prefix,
275
+ logLevel: this._level,
276
+ loadedAt: this._loadedAt,
277
+ now: this._now,
278
+ console: this._console,
279
+ formatTime: this._customFormatTime
280
+ });
281
+ }
282
+ };
283
+ function createLogger(options) {
284
+ return new Logger(options);
285
+ }
286
+
1
287
  // ../../protogen/trace_event.ts
2
288
  import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire";
3
289
  var TraceEventType = {
@@ -756,7 +1042,8 @@ var EVENT_NAME_TO_TYPE = {
756
1042
  timeInView: TraceEventType.TIME_IN_VIEW,
757
1043
  viewable: TraceEventType.VIEWABLE
758
1044
  };
759
- var IMMEDIATE_FLUSH_TYPES = /* @__PURE__ */ new Set([
1045
+ var REVENUE_FLUSH_DELAY_MS = 3e3;
1046
+ var REVENUE_FLUSH_TYPES = /* @__PURE__ */ new Set([
760
1047
  TraceEventType.IMPRESSION,
761
1048
  TraceEventType.BID_WIN,
762
1049
  TraceEventType.CLICK
@@ -1240,7 +1527,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1240
1527
  consecutiveSendFailures = 0;
1241
1528
  nextSendAllowedAt = 0;
1242
1529
  replayedEventCount = 0;
1243
- immediateFlushScheduled = false;
1530
+ revenueFlushTimer = null;
1244
1531
  // localStorage keys of exit batches this instance persisted, so a page that
1245
1532
  // survives its own pagehide/hidden (bfcache restore, tab re-focus) can
1246
1533
  // remove them instead of leaving them for a duplicate resend.
@@ -1261,6 +1548,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1261
1548
  videoDetachCleanups = /* @__PURE__ */ new Set();
1262
1549
  // Compacted bidder participation map per auction: auctionId -> Map<`${bidder}:${adUnitCode}`, BidderOutcomeEntry>
1263
1550
  auctionBidderOutcomes = /* @__PURE__ */ new Map();
1551
+ logger;
1264
1552
  constructor(config) {
1265
1553
  const requestedEndpoint = config.endpoint || "";
1266
1554
  const endpoint = !requestedEndpoint || isTrustedEndpoint(requestedEndpoint) ? requestedEndpoint : "";
@@ -1279,8 +1567,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1279
1567
  viewabilityEnabled: config.viewabilityEnabled ?? true,
1280
1568
  logLevel: config.logLevel || "INFO",
1281
1569
  pbjsGlobalName: config.pbjsGlobalName || "pbjs",
1282
- attachPbjsListeners: config.attachPbjsListeners ?? true
1570
+ attachPbjsListeners: config.attachPbjsListeners ?? true,
1571
+ revenueFlushDelayMs: Number.isFinite(config.revenueFlushDelayMs) && config.revenueFlushDelayMs >= 0 ? config.revenueFlushDelayMs : REVENUE_FLUSH_DELAY_MS
1283
1572
  };
1573
+ this.logger = createLogger({
1574
+ prefix: "[bidkernel][prebid-analytics]",
1575
+ logLevel: this.config.logLevel
1576
+ });
1284
1577
  if (requestedEndpoint && !endpoint) {
1285
1578
  this.log(
1286
1579
  "ERROR",
@@ -1297,6 +1590,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1297
1590
  if (this.isEnabled) return;
1298
1591
  this.isEnabled = true;
1299
1592
  _BidkernelPrebidAnalytics.activeInstances.add(this);
1593
+ this.logger.setLevel(this.config.logLevel);
1300
1594
  if (!this.config.endpoint) {
1301
1595
  this.log("WARN", "Endpoint is empty. Analytics events will not be transmitted.");
1302
1596
  }
@@ -1433,6 +1727,10 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
1433
1727
  clearInterval(this.flushTimer);
1434
1728
  this.flushTimer = null;
1435
1729
  }
1730
+ if (this.revenueFlushTimer) {
1731
+ clearTimeout(this.revenueFlushTimer);
1732
+ this.revenueFlushTimer = null;
1733
+ }
1436
1734
  if (this.persistedCleanupTimer) {
1437
1735
  clearTimeout(this.persistedCleanupTimer);
1438
1736
  this.persistedCleanupTimer = null;
@@ -2582,6 +2880,7 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2582
2880
  } else {
2583
2881
  this.pageUrl = "";
2584
2882
  }
2883
+ this.logger.resetTiming();
2585
2884
  extendSession();
2586
2885
  this.enable();
2587
2886
  }
@@ -2996,12 +3295,11 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
2996
3295
  }
2997
3296
  if (this.queue.length >= BATCH_SIZE) {
2998
3297
  this.flush();
2999
- } else if (IMMEDIATE_FLUSH_TYPES.has(type) && !this.immediateFlushScheduled) {
3000
- this.immediateFlushScheduled = true;
3001
- setTimeout(() => {
3002
- this.immediateFlushScheduled = false;
3298
+ } else if (REVENUE_FLUSH_TYPES.has(type) && this.revenueFlushTimer === null) {
3299
+ this.revenueFlushTimer = setTimeout(() => {
3300
+ this.revenueFlushTimer = null;
3003
3301
  this.flush();
3004
- }, 0);
3302
+ }, this.config.revenueFlushDelayMs);
3005
3303
  }
3006
3304
  }
3007
3305
  shouldSample(type, level) {
@@ -3261,25 +3559,13 @@ var BidkernelPrebidAnalytics = class _BidkernelPrebidAnalytics {
3261
3559
  }
3262
3560
  }
3263
3561
  log(level, msg, ...args) {
3264
- const levels = { DEBUG: 10, INFO: 20, WARN: 30, ERROR: 40 };
3265
- if (levels[level] < levels[this.config.logLevel]) return;
3266
- const prefix = `[bidkernel][prebid-analytics][${level}]`;
3267
- switch (level) {
3268
- case "DEBUG":
3269
- console.debug(prefix, msg, ...args);
3270
- break;
3271
- case "INFO":
3272
- console.info(prefix, msg, ...args);
3273
- break;
3274
- case "WARN":
3275
- console.warn(prefix, msg, ...args);
3276
- break;
3277
- case "ERROR":
3278
- console.error(prefix, msg, ...args);
3279
- break;
3280
- }
3562
+ this.logger.log(level, msg, ...args);
3281
3563
  }
3282
3564
  };
3565
+ var defaultLogger = createLogger({
3566
+ prefix: "[bidkernel]",
3567
+ logLevel: "INFO"
3568
+ });
3283
3569
  function extractAnalyticsOptions(config, allowProviderless = true) {
3284
3570
  if (!config) return null;
3285
3571
  if (Array.isArray(config)) {
@@ -3336,8 +3622,8 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
3336
3622
  ...options
3337
3623
  };
3338
3624
  if (pinnedEndpoint && !sameEndpointOrigin(merged.endpoint || "", pinnedEndpoint)) {
3339
- console.warn(
3340
- `[bidkernel] Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
3625
+ defaultLogger.warn(
3626
+ `Ignoring analytics endpoint change to ${merged.endpoint || "(empty)"}: this page is pinned to ${pinnedEndpoint}. Reload to change the ingest endpoint.`
3341
3627
  );
3342
3628
  merged.endpoint = pinnedEndpoint;
3343
3629
  }
@@ -3345,7 +3631,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
3345
3631
  try {
3346
3632
  previous.disable();
3347
3633
  } catch (e) {
3348
- console.warn("[bidkernel] Failed to disable previous analytics instance:", e);
3634
+ defaultLogger.warn("Failed to disable previous analytics instance:", e);
3349
3635
  }
3350
3636
  }
3351
3637
  const analytics = new BidkernelPrebidAnalytics(merged);
@@ -3378,7 +3664,7 @@ function registerPrebidAnalytics(pbjsGlobalName = "pbjs", defaults) {
3378
3664
  enableInstance(stashed);
3379
3665
  }
3380
3666
  } catch (e) {
3381
- console.warn("[bidkernel] Failed to register standard Prebid analytics adapter:", e);
3667
+ defaultLogger.warn("Failed to register standard Prebid analytics adapter:", e);
3382
3668
  }
3383
3669
  };
3384
3670
  if (pbjs.adapterManager && typeof pbjs.adapterManager.registerAnalyticsAdapter === "function") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bidkernel/analytics",
3
- "version": "0.10.0",
3
+ "version": "0.12.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",
@@ -55,6 +55,7 @@
55
55
  "@bufbuild/protobuf": "^2.11.0"
56
56
  },
57
57
  "devDependencies": {
58
+ "@bidkernel/logger": "^0.1.0",
58
59
  "happy-dom": "^20.11.1",
59
60
  "tsup": "^8.5.0",
60
61
  "typescript": "^5.9.3",