@newrelic/video-core 5.0.1 → 5.1.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/CHANGELOG.md +20 -2
- package/README.md +13 -0
- package/__mock__.js +8 -4
- package/dist/cjs/browser/index.js +1 -1
- package/dist/cjs/browser/index.js.LICENSE.txt +1 -1
- package/dist/cjs/browser/index.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.LICENSE.txt +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/vega/index.js +1 -1
- package/dist/cjs/vega/index.js.LICENSE.txt +1 -1
- package/dist/cjs/vega/index.js.map +1 -1
- package/dist/esm/browser/index.js +1 -1
- package/dist/esm/browser/index.js.LICENSE.txt +1 -1
- package/dist/esm/browser/index.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.LICENSE.txt +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/vega/index.js +1 -1
- package/dist/esm/vega/index.js.LICENSE.txt +1 -1
- package/dist/esm/vega/index.js.map +1 -1
- package/dist/types/browser/agent.d.ts +46 -0
- package/dist/types/browser/harvestScheduler.d.ts +125 -0
- package/dist/types/browser/index.d.ts +34 -0
- package/dist/types/chrono.d.ts +52 -0
- package/dist/types/connectedDevice/connectedDeviceAgent.d.ts +63 -0
- package/dist/types/connectedDevice/connectedDeviceConstants.d.ts +51 -0
- package/dist/types/connectedDevice/connectedDeviceHarvester.d.ts +198 -0
- package/dist/types/connectedDevice/index.d.ts +30 -0
- package/dist/types/constants.d.ts +30 -0
- package/dist/types/core.d.ts +54 -0
- package/dist/types/emitter.d.ts +42 -0
- package/dist/types/eventAggregator.d.ts +97 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/log.d.ts +61 -0
- package/dist/types/obfuscate.d.ts +13 -0
- package/dist/types/optimizedHttpClient.d.ts +83 -0
- package/dist/types/recordEvent.d.ts +30 -0
- package/dist/types/retryQueueHandler.d.ts +46 -0
- package/dist/types/tracker.d.ts +176 -0
- package/dist/types/utils/eventBuilder.d.ts +69 -0
- package/dist/types/utils/harvestTimer.d.ts +45 -0
- package/dist/types/utils/index.d.ts +38 -0
- package/dist/types/utils/qoeFilters.d.ts +88 -0
- package/dist/types/videoConfiguration.d.ts +90 -0
- package/dist/types/videotracker.d.ts +366 -0
- package/dist/types/videotrackerstate.d.ts +228 -0
- package/dist/umd/nrvideo.min.js +1 -1
- package/dist/umd/nrvideo.min.js.LICENSE.txt +1 -1
- package/dist/umd/nrvideo.min.js.map +1 -1
- package/package.json +15 -2
- package/src/browser/{agent.js → agent.ts} +21 -16
- package/src/browser/{harvestScheduler.js → harvestScheduler.ts} +51 -24
- package/src/{chrono.js → chrono.ts} +29 -24
- package/src/connectedDevice/{connectedDeviceAgent.js → connectedDeviceAgent.ts} +14 -10
- package/src/connectedDevice/{connectedDeviceConstants.js → connectedDeviceConstants.ts} +5 -3
- package/src/connectedDevice/{connectedDeviceHarvester.js → connectedDeviceHarvester.ts} +71 -34
- package/src/constants.ts +62 -0
- package/src/{core.js → core.ts} +31 -18
- package/src/{emitter.js → emitter.ts} +16 -5
- package/src/{eventAggregator.js → eventAggregator.ts} +33 -19
- package/src/global.d.ts +25 -0
- package/src/{log.js → log.ts} +23 -17
- package/src/{obfuscate.js → obfuscate.ts} +6 -1
- package/src/{optimizedHttpClient.js → optimizedHttpClient.ts} +37 -13
- package/src/{recordEvent.js → recordEvent.ts} +11 -9
- package/src/{retryQueueHandler.js → retryQueueHandler.ts} +17 -12
- package/src/{tracker.js → tracker.ts} +56 -23
- package/src/utils/{eventBuilder.js → eventBuilder.ts} +31 -14
- package/src/utils/{harvestTimer.js → harvestTimer.ts} +21 -4
- package/src/utils/{index.js → index.ts} +14 -14
- package/src/utils/{qoeFilters.js → qoeFilters.ts} +19 -8
- package/src/{videoConfiguration.js → videoConfiguration.ts} +44 -10
- package/src/{videotracker.js → videotracker.ts} +119 -78
- package/src/{videotrackerstate.js → videotrackerstate.ts} +113 -49
- package/src/constants.js +0 -63
- /package/src/browser/{index.js → index.ts} +0 -0
- /package/src/connectedDevice/{index.js → index.ts} +0 -0
- /package/src/{index.js → index.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,26 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newrelic/video-core",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.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
|
+
"types": "./dist/types/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
10
|
+
"types": "./dist/types/index.d.ts",
|
|
9
11
|
"import": "./dist/esm/index.js",
|
|
10
12
|
"require": "./dist/cjs/index.js"
|
|
11
13
|
},
|
|
12
14
|
"./browser": {
|
|
15
|
+
"types": "./dist/types/browser/index.d.ts",
|
|
13
16
|
"import": "./dist/esm/browser/index.js",
|
|
14
17
|
"require": "./dist/cjs/browser/index.js"
|
|
15
18
|
},
|
|
16
19
|
"./vega": {
|
|
20
|
+
"types": "./dist/types/connectedDevice/index.d.ts",
|
|
17
21
|
"import": "./dist/esm/vega/index.js",
|
|
18
22
|
"require": "./dist/cjs/vega/index.js"
|
|
19
23
|
},
|
|
20
24
|
"./package.json": "./package.json"
|
|
21
25
|
},
|
|
22
26
|
"scripts": {
|
|
23
|
-
"build": "webpack --mode production",
|
|
27
|
+
"build": "webpack --mode production && npm run build:types",
|
|
28
|
+
"build:types": "tsc --emitDeclarationOnly",
|
|
24
29
|
"build:dev": "webpack --mode development",
|
|
25
30
|
"watch": "webpack --mode production --progress --color --watch",
|
|
26
31
|
"watch:dev": "webpack --progress --color --watch",
|
|
@@ -46,6 +51,7 @@
|
|
|
46
51
|
"@babel/core": "^7.24.5",
|
|
47
52
|
"@babel/plugin-transform-modules-commonjs": "^7.24.1",
|
|
48
53
|
"@babel/preset-env": "^7.24.5",
|
|
54
|
+
"@babel/preset-typescript": "^7.29.7",
|
|
49
55
|
"@babel/register": "^7.24.6",
|
|
50
56
|
"@newrelic/newrelic-oss-cli": "^0.1.2",
|
|
51
57
|
"@semantic-release/changelog": "^6.0.3",
|
|
@@ -56,6 +62,7 @@
|
|
|
56
62
|
"@semantic-release/release-notes-generator": "^13.0.0",
|
|
57
63
|
"babel-jest": "^30.0.0",
|
|
58
64
|
"babel-loader": "^9.1.3",
|
|
65
|
+
"conventional-changelog-conventionalcommits": "^7.0.2",
|
|
59
66
|
"diff": "^5.0.0",
|
|
60
67
|
"jest": "^30.2.0",
|
|
61
68
|
"jest-environment-jsdom": "^30.2.0",
|
|
@@ -64,6 +71,7 @@
|
|
|
64
71
|
"nyc": "^18.0.0",
|
|
65
72
|
"semantic-release": "^25.0.8",
|
|
66
73
|
"sinon": "^2.4.1",
|
|
74
|
+
"typescript": "^7.0.2",
|
|
67
75
|
"webpack": "^5.91.0",
|
|
68
76
|
"webpack-cli": "^4.9.2"
|
|
69
77
|
},
|
|
@@ -83,5 +91,10 @@
|
|
|
83
91
|
},
|
|
84
92
|
"dependencies": {
|
|
85
93
|
"terser-webpack-plugin": "^5.6.1"
|
|
94
|
+
},
|
|
95
|
+
"overrides": {
|
|
96
|
+
"ip-address": "^10.3.1",
|
|
97
|
+
"serialize-javascript": "^7.0.5",
|
|
98
|
+
"undici": "^6.28.0"
|
|
86
99
|
}
|
|
87
100
|
}
|
|
@@ -1,16 +1,21 @@
|
|
|
1
|
-
import { HarvestScheduler } from "./harvestScheduler
|
|
2
|
-
import { NrVideoEventAggregator } from "../eventAggregator
|
|
3
|
-
import Log from "../log
|
|
4
|
-
import { registerHarvester } from "../recordEvent
|
|
1
|
+
import { HarvestScheduler } from "./harvestScheduler";
|
|
2
|
+
import { NrVideoEventAggregator } from "../eventAggregator";
|
|
3
|
+
import Log from "../log";
|
|
4
|
+
import { registerHarvester } from "../recordEvent";
|
|
5
5
|
import {
|
|
6
6
|
bufferEventWithQoeDedup,
|
|
7
7
|
refreshQoeKpisInBuffer,
|
|
8
8
|
} from "../utils/qoeFilters";
|
|
9
|
+
import { EventAttributes, Harvester } from "../utils/eventBuilder";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Enhanced video analytics agent with HarvestScheduler only.
|
|
12
13
|
*/
|
|
13
|
-
class VideoAnalyticsAgent {
|
|
14
|
+
class VideoAnalyticsAgent implements Harvester {
|
|
15
|
+
isInitialized: boolean;
|
|
16
|
+
harvestScheduler: HarvestScheduler | null;
|
|
17
|
+
eventBuffer: NrVideoEventAggregator | null;
|
|
18
|
+
|
|
14
19
|
constructor() {
|
|
15
20
|
this.isInitialized = false;
|
|
16
21
|
this.harvestScheduler = null;
|
|
@@ -20,7 +25,7 @@ class VideoAnalyticsAgent {
|
|
|
20
25
|
/**
|
|
21
26
|
* Initializes the video analytics agent with enhanced HarvestScheduler.
|
|
22
27
|
*/
|
|
23
|
-
initialize() {
|
|
28
|
+
initialize(): void {
|
|
24
29
|
if (this.isInitialized) {
|
|
25
30
|
Log.warn("Video analytics agent already initialized");
|
|
26
31
|
return;
|
|
@@ -35,7 +40,7 @@ class VideoAnalyticsAgent {
|
|
|
35
40
|
|
|
36
41
|
this.isInitialized = true;
|
|
37
42
|
Log.notice("Video analytics agent initialized successfully");
|
|
38
|
-
} catch (error) {
|
|
43
|
+
} catch (error: any) {
|
|
39
44
|
Log.error("Failed to initialize video analytics agent:", error.message);
|
|
40
45
|
}
|
|
41
46
|
}
|
|
@@ -45,7 +50,7 @@ class VideoAnalyticsAgent {
|
|
|
45
50
|
* @param {object} eventObject - Event to add
|
|
46
51
|
* @returns {boolean} True if event was added successfully
|
|
47
52
|
*/
|
|
48
|
-
addEvent(eventObject) {
|
|
53
|
+
addEvent(eventObject: EventAttributes): boolean {
|
|
49
54
|
if (!this.isInitialized) {
|
|
50
55
|
Log.warn("Video analytics agent not initialized, initializing now");
|
|
51
56
|
this.initialize();
|
|
@@ -53,8 +58,8 @@ class VideoAnalyticsAgent {
|
|
|
53
58
|
|
|
54
59
|
try {
|
|
55
60
|
// QOE_AGGREGATE dedup + plain append, shared with the Vega pipeline.
|
|
56
|
-
return bufferEventWithQoeDedup(this.eventBuffer, eventObject);
|
|
57
|
-
} catch (error) {
|
|
61
|
+
return bufferEventWithQoeDedup(this.eventBuffer as NrVideoEventAggregator, eventObject);
|
|
62
|
+
} catch (error: any) {
|
|
58
63
|
Log.error("Failed to add event to harvesting system:", error.message);
|
|
59
64
|
return false;
|
|
60
65
|
}
|
|
@@ -65,19 +70,19 @@ class VideoAnalyticsAgent {
|
|
|
65
70
|
* @param {number} interval - The harvest interval in milliseconds.
|
|
66
71
|
*/
|
|
67
72
|
|
|
68
|
-
setHarvestInterval(interval) {
|
|
73
|
+
setHarvestInterval(interval: number): void {
|
|
69
74
|
if (!this.isInitialized) {
|
|
70
75
|
this.initialize();
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
this.harvestScheduler.updateHarvestInterval(interval);
|
|
78
|
+
(this.harvestScheduler as HarvestScheduler).updateHarvestInterval(interval);
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
82
|
* Forces the next harvest cycle to include QOE_AGGREGATE events.
|
|
78
83
|
* Called at CONTENT_END to ensure final QoE is sent.
|
|
79
84
|
*/
|
|
80
|
-
forceNextQoeCycle() {
|
|
85
|
+
forceNextQoeCycle(): void {
|
|
81
86
|
if (this.harvestScheduler) {
|
|
82
87
|
this.harvestScheduler.forceNextQoeCycle = true;
|
|
83
88
|
}
|
|
@@ -87,7 +92,7 @@ class VideoAnalyticsAgent {
|
|
|
87
92
|
* Sets a callback to be called before each drain to refresh QoE KPIs.
|
|
88
93
|
* @param {Function|null} callback - Function that refreshes QoE data in the buffer, or null to clear
|
|
89
94
|
*/
|
|
90
|
-
setBeforeDrainCallback(callback) {
|
|
95
|
+
setBeforeDrainCallback(callback: (() => void) | null): void {
|
|
91
96
|
if (this.harvestScheduler) {
|
|
92
97
|
this.harvestScheduler.beforeDrainCallback = callback;
|
|
93
98
|
}
|
|
@@ -99,8 +104,8 @@ class VideoAnalyticsAgent {
|
|
|
99
104
|
* @param {object} freshKpis - Object with latest KPI values
|
|
100
105
|
* @param {string} [viewId] - The viewId of the player whose QoE event to update
|
|
101
106
|
*/
|
|
102
|
-
refreshQoeKpis(freshKpis, viewId) {
|
|
103
|
-
refreshQoeKpisInBuffer(this.eventBuffer, freshKpis, viewId);
|
|
107
|
+
refreshQoeKpis(freshKpis: EventAttributes, viewId?: string): void {
|
|
108
|
+
refreshQoeKpisInBuffer(this.eventBuffer as NrVideoEventAggregator, freshKpis, viewId);
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
111
|
|
|
@@ -6,10 +6,24 @@ import {
|
|
|
6
6
|
partitionByQoeCycle,
|
|
7
7
|
applyQoeDirtyFilter,
|
|
8
8
|
} from "../utils/qoeFilters";
|
|
9
|
-
import { createHarvestTimer } from "../utils/harvestTimer";
|
|
9
|
+
import { createHarvestTimer, HarvestTimer } from "../utils/harvestTimer";
|
|
10
10
|
import Constants from "../constants";
|
|
11
11
|
import Tracker from "../tracker";
|
|
12
12
|
import Log from "../log";
|
|
13
|
+
import { EventAttributes } from "../utils/eventBuilder";
|
|
14
|
+
|
|
15
|
+
interface HarvestOptions {
|
|
16
|
+
isFinalHarvest?: boolean;
|
|
17
|
+
force?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface HarvestResult {
|
|
21
|
+
success: boolean;
|
|
22
|
+
reason?: string;
|
|
23
|
+
totalChunks?: number;
|
|
24
|
+
results?: any[];
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
13
27
|
|
|
14
28
|
/**
|
|
15
29
|
* Enhanced harvest scheduler that orchestrates the video analytics data collection,
|
|
@@ -17,7 +31,20 @@ import Log from "../log";
|
|
|
17
31
|
*/
|
|
18
32
|
|
|
19
33
|
export class HarvestScheduler {
|
|
20
|
-
|
|
34
|
+
eventBuffer: NrVideoEventAggregator;
|
|
35
|
+
retryQueueHandler: RetryQueueHandler;
|
|
36
|
+
httpClient: OptimizedHttpClient;
|
|
37
|
+
fallBackUrl: string;
|
|
38
|
+
retryCount: number;
|
|
39
|
+
harvestCycle: number;
|
|
40
|
+
isHarvesting: boolean;
|
|
41
|
+
qoeCycleCount: number;
|
|
42
|
+
forceNextQoeCycle: boolean;
|
|
43
|
+
beforeDrainCallback: (() => void) | null;
|
|
44
|
+
_lastSentQoeKpis: Record<string, any>;
|
|
45
|
+
timer: HarvestTimer;
|
|
46
|
+
|
|
47
|
+
constructor(eventAggregator: NrVideoEventAggregator) {
|
|
21
48
|
// Core components
|
|
22
49
|
this.eventBuffer = eventAggregator;
|
|
23
50
|
this.retryQueueHandler = new RetryQueueHandler();
|
|
@@ -55,14 +82,14 @@ export class HarvestScheduler {
|
|
|
55
82
|
* Whether the scheduler is currently running. Backed by the shared timer.
|
|
56
83
|
* @returns {boolean}
|
|
57
84
|
*/
|
|
58
|
-
get isStarted() {
|
|
85
|
+
get isStarted(): boolean {
|
|
59
86
|
return this.timer.isRunning();
|
|
60
87
|
}
|
|
61
88
|
|
|
62
89
|
/**
|
|
63
90
|
* Starts the harvest scheduler.
|
|
64
91
|
*/
|
|
65
|
-
startScheduler() {
|
|
92
|
+
startScheduler(): void {
|
|
66
93
|
if (this.timer.isRunning()) {
|
|
67
94
|
Log.warn("Harvest scheduler is already started");
|
|
68
95
|
return;
|
|
@@ -79,7 +106,7 @@ export class HarvestScheduler {
|
|
|
79
106
|
/**
|
|
80
107
|
* Stops the harvest scheduler.
|
|
81
108
|
*/
|
|
82
|
-
stopScheduler() {
|
|
109
|
+
stopScheduler(): void {
|
|
83
110
|
if (!this.timer.isRunning()) return;
|
|
84
111
|
this.timer.stop();
|
|
85
112
|
Log.notice("Harvest scheduler stopped");
|
|
@@ -90,7 +117,7 @@ export class HarvestScheduler {
|
|
|
90
117
|
* @param {string} type - Type of harvest trigger ('smart' or 'overflow')
|
|
91
118
|
* @param {number} threshold - Threshold percentage that triggered the harvest (60 or 90)
|
|
92
119
|
*/
|
|
93
|
-
async triggerSmartHarvest(type, threshold) {
|
|
120
|
+
async triggerSmartHarvest(type: string, threshold: number): Promise<void> {
|
|
94
121
|
Log.notice(`${type} harvest triggered at ${threshold}% threshold`, {
|
|
95
122
|
type,
|
|
96
123
|
threshold,
|
|
@@ -101,7 +128,7 @@ export class HarvestScheduler {
|
|
|
101
128
|
|
|
102
129
|
try {
|
|
103
130
|
await this.triggerHarvest({});
|
|
104
|
-
} catch (error) {
|
|
131
|
+
} catch (error: any) {
|
|
105
132
|
Log.error(`${type} harvest failed:`, error.message);
|
|
106
133
|
} finally {
|
|
107
134
|
// Reset the periodic clock — next periodic tick happens `harvestCycle`
|
|
@@ -114,7 +141,7 @@ export class HarvestScheduler {
|
|
|
114
141
|
* Periodic-tick callback. Invoked by the shared harvest timer on each cycle.
|
|
115
142
|
* @private
|
|
116
143
|
*/
|
|
117
|
-
async onHarvestInterval() {
|
|
144
|
+
async onHarvestInterval(): Promise<void> {
|
|
118
145
|
// Check if there's any data to harvest (buffer or retry queue) before
|
|
119
146
|
// starting the harvest process — avoids unnecessary network calls when
|
|
120
147
|
// there's nothing to ship.
|
|
@@ -132,7 +159,7 @@ export class HarvestScheduler {
|
|
|
132
159
|
* @param {boolean} options.force - Force harvest even if buffer is empty
|
|
133
160
|
* @returns {Promise<object>} Harvest result
|
|
134
161
|
*/
|
|
135
|
-
async triggerHarvest(options = {}) {
|
|
162
|
+
async triggerHarvest(options: HarvestOptions = {}): Promise<HarvestResult> {
|
|
136
163
|
if (this.isHarvesting) {
|
|
137
164
|
return { success: false, reason: "harvest_in_progress" };
|
|
138
165
|
}
|
|
@@ -146,7 +173,7 @@ export class HarvestScheduler {
|
|
|
146
173
|
// For beacon harvests, trim events to fit beacon size if necessary
|
|
147
174
|
if (options.isFinalHarvest) {
|
|
148
175
|
const maxBeaconSize = Constants.MAX_BEACON_SIZE;
|
|
149
|
-
const payloadSize = dataSize(events);
|
|
176
|
+
const payloadSize = dataSize(events) ?? 0;
|
|
150
177
|
|
|
151
178
|
if (payloadSize > maxBeaconSize) {
|
|
152
179
|
// Trim events to fit beacon size (keep most recent events)
|
|
@@ -162,7 +189,7 @@ export class HarvestScheduler {
|
|
|
162
189
|
totalChunks: 1,
|
|
163
190
|
results: [result],
|
|
164
191
|
};
|
|
165
|
-
} catch (error) {
|
|
192
|
+
} catch (error: any) {
|
|
166
193
|
Log.error("Harvest cycle failed:", error.message);
|
|
167
194
|
this.handleHarvestFailure(error);
|
|
168
195
|
|
|
@@ -183,18 +210,18 @@ export class HarvestScheduler {
|
|
|
183
210
|
* @returns {Array} Trimmed events that fit within size limit
|
|
184
211
|
* @private
|
|
185
212
|
*/
|
|
186
|
-
trimEventsToFit(events, maxSize) {
|
|
213
|
+
trimEventsToFit(events: EventAttributes[], maxSize: number): EventAttributes[] {
|
|
187
214
|
if (events.length === 0) return events;
|
|
188
215
|
|
|
189
216
|
// Start from the most recent events (end of array) and work backwards
|
|
190
|
-
const trimmedEvents = [];
|
|
217
|
+
const trimmedEvents: EventAttributes[] = [];
|
|
191
218
|
let currentSize = 0;
|
|
192
219
|
|
|
193
220
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
194
221
|
const event = events[i];
|
|
195
222
|
|
|
196
223
|
// Check if adding this event would exceed the limit
|
|
197
|
-
const testPayloadSize = dataSize({ ins: [event, ...trimmedEvents] });
|
|
224
|
+
const testPayloadSize = dataSize({ ins: [event, ...trimmedEvents] }) ?? 0;
|
|
198
225
|
|
|
199
226
|
if (testPayloadSize > maxSize) continue;
|
|
200
227
|
|
|
@@ -231,7 +258,7 @@ export class HarvestScheduler {
|
|
|
231
258
|
* @returns {Array} Drained events
|
|
232
259
|
* @private
|
|
233
260
|
*/
|
|
234
|
-
drainEvents(options = {}) {
|
|
261
|
+
drainEvents(options: HarvestOptions = {}): EventAttributes[] {
|
|
235
262
|
// Determine if this cycle should include the QOE_AGGREGATE event
|
|
236
263
|
const multiplier = window.NRVIDEO?.config?.qoeIntervalFactor ?? 1;
|
|
237
264
|
const isForced = !!options.isFinalHarvest || this.forceNextQoeCycle;
|
|
@@ -245,7 +272,7 @@ export class HarvestScheduler {
|
|
|
245
272
|
if (this.beforeDrainCallback && typeof this.beforeDrainCallback === 'function') {
|
|
246
273
|
try {
|
|
247
274
|
this.beforeDrainCallback();
|
|
248
|
-
} catch (e) {
|
|
275
|
+
} catch (e: any) {
|
|
249
276
|
Log.error("Before drain callback failed:", e.message);
|
|
250
277
|
}
|
|
251
278
|
}
|
|
@@ -265,7 +292,7 @@ export class HarvestScheduler {
|
|
|
265
292
|
applyQoeDirtyFilter(filteredFreshEvents, this._lastSentQoeKpis, isForced);
|
|
266
293
|
|
|
267
294
|
let events = [...filteredFreshEvents];
|
|
268
|
-
let currentPayloadSize = dataSize(filteredFreshEvents);
|
|
295
|
+
let currentPayloadSize = dataSize(filteredFreshEvents) ?? 0;
|
|
269
296
|
|
|
270
297
|
// Always check retry queue if it has data - no flags needed
|
|
271
298
|
if (this.retryQueueHandler && this.retryQueueHandler.getQueueSize() > 0) {
|
|
@@ -298,8 +325,8 @@ export class HarvestScheduler {
|
|
|
298
325
|
* @returns {Promise<object>} Send result
|
|
299
326
|
* @private
|
|
300
327
|
*/
|
|
301
|
-
async sendChunk(chunk, options, isLastChunk) {
|
|
302
|
-
const url = buildUrl(this.fallBackUrl); //
|
|
328
|
+
async sendChunk(chunk: EventAttributes[], options: HarvestOptions, isLastChunk: boolean): Promise<{ success: boolean; status: number; error?: string; chunk: EventAttributes[]; eventCount: number }> {
|
|
329
|
+
const url = buildUrl(this.fallBackUrl) as string; //
|
|
303
330
|
const payload = { body: { ins: chunk } };
|
|
304
331
|
const requestOptions = {
|
|
305
332
|
url,
|
|
@@ -334,7 +361,7 @@ export class HarvestScheduler {
|
|
|
334
361
|
* @param {Array} chunk - Failed chunk to add to retry queue
|
|
335
362
|
* @private
|
|
336
363
|
*/
|
|
337
|
-
handleRequestFailure(chunk) {
|
|
364
|
+
handleRequestFailure(chunk: EventAttributes[]): void {
|
|
338
365
|
// Add failed events to retry queue
|
|
339
366
|
this.retryQueueHandler.addFailedEvents(chunk);
|
|
340
367
|
// Only apply failover logic for US region
|
|
@@ -346,7 +373,7 @@ export class HarvestScheduler {
|
|
|
346
373
|
this.fallBackUrl = "";
|
|
347
374
|
} else if (this.retryCount >= 2) {
|
|
348
375
|
// Switch to fallback after 2 consecutive failures
|
|
349
|
-
this.fallBackUrl = Constants.COLLECTOR["US"][1];
|
|
376
|
+
this.fallBackUrl = (Constants.COLLECTOR["US"] as string[])[1];
|
|
350
377
|
}
|
|
351
378
|
}
|
|
352
379
|
|
|
@@ -355,7 +382,7 @@ export class HarvestScheduler {
|
|
|
355
382
|
* @param {Error} error - Harvest error
|
|
356
383
|
* @private
|
|
357
384
|
*/
|
|
358
|
-
handleHarvestFailure(error) {
|
|
385
|
+
handleHarvestFailure(error: Error): void {
|
|
359
386
|
Log.warn("Harvest failure handled", { error: error.message });
|
|
360
387
|
}
|
|
361
388
|
|
|
@@ -365,7 +392,7 @@ export class HarvestScheduler {
|
|
|
365
392
|
* @returns {boolean} - True if interval was updated successfully, false otherwise
|
|
366
393
|
*/
|
|
367
394
|
|
|
368
|
-
updateHarvestInterval(newInterval) {
|
|
395
|
+
updateHarvestInterval(newInterval: number): void {
|
|
369
396
|
if (typeof newInterval !== "number" || isNaN(newInterval)) {
|
|
370
397
|
Log.warn("Invalid newInterval provided to updateHarvestInterval");
|
|
371
398
|
return;
|
|
@@ -388,7 +415,7 @@ export class HarvestScheduler {
|
|
|
388
415
|
* Sets up page lifecycle event handlers.
|
|
389
416
|
* @private
|
|
390
417
|
*/
|
|
391
|
-
setupPageLifecycleHandlers() {
|
|
418
|
+
setupPageLifecycleHandlers(): void {
|
|
392
419
|
let finalHarvestTriggered = false;
|
|
393
420
|
|
|
394
421
|
const triggerFinalHarvest = () => {
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
* This class calculates time lapses between two points on time.
|
|
3
3
|
*/
|
|
4
4
|
class Chrono {
|
|
5
|
+
/** Start time */
|
|
6
|
+
startTime: number = 0;
|
|
7
|
+
|
|
8
|
+
/** Stop time */
|
|
9
|
+
stopTime: number = 0;
|
|
10
|
+
|
|
11
|
+
/** accumulation of all the start and stop intervals */
|
|
12
|
+
accumulator: number = 0;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* If you set an offset in a chrono, its value will be added getDeltaTime and stop.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* let chrono = new Chrono()
|
|
19
|
+
* chrono.offset = 500
|
|
20
|
+
* chrono.start()
|
|
21
|
+
* process.sleep(500)
|
|
22
|
+
* chrono.stop() // Will return 1000
|
|
23
|
+
*
|
|
24
|
+
* @type {number}
|
|
25
|
+
*/
|
|
26
|
+
offset: number = 0;
|
|
27
|
+
|
|
5
28
|
/**
|
|
6
29
|
* Constructor
|
|
7
30
|
*/
|
|
@@ -10,28 +33,10 @@ class Chrono {
|
|
|
10
33
|
}
|
|
11
34
|
|
|
12
35
|
/** Reset chrono values. */
|
|
13
|
-
reset() {
|
|
14
|
-
/** Start time */
|
|
36
|
+
reset(): void {
|
|
15
37
|
this.startTime = 0;
|
|
16
|
-
|
|
17
|
-
/** Stop time */
|
|
18
38
|
this.stopTime = 0;
|
|
19
|
-
|
|
20
|
-
/** accumulation of all the start and stop intervals */
|
|
21
39
|
this.accumulator = 0;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* If you set an offset in a chrono, its value will be added getDeltaTime and stop.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* let chrono = new Chrono()
|
|
28
|
-
* chrono.offset = 500
|
|
29
|
-
* chrono.start()
|
|
30
|
-
* process.sleep(500)
|
|
31
|
-
* chrono.stop() // Will return 1000
|
|
32
|
-
*
|
|
33
|
-
* @type {number}
|
|
34
|
-
*/
|
|
35
40
|
this.offset = 0;
|
|
36
41
|
}
|
|
37
42
|
|
|
@@ -40,7 +45,7 @@ class Chrono {
|
|
|
40
45
|
* called.
|
|
41
46
|
* @return {(number|null)} Time lapse in ms.
|
|
42
47
|
*/
|
|
43
|
-
getDeltaTime() {
|
|
48
|
+
getDeltaTime(): number | null {
|
|
44
49
|
if (this.startTime) {
|
|
45
50
|
return this.offset + (new Date().getTime() - this.startTime);
|
|
46
51
|
} else {
|
|
@@ -51,7 +56,7 @@ class Chrono {
|
|
|
51
56
|
/**
|
|
52
57
|
* Starts the chrono.
|
|
53
58
|
*/
|
|
54
|
-
start() {
|
|
59
|
+
start(): void {
|
|
55
60
|
this.startTime = new Date().getTime();
|
|
56
61
|
this.stopTime = 0;
|
|
57
62
|
}
|
|
@@ -60,7 +65,7 @@ class Chrono {
|
|
|
60
65
|
* Stops the timer and returns delta time.
|
|
61
66
|
* @return {(number|null)} Returns the delta time
|
|
62
67
|
*/
|
|
63
|
-
stop() {
|
|
68
|
+
stop(): number | null {
|
|
64
69
|
this.stopTime = new Date().getTime();
|
|
65
70
|
if(this.startTime < this.stopTime) {
|
|
66
71
|
this.accumulator += (this.stopTime - this.startTime);
|
|
@@ -68,7 +73,7 @@ class Chrono {
|
|
|
68
73
|
return this.getDeltaTime();
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
getDuration() {
|
|
76
|
+
getDuration(): number {
|
|
72
77
|
if(this.stopTime) {
|
|
73
78
|
return this.accumulator + this.offset;
|
|
74
79
|
} else {
|
|
@@ -80,7 +85,7 @@ class Chrono {
|
|
|
80
85
|
* Creates a copy of the chrono.
|
|
81
86
|
* @returns {Chrono} Cloned chrono
|
|
82
87
|
*/
|
|
83
|
-
clone() {
|
|
88
|
+
clone(): Chrono {
|
|
84
89
|
var chrono = new Chrono();
|
|
85
90
|
chrono.startTime = this.startTime;
|
|
86
91
|
chrono.stopTime = this.stopTime;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import ConnectedDeviceHarvester from "./connectedDeviceHarvester";
|
|
2
2
|
import Log from "../log";
|
|
3
3
|
import { registerHarvester } from "../recordEvent";
|
|
4
|
+
import { EventAttributes, Harvester } from "../utils/eventBuilder";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Vega-side analytics agent. Mirror of `VideoAnalyticsAgent` in `agent.js`.
|
|
7
8
|
*/
|
|
8
|
-
class ConnectedDeviceAnalyticsAgent {
|
|
9
|
+
class ConnectedDeviceAnalyticsAgent implements Harvester {
|
|
10
|
+
isInitialized: boolean;
|
|
11
|
+
harvester: ConnectedDeviceHarvester | null;
|
|
12
|
+
|
|
9
13
|
constructor() {
|
|
10
14
|
this.isInitialized = false;
|
|
11
15
|
this.harvester = null;
|
|
@@ -16,7 +20,7 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
16
20
|
* constructs the wrapped `ConnectedDeviceHarvester`. If `info` is not yet populated,
|
|
17
21
|
* returns without flipping `isInitialized` so the next `addEvent` retries.
|
|
18
22
|
*/
|
|
19
|
-
initialize() {
|
|
23
|
+
initialize(): void {
|
|
20
24
|
if (this.isInitialized) return;
|
|
21
25
|
|
|
22
26
|
const info = globalThis.__NRVIDEO_CD__?.info;
|
|
@@ -31,7 +35,7 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
31
35
|
this.harvester = new ConnectedDeviceHarvester(info);
|
|
32
36
|
this.isInitialized = true;
|
|
33
37
|
Log.notice("ConnectedDeviceAnalyticsAgent initialized");
|
|
34
|
-
} catch (err) {
|
|
38
|
+
} catch (err: any) {
|
|
35
39
|
Log.error("ConnectedDeviceAnalyticsAgent.initialize failed:", err.message);
|
|
36
40
|
}
|
|
37
41
|
}
|
|
@@ -46,17 +50,17 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
46
50
|
* @param {object} eventObject
|
|
47
51
|
* @returns {boolean}
|
|
48
52
|
*/
|
|
49
|
-
addEvent(eventObject) {
|
|
53
|
+
addEvent(eventObject: EventAttributes): boolean {
|
|
50
54
|
if (!this.isInitialized) this.initialize();
|
|
51
55
|
if (!this.isInitialized) return false;
|
|
52
|
-
return this.harvester.addEvent(eventObject);
|
|
56
|
+
return (this.harvester as ConnectedDeviceHarvester).addEvent(eventObject);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
60
|
* Forwards to the underlying ConnectedDeviceHarvester. No-op if not yet initialized.
|
|
57
61
|
* @see ConnectedDeviceHarvester#forceQoeNextHarvest
|
|
58
62
|
*/
|
|
59
|
-
forceQoeNextHarvest() {
|
|
63
|
+
forceQoeNextHarvest(): void {
|
|
60
64
|
if (this.harvester) this.harvester.forceQoeNextHarvest();
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -65,7 +69,7 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
65
69
|
* single method name on whichever harvester `getHarvester()` returns —
|
|
66
70
|
* Browser exposes `forceNextQoeCycle`, Vega exposes both names.
|
|
67
71
|
*/
|
|
68
|
-
forceNextQoeCycle() {
|
|
72
|
+
forceNextQoeCycle(): void {
|
|
69
73
|
this.forceQoeNextHarvest();
|
|
70
74
|
}
|
|
71
75
|
|
|
@@ -74,7 +78,7 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
74
78
|
* @see ConnectedDeviceHarvester#setBeforeDrainCallback
|
|
75
79
|
* @param {Function|null} cb
|
|
76
80
|
*/
|
|
77
|
-
setBeforeDrainCallback(cb) {
|
|
81
|
+
setBeforeDrainCallback(cb: (() => void) | null): void {
|
|
78
82
|
if (this.harvester) this.harvester.setBeforeDrainCallback(cb);
|
|
79
83
|
}
|
|
80
84
|
|
|
@@ -86,7 +90,7 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
86
90
|
* @param {object} freshKpis
|
|
87
91
|
* @param {string} [viewId]
|
|
88
92
|
*/
|
|
89
|
-
refreshQoeKpis(freshKpis, viewId) {
|
|
93
|
+
refreshQoeKpis(freshKpis: EventAttributes, viewId?: string): void {
|
|
90
94
|
if (!this.isInitialized) this.initialize();
|
|
91
95
|
if (this.harvester) this.harvester.refreshQoeKpis(freshKpis, viewId);
|
|
92
96
|
}
|
|
@@ -98,7 +102,7 @@ class ConnectedDeviceAnalyticsAgent {
|
|
|
98
102
|
*
|
|
99
103
|
* @param {number} interval
|
|
100
104
|
*/
|
|
101
|
-
setHarvestInterval(interval) {
|
|
105
|
+
setHarvestInterval(interval: number): void {
|
|
102
106
|
if (!this.isInitialized) this.initialize();
|
|
103
107
|
if (this.harvester) this.harvester.setHarvestInterval(interval);
|
|
104
108
|
}
|
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
// Only `connectedDeviceHarvester.js` imports these — browser builds
|
|
7
7
|
// never reference this file, keeping the browser bundle lean.
|
|
8
8
|
|
|
9
|
+
export type VegaEndpoint = "us" | "eu" | "staging" | "gov" | "jp";
|
|
10
|
+
|
|
9
11
|
/**
|
|
10
12
|
* Maps region key (lowercase) directly to the regional mobile collector base URL.
|
|
11
13
|
* Accepts global.region.key values: us, eu, gov, jp, staging.
|
|
12
14
|
*/
|
|
13
|
-
export const ENDPOINT_URL = {
|
|
15
|
+
export const ENDPOINT_URL: Record<VegaEndpoint, string> = {
|
|
14
16
|
us: "https://mobile-collector.newrelic.com/mobile",
|
|
15
17
|
eu: "https://mobile-collector.eu01.nr-data.net/mobile",
|
|
16
18
|
staging: "https://staging-mobile-collector.newrelic.com/mobile",
|
|
@@ -55,7 +57,7 @@ export const CD_DATA_TIMEOUT_MS = 30000;
|
|
|
55
57
|
* `agentName='AndroidAgent'` for collector auth; the real device identity
|
|
56
58
|
* is recorded in slot [1] / slot [8] of `/v3/data`.
|
|
57
59
|
*/
|
|
58
|
-
export const CD_DATA_TOKENS_PAYLOAD = [
|
|
60
|
+
export const CD_DATA_TOKENS_PAYLOAD: any[] = [
|
|
59
61
|
// appInfo
|
|
60
62
|
[
|
|
61
63
|
"newrelic_mobile_example", // appName
|
|
@@ -86,7 +88,7 @@ export const CD_DATA_TOKENS_PAYLOAD = [
|
|
|
86
88
|
* (Vega + Amazon by default). Customer-supplied `info.deviceInfo` overrides
|
|
87
89
|
* the runtime fields; static slots (osName, agentName, etc.) stay fixed.
|
|
88
90
|
*/
|
|
89
|
-
export const CD_DEVICE_INFO = [
|
|
91
|
+
export const CD_DEVICE_INFO: any[] = [
|
|
90
92
|
"Vega", // osName
|
|
91
93
|
"1.0", // osVersion
|
|
92
94
|
"VegaDevice", // deviceModel
|