@newrelic/video-core 4.1.8 → 5.0.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.
Files changed (41) hide show
  1. package/README.md +140 -27
  2. package/dist/cjs/browser/index.js +3 -0
  3. package/dist/cjs/browser/index.js.LICENSE.txt +6 -0
  4. package/dist/cjs/browser/index.js.map +1 -0
  5. package/dist/cjs/index.js +1 -1
  6. package/dist/cjs/index.js.LICENSE.txt +1 -1
  7. package/dist/cjs/index.js.map +1 -1
  8. package/dist/cjs/vega/index.js +3 -0
  9. package/dist/cjs/vega/index.js.LICENSE.txt +6 -0
  10. package/dist/cjs/vega/index.js.map +1 -0
  11. package/dist/esm/browser/index.js +3 -0
  12. package/dist/esm/browser/index.js.LICENSE.txt +6 -0
  13. package/dist/esm/browser/index.js.map +1 -0
  14. package/dist/esm/index.js +1 -1
  15. package/dist/esm/index.js.LICENSE.txt +1 -1
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/vega/index.js +3 -0
  18. package/dist/esm/vega/index.js.LICENSE.txt +6 -0
  19. package/dist/esm/vega/index.js.map +1 -0
  20. package/dist/umd/nrvideo.min.js +1 -1
  21. package/dist/umd/nrvideo.min.js.LICENSE.txt +1 -1
  22. package/dist/umd/nrvideo.min.js.map +1 -1
  23. package/package.json +25 -7
  24. package/src/{agent.js → browser/agent.js} +14 -35
  25. package/src/{harvestScheduler.js → browser/harvestScheduler.js} +55 -137
  26. package/src/browser/index.js +58 -0
  27. package/src/connectedDevice/connectedDeviceAgent.js +116 -0
  28. package/src/connectedDevice/connectedDeviceConstants.js +121 -0
  29. package/src/connectedDevice/connectedDeviceHarvester.js +522 -0
  30. package/src/connectedDevice/index.js +49 -0
  31. package/src/constants.js +2 -0
  32. package/src/core.js +4 -18
  33. package/src/index.js +9 -10
  34. package/src/recordEvent.js +56 -49
  35. package/src/tracker.js +14 -4
  36. package/src/utils/eventBuilder.js +126 -0
  37. package/src/utils/harvestTimer.js +109 -0
  38. package/src/{utils.js → utils/index.js} +2 -2
  39. package/src/utils/qoeFilters.js +149 -0
  40. package/src/videoConfiguration.js +56 -10
  41. package/src/videotracker.js +26 -9
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "@newrelic/video-core",
3
- "version": "4.1.8",
3
+ "version": "5.0.0",
4
4
  "description": "New Relic video tracking core library",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/esm/index.js",
10
+ "require": "./dist/cjs/index.js"
11
+ },
12
+ "./browser": {
13
+ "import": "./dist/esm/browser/index.js",
14
+ "require": "./dist/cjs/browser/index.js"
15
+ },
16
+ "./vega": {
17
+ "import": "./dist/esm/vega/index.js",
18
+ "require": "./dist/cjs/vega/index.js"
19
+ },
20
+ "./package.json": "./package.json"
21
+ },
7
22
  "scripts": {
8
23
  "build": "webpack --mode production",
9
24
  "build:dev": "webpack --mode development",
@@ -26,6 +41,7 @@
26
41
  "Bharat Sarsawat"
27
42
  ],
28
43
  "license": "Apache-2.0",
44
+ "sideEffects": false,
29
45
  "devDependencies": {
30
46
  "@babel/core": "^7.24.5",
31
47
  "@babel/plugin-transform-modules-commonjs": "^7.24.1",
@@ -36,18 +52,17 @@
36
52
  "@semantic-release/commit-analyzer": "^12.0.0",
37
53
  "@semantic-release/git": "^10.0.1",
38
54
  "@semantic-release/github": "^10.0.3",
39
- "@semantic-release/npm": "^12.0.0",
55
+ "@semantic-release/npm": "^13.1.5",
40
56
  "@semantic-release/release-notes-generator": "^13.0.0",
41
- "aws-sdk": "^2.920.0",
42
- "babel-jest": "^30.2.0",
57
+ "babel-jest": "^30.0.0",
43
58
  "babel-loader": "^9.1.3",
44
59
  "diff": "^5.0.0",
45
60
  "jest": "^30.2.0",
46
61
  "jest-environment-jsdom": "^30.2.0",
47
62
  "jsdom": "^25.0.1",
48
- "mocha": "^10.4.0",
49
- "nyc": "^15.1.0",
50
- "semantic-release": "^23.0.8",
63
+ "mocha": "^11.0.0",
64
+ "nyc": "^18.0.0",
65
+ "semantic-release": "^25.0.8",
51
66
  "sinon": "^2.4.1",
52
67
  "webpack": "^5.91.0",
53
68
  "webpack-cli": "^4.9.2"
@@ -65,5 +80,8 @@
65
80
  ],
66
81
  "publishConfig": {
67
82
  "access": "public"
83
+ },
84
+ "dependencies": {
85
+ "terser-webpack-plugin": "^5.6.1"
68
86
  }
69
87
  }
@@ -1,8 +1,11 @@
1
1
  import { HarvestScheduler } from "./harvestScheduler.js";
2
- import { NrVideoEventAggregator } from "./eventAggregator.js";
3
- import Constants from "./constants.js";
4
- import Log from "./log.js";
5
- import Tracker from "./tracker";
2
+ import { NrVideoEventAggregator } from "../eventAggregator.js";
3
+ import Log from "../log.js";
4
+ import { registerHarvester } from "../recordEvent.js";
5
+ import {
6
+ bufferEventWithQoeDedup,
7
+ refreshQoeKpisInBuffer,
8
+ } from "../utils/qoeFilters";
6
9
 
7
10
  /**
8
11
  * Enhanced video analytics agent with HarvestScheduler only.
@@ -49,20 +52,8 @@ class VideoAnalyticsAgent {
49
52
  }
50
53
 
51
54
  try {
52
- if(eventObject.actionName && eventObject.actionName === Tracker.Events.QOE_AGGREGATE) {
53
- // Ensure only one QOE_AGGREGATE event per (actionName + viewId) in buffer per harvest cycle.
54
- // Each player has a unique viewId, so multi-player scenarios are handled correctly.
55
- // Dirty-check dedup happens at drain time in HarvestScheduler._qoeKpisUnchanged().
56
- if (eventObject.viewId) {
57
- return this.eventBuffer.addOrReplaceByActionNameAndViewId(
58
- Tracker.Events.QOE_AGGREGATE,
59
- eventObject.viewId,
60
- eventObject
61
- );
62
- }
63
- return this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, eventObject);
64
- }
65
- return this.eventBuffer.add(eventObject);
55
+ // QOE_AGGREGATE dedup + plain append, shared with the Vega pipeline.
56
+ return bufferEventWithQoeDedup(this.eventBuffer, eventObject);
66
57
  } catch (error) {
67
58
  Log.error("Failed to add event to harvesting system:", error.message);
68
59
  return false;
@@ -109,28 +100,16 @@ class VideoAnalyticsAgent {
109
100
  * @param {string} [viewId] - The viewId of the player whose QoE event to update
110
101
  */
111
102
  refreshQoeKpis(freshKpis, viewId) {
112
- if (!this.eventBuffer || !freshKpis) return;
113
- const existing = viewId
114
- ? this.eventBuffer.findByActionNameAndViewId(Tracker.Events.QOE_AGGREGATE, viewId)
115
- : this.eventBuffer.findByActionName(Tracker.Events.QOE_AGGREGATE);
116
- if (existing) {
117
- const updated = { ...existing };
118
- for (const key of Constants.QOE_KPI_KEYS) {
119
- if (key in freshKpis) {
120
- updated[key] = freshKpis[key];
121
- }
122
- }
123
- if (viewId) {
124
- this.eventBuffer.addOrReplaceByActionNameAndViewId(Tracker.Events.QOE_AGGREGATE, viewId, updated);
125
- } else {
126
- this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, updated);
127
- }
128
- }
103
+ refreshQoeKpisInBuffer(this.eventBuffer, freshKpis, viewId);
129
104
  }
130
105
  }
131
106
 
132
107
  // Create singleton instance
133
108
  const videoAnalyticsAgent = new VideoAnalyticsAgent();
134
109
 
110
+ // Self-register for the 'Browser' routing key. Importing this module is what
111
+ // makes the Browser pipeline reachable in the consumer's bundle.
112
+ registerHarvester("Browser", videoAnalyticsAgent);
113
+
135
114
  // Enhanced video analytics harvester
136
115
  export const videoAnalyticsHarvester = videoAnalyticsAgent;
@@ -1,10 +1,15 @@
1
- import { NrVideoEventAggregator } from "./eventAggregator";
2
- import { RetryQueueHandler } from "./retryQueueHandler";
3
- import { OptimizedHttpClient } from "./optimizedHttpClient";
4
- import { buildUrl, dataSize } from "./utils";
5
- import Constants from "./constants";
6
- import Tracker from "./tracker";
7
- import Log from "./log";
1
+ import { NrVideoEventAggregator } from "../eventAggregator";
2
+ import { RetryQueueHandler } from "../retryQueueHandler";
3
+ import { OptimizedHttpClient } from "../optimizedHttpClient";
4
+ import { buildUrl, dataSize } from "../utils";
5
+ import {
6
+ partitionByQoeCycle,
7
+ applyQoeDirtyFilter,
8
+ } from "../utils/qoeFilters";
9
+ import { createHarvestTimer } from "../utils/harvestTimer";
10
+ import Constants from "../constants";
11
+ import Tracker from "../tracker";
12
+ import Log from "../log";
8
13
 
9
14
  /**
10
15
  * Enhanced harvest scheduler that orchestrates the video analytics data collection,
@@ -28,8 +33,6 @@ export class HarvestScheduler {
28
33
  }
29
34
 
30
35
  // Scheduler state
31
- this.isStarted = false;
32
- this.currentTimerId = null;
33
36
  this.harvestCycle = Constants.INTERVAL;
34
37
  this.isHarvesting = false;
35
38
  this.qoeCycleCount = 1;
@@ -37,44 +40,48 @@ export class HarvestScheduler {
37
40
  this.beforeDrainCallback = null;
38
41
  this._lastSentQoeKpis = {};
39
42
 
43
+ // Periodic harvest timer. Chained-setTimeout under the hood
44
+ this.timer = createHarvestTimer({
45
+ interval: this.harvestCycle,
46
+ onTick: () => this.onHarvestInterval(),
47
+ errorLabel: "HarvestScheduler",
48
+ });
49
+
40
50
  // Page lifecycle handling
41
51
  this.setupPageLifecycleHandlers();
42
52
  }
43
53
 
54
+ /**
55
+ * Whether the scheduler is currently running. Backed by the shared timer.
56
+ * @returns {boolean}
57
+ */
58
+ get isStarted() {
59
+ return this.timer.isRunning();
60
+ }
61
+
44
62
  /**
45
63
  * Starts the harvest scheduler.
46
64
  */
47
65
  startScheduler() {
48
- if (this.isStarted) {
66
+ if (this.timer.isRunning()) {
49
67
  Log.warn("Harvest scheduler is already started");
50
68
  return;
51
69
  }
52
70
 
53
- this.isStarted = true;
54
-
55
71
  Log.notice("Starting harvest scheduler", {
56
72
  harvestCycle: this.harvestCycle,
57
73
  eventBufferSize: this.eventBuffer ? this.eventBuffer.size() : 0,
58
74
  });
59
75
 
60
- this.scheduleNextHarvest();
76
+ this.timer.start();
61
77
  }
62
78
 
63
79
  /**
64
80
  * Stops the harvest scheduler.
65
81
  */
66
82
  stopScheduler() {
67
- if (!this.isStarted) {
68
- return;
69
- }
70
-
71
- this.isStarted = false;
72
-
73
- if (this.currentTimerId) {
74
- clearTimeout(this.currentTimerId);
75
- this.currentTimerId = null;
76
- }
77
-
83
+ if (!this.timer.isRunning()) return;
84
+ this.timer.stop();
78
85
  Log.notice("Harvest scheduler stopped");
79
86
  }
80
87
 
@@ -92,53 +99,30 @@ export class HarvestScheduler {
92
99
  // If buffer is empty, abort harvest
93
100
  if (!this.eventBuffer || this.eventBuffer.isEmpty()) return;
94
101
 
95
- // Clear existing timer to prevent redundant harvests
96
- if (this.currentTimerId) {
97
- clearTimeout(this.currentTimerId);
98
- this.currentTimerId = null;
99
- }
100
-
101
102
  try {
102
103
  await this.triggerHarvest({});
103
104
  } catch (error) {
104
105
  Log.error(`${type} harvest failed:`, error.message);
105
106
  } finally {
106
- // Schedule next harvest after smart harvest completes
107
- if (this.isStarted) {
108
- this.scheduleNextHarvest();
109
- }
107
+ // Reset the periodic clock — next periodic tick happens `harvestCycle`
108
+ // after this smart drain, not at the originally-scheduled time.
109
+ this.timer.cancelAndReschedule();
110
110
  }
111
111
  }
112
112
 
113
113
  /**
114
- * Schedules the next harvest based on current conditions.
115
- * @private
116
- */
117
- scheduleNextHarvest() {
118
- if (!this.isStarted) return;
119
-
120
- const interval = this.harvestCycle;
121
- this.currentTimerId = setTimeout(() => this.onHarvestInterval(), interval);
122
- }
123
-
124
- /**
125
- * Handles the harvest interval timer.
114
+ * Periodic-tick callback. Invoked by the shared harvest timer on each cycle.
126
115
  * @private
127
116
  */
128
117
  async onHarvestInterval() {
129
- try {
130
- // Check if there's any data to harvest (buffer or retry queue) before starting the harvest process
131
- const hasBufferData = this.eventBuffer && !this.eventBuffer.isEmpty();
132
- const hasRetryData =
133
- this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0;
134
-
135
- if (!hasBufferData && !hasRetryData) return;
136
- await this.triggerHarvest({});
137
- } catch (error) {
138
- Log.error("Error during scheduled harvest:", error.message);
139
- } finally {
140
- this.scheduleNextHarvest();
141
- }
118
+ // Check if there's any data to harvest (buffer or retry queue) before
119
+ // starting the harvest process avoids unnecessary network calls when
120
+ // there's nothing to ship.
121
+ const hasBufferData = this.eventBuffer && !this.eventBuffer.isEmpty();
122
+ const hasRetryData =
123
+ this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0;
124
+ if (!hasBufferData && !hasRetryData) return;
125
+ await this.triggerHarvest({});
142
126
  }
143
127
 
144
128
  /**
@@ -185,7 +169,6 @@ export class HarvestScheduler {
185
169
  return {
186
170
  success: false,
187
171
  error: error.message,
188
- consecutiveFailures: this.consecutiveFailures,
189
172
  };
190
173
  } finally {
191
174
  this.isHarvesting = false;
@@ -269,35 +252,17 @@ export class HarvestScheduler {
269
252
 
270
253
  // Always drain fresh events first (priority approach)
271
254
  const freshEvents = this.eventBuffer.drain();
272
- let filteredFreshEvents;
273
- if (isQoeCycle) {
274
- filteredFreshEvents = freshEvents;
275
- } else {
276
- // On non-QoE cycles, put QoE events back into buffer instead of losing them
277
- filteredFreshEvents = [];
278
- for (const e of freshEvents) {
279
- if (e.actionName === Tracker.Events.QOE_AGGREGATE) {
280
- this.eventBuffer.add(e);
281
- } else {
282
- filteredFreshEvents.push(e);
283
- }
284
- }
285
- }
255
+ let filteredFreshEvents = partitionByQoeCycle(
256
+ freshEvents,
257
+ isQoeCycle,
258
+ this.eventBuffer
259
+ );
286
260
 
287
261
  this.qoeCycleCount++;
288
262
 
289
- // Cross-cycle dirty check: skip QoE if KPIs unchanged since last send
290
- // Forced cycles (CONTENT_END, page unload) always send QoE
291
- for (let i = filteredFreshEvents.length - 1; i >= 0; i--) {
292
- const e = filteredFreshEvents[i];
293
- if (e.actionName === Tracker.Events.QOE_AGGREGATE) {
294
- if (!isForced && this._qoeKpisUnchanged(e)) {
295
- filteredFreshEvents.splice(i, 1);
296
- } else {
297
- this._saveQoeKpis(e);
298
- }
299
- }
300
- }
263
+ // Cross-cycle dirty check: skip QoE if KPIs unchanged since last send.
264
+ // Forced cycles (CONTENT_END, page unload) always send QoE.
265
+ applyQoeDirtyFilter(filteredFreshEvents, this._lastSentQoeKpis, isForced);
301
266
 
302
267
  let events = [...filteredFreshEvents];
303
268
  let currentPayloadSize = dataSize(filteredFreshEvents);
@@ -391,12 +356,7 @@ export class HarvestScheduler {
391
356
  * @private
392
357
  */
393
358
  handleHarvestFailure(error) {
394
- this.consecutiveFailures++;
395
-
396
- Log.warn("Harvest failure handled", {
397
- error: error.message,
398
- consecutiveFailures: this.consecutiveFailures,
399
- });
359
+ Log.warn("Harvest failure handled", { error: error.message });
400
360
  }
401
361
 
402
362
  /**
@@ -406,7 +366,7 @@ export class HarvestScheduler {
406
366
  */
407
367
 
408
368
  updateHarvestInterval(newInterval) {
409
- if (typeof newInterval !== "number" && isNaN(newInterval)) {
369
+ if (typeof newInterval !== "number" || isNaN(newInterval)) {
410
370
  Log.warn("Invalid newInterval provided to updateHarvestInterval");
411
371
  return;
412
372
  }
@@ -417,26 +377,11 @@ export class HarvestScheduler {
417
377
  }
418
378
 
419
379
  // Check if the interval has actually changed to avoid unnecessary actions
420
- if (this.harvestCycle === newInterval) {
421
- return;
422
- }
380
+ if (this.harvestCycle === newInterval) return;
423
381
 
424
- // 1. Update the harvestCycle property with the new interval
425
382
  this.harvestCycle = newInterval;
426
383
  Log.notice("Updated harvestCycle:", this.harvestCycle);
427
-
428
- // 2. Clear the existing timer
429
- if (this.currentTimerId) {
430
- clearTimeout(this.currentTimerId);
431
- this.currentTimerId = null;
432
- }
433
-
434
- // 3. Schedule a new timer with the updated interval
435
- if (this.isStarted) {
436
- this.scheduleNextHarvest();
437
- }
438
-
439
- return;
384
+ this.timer.updateInterval(newInterval);
440
385
  }
441
386
 
442
387
  /**
@@ -469,31 +414,4 @@ export class HarvestScheduler {
469
414
  });
470
415
  }
471
416
 
472
- /**
473
- * Checks if QoE KPIs are unchanged since last send.
474
- * @param {object} event - QoE event to compare
475
- * @returns {boolean} True if KPIs are identical to last sent
476
- * @private
477
- */
478
- _qoeKpisUnchanged(event) {
479
- const snapshot = this._lastSentQoeKpis[event.viewId];
480
- if (!snapshot) return false;
481
- for (const key of Constants.QOE_KPI_KEYS) {
482
- if (event[key] !== snapshot[key]) return false;
483
- }
484
- return true;
485
- }
486
-
487
- /**
488
- * Saves QoE KPI values after sending, keyed by viewId to support multiple players.
489
- * @param {object} event - QoE event that was sent
490
- * @private
491
- */
492
- _saveQoeKpis(event) {
493
- const snapshot = {};
494
- for (const key of Constants.QOE_KPI_KEYS) {
495
- snapshot[key] = event[key];
496
- }
497
- this._lastSentQoeKpis[event.viewId] = snapshot;
498
- }
499
417
  }
@@ -0,0 +1,58 @@
1
+ import Core from "../core";
2
+ import Constants from "../constants";
3
+ import Chrono from "../chrono";
4
+ import Log from "../log";
5
+ import Emitter from "../emitter";
6
+ import Tracker from "../tracker";
7
+ import VideoTracker from "../videotracker";
8
+ import VideoTrackerState from "../videotrackerstate";
9
+ import { NrVideoEventAggregator } from "../eventAggregator";
10
+ import { RetryQueueHandler } from "../retryQueueHandler";
11
+ import { OptimizedHttpClient } from "../optimizedHttpClient";
12
+ import { HarvestScheduler } from "./harvestScheduler";
13
+ import { videoAnalyticsHarvester } from "./agent";
14
+ import { getRegisteredHarvester } from "../recordEvent";
15
+ // recordEvent is the single shared implementation from `src/recordEvent.js`
16
+ // — handles both Browser and Vega pipelines via the registry.
17
+ import { recordEvent } from "../recordEvent";
18
+ import { version } from "../../package.json";
19
+
20
+ const nrvideo = {
21
+ Constants,
22
+ Chrono,
23
+ Log,
24
+ Emitter,
25
+ Tracker,
26
+ VideoTracker,
27
+ VideoTrackerState,
28
+ Core,
29
+ version,
30
+
31
+ NrVideoEventAggregator,
32
+ RetryQueueHandler,
33
+ OptimizedHttpClient,
34
+ HarvestScheduler,
35
+
36
+ recordEvent,
37
+ };
38
+
39
+ export {
40
+ Core,
41
+ Constants,
42
+ Chrono,
43
+ Log,
44
+ Emitter,
45
+ Tracker,
46
+ VideoTracker,
47
+ VideoTrackerState,
48
+ NrVideoEventAggregator,
49
+ RetryQueueHandler,
50
+ OptimizedHttpClient,
51
+ HarvestScheduler,
52
+ videoAnalyticsHarvester,
53
+ getRegisteredHarvester,
54
+ recordEvent,
55
+ version,
56
+ };
57
+
58
+ export default nrvideo;
@@ -0,0 +1,116 @@
1
+ import ConnectedDeviceHarvester from "./connectedDeviceHarvester";
2
+ import Log from "../log";
3
+ import { registerHarvester } from "../recordEvent";
4
+
5
+ /**
6
+ * Vega-side analytics agent. Mirror of `VideoAnalyticsAgent` in `agent.js`.
7
+ */
8
+ class ConnectedDeviceAnalyticsAgent {
9
+ constructor() {
10
+ this.isInitialized = false;
11
+ this.harvester = null;
12
+ }
13
+
14
+ /**
15
+ * Lazy initialization. Reads `info` from `globalThis.__NRVIDEO_CD__` and
16
+ * constructs the wrapped `ConnectedDeviceHarvester`. If `info` is not yet populated,
17
+ * returns without flipping `isInitialized` so the next `addEvent` retries.
18
+ */
19
+ initialize() {
20
+ if (this.isInitialized) return;
21
+
22
+ const info = globalThis.__NRVIDEO_CD__?.info;
23
+ if (!info) {
24
+ Log.warn(
25
+ "ConnectedDeviceAnalyticsAgent.initialize: globalThis.__NRVIDEO_CD__.info is missing"
26
+ );
27
+ return;
28
+ }
29
+
30
+ try {
31
+ this.harvester = new ConnectedDeviceHarvester(info);
32
+ this.isInitialized = true;
33
+ Log.notice("ConnectedDeviceAnalyticsAgent initialized");
34
+ } catch (err) {
35
+ Log.error("ConnectedDeviceAnalyticsAgent.initialize failed:", err.message);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Buffers an event for the wrapped ConnectedDeviceHarvester. Triggers lazy init on
41
+ * the first call. Drops events silently if init has not yet succeeded
42
+ * (defensive — under normal flow, `setVideoConfig` runs synchronously
43
+ * inside `super()` before any player event can fire).
44
+ *
45
+ *
46
+ * @param {object} eventObject
47
+ * @returns {boolean}
48
+ */
49
+ addEvent(eventObject) {
50
+ if (!this.isInitialized) this.initialize();
51
+ if (!this.isInitialized) return false;
52
+ return this.harvester.addEvent(eventObject);
53
+ }
54
+
55
+ /**
56
+ * Forwards to the underlying ConnectedDeviceHarvester. No-op if not yet initialized.
57
+ * @see ConnectedDeviceHarvester#forceQoeNextHarvest
58
+ */
59
+ forceQoeNextHarvest() {
60
+ if (this.harvester) this.harvester.forceQoeNextHarvest();
61
+ }
62
+
63
+ /**
64
+ * API-parity alias for `forceQoeNextHarvest`. Lets `videotracker.js` call a
65
+ * single method name on whichever harvester `getHarvester()` returns —
66
+ * Browser exposes `forceNextQoeCycle`, Vega exposes both names.
67
+ */
68
+ forceNextQoeCycle() {
69
+ this.forceQoeNextHarvest();
70
+ }
71
+
72
+ /**
73
+ * Forwards to the underlying ConnectedDeviceHarvester. No-op if not yet initialized.
74
+ * @see ConnectedDeviceHarvester#setBeforeDrainCallback
75
+ * @param {Function|null} cb
76
+ */
77
+ setBeforeDrainCallback(cb) {
78
+ if (this.harvester) this.harvester.setBeforeDrainCallback(cb);
79
+ }
80
+
81
+ /**
82
+ * Forwards to the underlying ConnectedDeviceHarvester. Triggers lazy init so
83
+ * `videotracker.js`'s drain callback (registered in sendStart) can refresh
84
+ * QoE on the buffered event before send.
85
+ *
86
+ * @param {object} freshKpis
87
+ * @param {string} [viewId]
88
+ */
89
+ refreshQoeKpis(freshKpis, viewId) {
90
+ if (!this.isInitialized) this.initialize();
91
+ if (this.harvester) this.harvester.refreshQoeKpis(freshKpis, viewId);
92
+ }
93
+
94
+ /**
95
+ * Forwards to the underlying ConnectedDeviceHarvester. Triggers lazy init so
96
+ * `tracker.setHarvestInterval(...)` works for VegaTracker the same way it
97
+ * does for other trackers.
98
+ *
99
+ * @param {number} interval
100
+ */
101
+ setHarvestInterval(interval) {
102
+ if (!this.isInitialized) this.initialize();
103
+ if (this.harvester) this.harvester.setHarvestInterval(interval);
104
+ }
105
+ }
106
+
107
+ const connectedDeviceAnalyticsAgent = new ConnectedDeviceAnalyticsAgent();
108
+
109
+ // Self-register for the 'Vega' routing key. Importing this module is what
110
+ // makes the Vega pipeline reachable in the consumer's bundle. If no consumer
111
+ // imports this module (e.g., Html5Tracker-only build), the entire Vega chain
112
+ // — connectedDeviceHarvester, connectedDeviceConstants, and indirectly
113
+ // retryQueueHandler/optimizedHttpClient — is tree-shaken.
114
+ registerHarvester("Vega", connectedDeviceAnalyticsAgent);
115
+
116
+ export const connectedDeviceAnalyticsHarvester = connectedDeviceAnalyticsAgent;