@newrelic/video-core 4.1.8-beta → 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 (46) hide show
  1. package/CHANGELOG.md +65 -15
  2. package/README.md +628 -45
  3. package/dist/cjs/browser/index.js +3 -0
  4. package/dist/cjs/browser/index.js.LICENSE.txt +6 -0
  5. package/dist/cjs/browser/index.js.map +1 -0
  6. package/dist/cjs/index.js +1 -1
  7. package/dist/cjs/index.js.LICENSE.txt +1 -1
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/vega/index.js +3 -0
  10. package/dist/cjs/vega/index.js.LICENSE.txt +6 -0
  11. package/dist/cjs/vega/index.js.map +1 -0
  12. package/dist/esm/browser/index.js +3 -0
  13. package/dist/esm/browser/index.js.LICENSE.txt +6 -0
  14. package/dist/esm/browser/index.js.map +1 -0
  15. package/dist/esm/index.js +1 -1
  16. package/dist/esm/index.js.LICENSE.txt +1 -1
  17. package/dist/esm/index.js.map +1 -1
  18. package/dist/esm/vega/index.js +3 -0
  19. package/dist/esm/vega/index.js.LICENSE.txt +6 -0
  20. package/dist/esm/vega/index.js.map +1 -0
  21. package/dist/umd/nrvideo.min.js +1 -1
  22. package/dist/umd/nrvideo.min.js.LICENSE.txt +1 -1
  23. package/dist/umd/nrvideo.min.js.map +1 -1
  24. package/package.json +31 -6
  25. package/src/{agent.js → browser/agent.js} +43 -8
  26. package/src/{harvestScheduler.js → browser/harvestScheduler.js} +87 -86
  27. package/src/browser/index.js +58 -0
  28. package/src/connectedDevice/connectedDeviceAgent.js +116 -0
  29. package/src/connectedDevice/connectedDeviceConstants.js +121 -0
  30. package/src/connectedDevice/connectedDeviceHarvester.js +522 -0
  31. package/src/connectedDevice/index.js +49 -0
  32. package/src/constants.js +8 -0
  33. package/src/core.js +4 -18
  34. package/src/eventAggregator.js +48 -0
  35. package/src/index.js +9 -10
  36. package/src/obfuscate.js +33 -0
  37. package/src/optimizedHttpClient.js +6 -2
  38. package/src/recordEvent.js +56 -49
  39. package/src/tracker.js +14 -4
  40. package/src/utils/eventBuilder.js +126 -0
  41. package/src/utils/harvestTimer.js +109 -0
  42. package/src/{utils.js → utils/index.js} +2 -2
  43. package/src/utils/qoeFilters.js +149 -0
  44. package/src/videoConfiguration.js +90 -7
  45. package/src/videotracker.js +100 -32
  46. package/src/videotrackerstate.js +156 -31
package/package.json CHANGED
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "name": "@newrelic/video-core",
3
- "version": "4.1.8-beta",
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,21 +41,28 @@
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",
32
- "@newrelic/newrelic-oss-cli": "^0.1.2",
33
48
  "@babel/preset-env": "^7.24.5",
34
49
  "@babel/register": "^7.24.6",
35
- "aws-sdk": "^2.920.0",
36
- "babel-jest": "^30.2.0",
50
+ "@newrelic/newrelic-oss-cli": "^0.1.2",
51
+ "@semantic-release/changelog": "^6.0.3",
52
+ "@semantic-release/commit-analyzer": "^12.0.0",
53
+ "@semantic-release/git": "^10.0.1",
54
+ "@semantic-release/github": "^10.0.3",
55
+ "@semantic-release/npm": "^13.1.5",
56
+ "@semantic-release/release-notes-generator": "^13.0.0",
57
+ "babel-jest": "^30.0.0",
37
58
  "babel-loader": "^9.1.3",
38
59
  "diff": "^5.0.0",
39
60
  "jest": "^30.2.0",
40
61
  "jest-environment-jsdom": "^30.2.0",
41
62
  "jsdom": "^25.0.1",
42
- "mocha": "^10.4.0",
43
- "nyc": "^15.1.0",
63
+ "mocha": "^11.0.0",
64
+ "nyc": "^18.0.0",
65
+ "semantic-release": "^25.0.8",
44
66
  "sinon": "^2.4.1",
45
67
  "webpack": "^5.91.0",
46
68
  "webpack-cli": "^4.9.2"
@@ -58,5 +80,8 @@
58
80
  ],
59
81
  "publishConfig": {
60
82
  "access": "public"
83
+ },
84
+ "dependencies": {
85
+ "terser-webpack-plugin": "^5.6.1"
61
86
  }
62
87
  }
@@ -1,7 +1,11 @@
1
1
  import { HarvestScheduler } from "./harvestScheduler.js";
2
- import { NrVideoEventAggregator } from "./eventAggregator.js";
3
- import Log from "./log.js";
4
- 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";
5
9
 
6
10
  /**
7
11
  * Enhanced video analytics agent with HarvestScheduler only.
@@ -48,11 +52,8 @@ class VideoAnalyticsAgent {
48
52
  }
49
53
 
50
54
  try {
51
- if(eventObject.actionName && eventObject.actionName === Tracker.Events.QOE_AGGREGATE) {
52
- // This makes sure that there is only one QOE aggregate event for a harvest cycle
53
- return this.eventBuffer.addOrReplaceByActionName(Tracker.Events.QOE_AGGREGATE, eventObject);
54
- }
55
- return this.eventBuffer.add(eventObject);
55
+ // QOE_AGGREGATE dedup + plain append, shared with the Vega pipeline.
56
+ return bufferEventWithQoeDedup(this.eventBuffer, eventObject);
56
57
  } catch (error) {
57
58
  Log.error("Failed to add event to harvesting system:", error.message);
58
59
  return false;
@@ -71,10 +72,44 @@ class VideoAnalyticsAgent {
71
72
 
72
73
  this.harvestScheduler.updateHarvestInterval(interval);
73
74
  }
75
+
76
+ /**
77
+ * Forces the next harvest cycle to include QOE_AGGREGATE events.
78
+ * Called at CONTENT_END to ensure final QoE is sent.
79
+ */
80
+ forceNextQoeCycle() {
81
+ if (this.harvestScheduler) {
82
+ this.harvestScheduler.forceNextQoeCycle = true;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Sets a callback to be called before each drain to refresh QoE KPIs.
88
+ * @param {Function|null} callback - Function that refreshes QoE data in the buffer, or null to clear
89
+ */
90
+ setBeforeDrainCallback(callback) {
91
+ if (this.harvestScheduler) {
92
+ this.harvestScheduler.beforeDrainCallback = callback;
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Updates QoE KPI fields on the existing QOE_AGGREGATE event in the buffer.
98
+ * Scoped to a specific viewId to support multiple players on the same page.
99
+ * @param {object} freshKpis - Object with latest KPI values
100
+ * @param {string} [viewId] - The viewId of the player whose QoE event to update
101
+ */
102
+ refreshQoeKpis(freshKpis, viewId) {
103
+ refreshQoeKpisInBuffer(this.eventBuffer, freshKpis, viewId);
104
+ }
74
105
  }
75
106
 
76
107
  // Create singleton instance
77
108
  const videoAnalyticsAgent = new VideoAnalyticsAgent();
78
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
+
79
114
  // Enhanced video analytics harvester
80
115
  export const videoAnalyticsHarvester = videoAnalyticsAgent;
@@ -1,9 +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 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";
7
13
 
8
14
  /**
9
15
  * Enhanced harvest scheduler that orchestrates the video analytics data collection,
@@ -27,49 +33,55 @@ export class HarvestScheduler {
27
33
  }
28
34
 
29
35
  // Scheduler state
30
- this.isStarted = false;
31
- this.currentTimerId = null;
32
36
  this.harvestCycle = Constants.INTERVAL;
33
37
  this.isHarvesting = false;
38
+ this.qoeCycleCount = 1;
39
+ this.forceNextQoeCycle = false;
40
+ this.beforeDrainCallback = null;
41
+ this._lastSentQoeKpis = {};
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
+ });
34
49
 
35
50
  // Page lifecycle handling
36
51
  this.setupPageLifecycleHandlers();
37
52
  }
38
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
+
39
62
  /**
40
63
  * Starts the harvest scheduler.
41
64
  */
42
65
  startScheduler() {
43
- if (this.isStarted) {
66
+ if (this.timer.isRunning()) {
44
67
  Log.warn("Harvest scheduler is already started");
45
68
  return;
46
69
  }
47
70
 
48
- this.isStarted = true;
49
-
50
71
  Log.notice("Starting harvest scheduler", {
51
72
  harvestCycle: this.harvestCycle,
52
73
  eventBufferSize: this.eventBuffer ? this.eventBuffer.size() : 0,
53
74
  });
54
75
 
55
- this.scheduleNextHarvest();
76
+ this.timer.start();
56
77
  }
57
78
 
58
79
  /**
59
80
  * Stops the harvest scheduler.
60
81
  */
61
82
  stopScheduler() {
62
- if (!this.isStarted) {
63
- return;
64
- }
65
-
66
- this.isStarted = false;
67
-
68
- if (this.currentTimerId) {
69
- clearTimeout(this.currentTimerId);
70
- this.currentTimerId = null;
71
- }
72
-
83
+ if (!this.timer.isRunning()) return;
84
+ this.timer.stop();
73
85
  Log.notice("Harvest scheduler stopped");
74
86
  }
75
87
 
@@ -87,53 +99,30 @@ export class HarvestScheduler {
87
99
  // If buffer is empty, abort harvest
88
100
  if (!this.eventBuffer || this.eventBuffer.isEmpty()) return;
89
101
 
90
- // Clear existing timer to prevent redundant harvests
91
- if (this.currentTimerId) {
92
- clearTimeout(this.currentTimerId);
93
- this.currentTimerId = null;
94
- }
95
-
96
102
  try {
97
103
  await this.triggerHarvest({});
98
104
  } catch (error) {
99
105
  Log.error(`${type} harvest failed:`, error.message);
100
106
  } finally {
101
- // Schedule next harvest after smart harvest completes
102
- if (this.isStarted) {
103
- this.scheduleNextHarvest();
104
- }
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();
105
110
  }
106
111
  }
107
112
 
108
113
  /**
109
- * Schedules the next harvest based on current conditions.
110
- * @private
111
- */
112
- scheduleNextHarvest() {
113
- if (!this.isStarted) return;
114
-
115
- const interval = this.harvestCycle;
116
- this.currentTimerId = setTimeout(() => this.onHarvestInterval(), interval);
117
- }
118
-
119
- /**
120
- * Handles the harvest interval timer.
114
+ * Periodic-tick callback. Invoked by the shared harvest timer on each cycle.
121
115
  * @private
122
116
  */
123
117
  async onHarvestInterval() {
124
- try {
125
- // Check if there's any data to harvest (buffer or retry queue) before starting the harvest process
126
- const hasBufferData = this.eventBuffer && !this.eventBuffer.isEmpty();
127
- const hasRetryData =
128
- this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0;
129
-
130
- if (!hasBufferData && !hasRetryData) return;
131
- await this.triggerHarvest({});
132
- } catch (error) {
133
- Log.error("Error during scheduled harvest:", error.message);
134
- } finally {
135
- this.scheduleNextHarvest();
136
- }
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({});
137
126
  }
138
127
 
139
128
  /**
@@ -180,7 +169,6 @@ export class HarvestScheduler {
180
169
  return {
181
170
  success: false,
182
171
  error: error.message,
183
- consecutiveFailures: this.consecutiveFailures,
184
172
  };
185
173
  } finally {
186
174
  this.isHarvesting = false;
@@ -237,15 +225,47 @@ export class HarvestScheduler {
237
225
  /**
238
226
  * Drains events from the event buffer and optionally includes retry queue data.
239
227
  * Uses fresh-events-first approach with payload limits.
228
+ * Filters out QOE_AGGREGATE events based on the harvest interval multiplier,
229
+ * always including them on the first and final harvest cycles.
240
230
  * @param {object} options - Harvest options
241
231
  * @returns {Array} Drained events
242
232
  * @private
243
233
  */
244
- drainEvents() {
234
+ drainEvents(options = {}) {
235
+ // Determine if this cycle should include the QOE_AGGREGATE event
236
+ const multiplier = window.NRVIDEO?.config?.qoeIntervalFactor ?? 1;
237
+ const isForced = !!options.isFinalHarvest || this.forceNextQoeCycle;
238
+ const isQoeCycle =
239
+ (this.qoeCycleCount - 1) % multiplier === 0 || isForced;
240
+
241
+ // Reset force flag after use
242
+ if (this.forceNextQoeCycle) this.forceNextQoeCycle = false;
243
+
244
+ // Refresh QoE KPIs with latest tracker state before draining
245
+ if (this.beforeDrainCallback && typeof this.beforeDrainCallback === 'function') {
246
+ try {
247
+ this.beforeDrainCallback();
248
+ } catch (e) {
249
+ Log.error("Before drain callback failed:", e.message);
250
+ }
251
+ }
252
+
245
253
  // Always drain fresh events first (priority approach)
246
254
  const freshEvents = this.eventBuffer.drain();
247
- let events = [...freshEvents];
248
- let currentPayloadSize = dataSize(freshEvents);
255
+ let filteredFreshEvents = partitionByQoeCycle(
256
+ freshEvents,
257
+ isQoeCycle,
258
+ this.eventBuffer
259
+ );
260
+
261
+ this.qoeCycleCount++;
262
+
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);
266
+
267
+ let events = [...filteredFreshEvents];
268
+ let currentPayloadSize = dataSize(filteredFreshEvents);
249
269
 
250
270
  // Always check retry queue if it has data - no flags needed
251
271
  if (this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0) {
@@ -336,12 +356,7 @@ export class HarvestScheduler {
336
356
  * @private
337
357
  */
338
358
  handleHarvestFailure(error) {
339
- this.consecutiveFailures++;
340
-
341
- Log.warn("Harvest failure handled", {
342
- error: error.message,
343
- consecutiveFailures: this.consecutiveFailures,
344
- });
359
+ Log.warn("Harvest failure handled", { error: error.message });
345
360
  }
346
361
 
347
362
  /**
@@ -351,7 +366,7 @@ export class HarvestScheduler {
351
366
  */
352
367
 
353
368
  updateHarvestInterval(newInterval) {
354
- if (typeof newInterval !== "number" && isNaN(newInterval)) {
369
+ if (typeof newInterval !== "number" || isNaN(newInterval)) {
355
370
  Log.warn("Invalid newInterval provided to updateHarvestInterval");
356
371
  return;
357
372
  }
@@ -362,26 +377,11 @@ export class HarvestScheduler {
362
377
  }
363
378
 
364
379
  // Check if the interval has actually changed to avoid unnecessary actions
365
- if (this.harvestCycle === newInterval) {
366
- return;
367
- }
380
+ if (this.harvestCycle === newInterval) return;
368
381
 
369
- // 1. Update the harvestCycle property with the new interval
370
382
  this.harvestCycle = newInterval;
371
383
  Log.notice("Updated harvestCycle:", this.harvestCycle);
372
-
373
- // 2. Clear the existing timer
374
- if (this.currentTimerId) {
375
- clearTimeout(this.currentTimerId);
376
- this.currentTimerId = null;
377
- }
378
-
379
- // 3. Schedule a new timer with the updated interval
380
- if (this.isStarted) {
381
- this.scheduleNextHarvest();
382
- }
383
-
384
- return;
384
+ this.timer.updateInterval(newInterval);
385
385
  }
386
386
 
387
387
  /**
@@ -413,4 +413,5 @@ export class HarvestScheduler {
413
413
  triggerFinalHarvest();
414
414
  });
415
415
  }
416
+
416
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;