@newrelic/video-core 5.0.2 → 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 +11 -0
- package/README.md +13 -0
- 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 +14 -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
|
@@ -3,6 +3,13 @@ import Emitter from "./emitter";
|
|
|
3
3
|
import Chrono from "./chrono";
|
|
4
4
|
import Constants from "./constants";
|
|
5
5
|
import Log from "./log";
|
|
6
|
+
import { Harvester } from "./utils/eventBuilder";
|
|
7
|
+
|
|
8
|
+
export interface TrackerOptions {
|
|
9
|
+
heartbeat?: number;
|
|
10
|
+
customData?: Record<string, any>;
|
|
11
|
+
parentTracker?: Tracker;
|
|
12
|
+
}
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* Tracker class provides the basic logic to extend Newrelic's Browser Agent capabilities.
|
|
@@ -17,12 +24,31 @@ import Log from "./log";
|
|
|
17
24
|
* @extends Emitter
|
|
18
25
|
*/
|
|
19
26
|
class Tracker extends Emitter {
|
|
27
|
+
static Events: Record<string, string>;
|
|
28
|
+
|
|
29
|
+
customData: Record<string, any>;
|
|
30
|
+
heartbeat: number | null;
|
|
31
|
+
parentTracker: Tracker | null;
|
|
32
|
+
/**
|
|
33
|
+
* Set externally by subclasses (VideoTracker, which assigns a real
|
|
34
|
+
* VideoTrackerState instance) / tests (which assign plain `{ _isAd }`
|
|
35
|
+
* objects) before any method that reads `this.state._isAd` is invoked.
|
|
36
|
+
* Not initialized in the base constructor. Loosely typed (`any`) since
|
|
37
|
+
* both shapes need to be assignable here without a circular import of
|
|
38
|
+
* VideoTrackerState into the base Tracker.
|
|
39
|
+
*/
|
|
40
|
+
state?: any;
|
|
41
|
+
_trackerReadyChrono: Chrono;
|
|
42
|
+
_actionTable: any;
|
|
43
|
+
_actionAdTable: any;
|
|
44
|
+
_heartbeatInterval?: ReturnType<typeof setInterval>;
|
|
45
|
+
|
|
20
46
|
/**
|
|
21
47
|
* Constructor, receives options. You should call {@see registerListeners} after this.
|
|
22
48
|
*
|
|
23
49
|
* @param {Object} [options] Options for the tracker. See {@link setOptions}.
|
|
24
50
|
*/
|
|
25
|
-
constructor(options) {
|
|
51
|
+
constructor(options?: TrackerOptions) {
|
|
26
52
|
super();
|
|
27
53
|
|
|
28
54
|
/**
|
|
@@ -56,8 +82,8 @@ class Tracker extends Emitter {
|
|
|
56
82
|
/**
|
|
57
83
|
* Store the initial table of actions with time 0 ms
|
|
58
84
|
*/
|
|
59
|
-
this._actionTable = Constants.ACTION_TABLE;
|
|
60
|
-
this._actionAdTable = Constants.ACTION_AD_TABLE;
|
|
85
|
+
this._actionTable = (Constants as any).ACTION_TABLE;
|
|
86
|
+
this._actionAdTable = (Constants as any).ACTION_AD_TABLE;
|
|
61
87
|
|
|
62
88
|
options = options || {};
|
|
63
89
|
this.setOptions(options);
|
|
@@ -71,7 +97,7 @@ class Tracker extends Emitter {
|
|
|
71
97
|
* @param {Object} [options.customData] Set custom data. See {@link customData}.
|
|
72
98
|
* @param {Tracker} [options.parentTracker] Set parent tracker. See {@link parentTracker}.
|
|
73
99
|
*/
|
|
74
|
-
setOptions(options) {
|
|
100
|
+
setOptions(options?: TrackerOptions): void {
|
|
75
101
|
if (options) {
|
|
76
102
|
if (options.parentTracker) this.parentTracker = options.parentTracker;
|
|
77
103
|
if (options.customData) this.customData = options.customData;
|
|
@@ -82,7 +108,7 @@ class Tracker extends Emitter {
|
|
|
82
108
|
/**
|
|
83
109
|
* Prepares tracker to dispose. Calls {@see unregisterListeners} and drops references.
|
|
84
110
|
*/
|
|
85
|
-
dispose() {
|
|
111
|
+
dispose(): void {
|
|
86
112
|
this.unregisterListeners();
|
|
87
113
|
}
|
|
88
114
|
|
|
@@ -100,7 +126,7 @@ class Tracker extends Emitter {
|
|
|
100
126
|
* }
|
|
101
127
|
* }
|
|
102
128
|
*/
|
|
103
|
-
registerListeners() {}
|
|
129
|
+
registerListeners(): void {}
|
|
104
130
|
|
|
105
131
|
/**
|
|
106
132
|
* Override this method to unregister listeners to third party elements created with
|
|
@@ -121,15 +147,15 @@ class Tracker extends Emitter {
|
|
|
121
147
|
* }
|
|
122
148
|
* }
|
|
123
149
|
*/
|
|
124
|
-
unregisterListeners() {}
|
|
150
|
+
unregisterListeners(): void {}
|
|
125
151
|
|
|
126
152
|
/**
|
|
127
153
|
* Returns heartbeat time interval. 30000 (30s) if not set. See {@link setOptions}.
|
|
128
154
|
* @return {number} Heartbeat interval in ms.
|
|
129
155
|
* @final
|
|
130
156
|
*/
|
|
131
|
-
getHeartbeat() {
|
|
132
|
-
if (this.state
|
|
157
|
+
getHeartbeat(): number {
|
|
158
|
+
if (this.state?._isAd) {
|
|
133
159
|
// modifying heartbeat for Ad Tracker
|
|
134
160
|
return 2000;
|
|
135
161
|
} else {
|
|
@@ -147,7 +173,7 @@ class Tracker extends Emitter {
|
|
|
147
173
|
* Starts heartbeating. Interval period set by options.heartbeat. Min 2000 ms.
|
|
148
174
|
* This method is automaticaly called by the tracker once sendRequest is called.
|
|
149
175
|
*/
|
|
150
|
-
startHeartbeat() {
|
|
176
|
+
startHeartbeat(): void {
|
|
151
177
|
this._heartbeatInterval = setInterval(
|
|
152
178
|
this.sendHeartbeat.bind(this),
|
|
153
179
|
Math.max(this.getHeartbeat(), 2000)
|
|
@@ -157,7 +183,7 @@ class Tracker extends Emitter {
|
|
|
157
183
|
/**
|
|
158
184
|
* Stops heartbeating. This method is automaticaly called by the tracker.
|
|
159
185
|
*/
|
|
160
|
-
stopHeartbeat() {
|
|
186
|
+
stopHeartbeat(): void {
|
|
161
187
|
if (this._heartbeatInterval) {
|
|
162
188
|
clearInterval(this._heartbeatInterval);
|
|
163
189
|
}
|
|
@@ -181,7 +207,7 @@ class Tracker extends Emitter {
|
|
|
181
207
|
*
|
|
182
208
|
* @param {Object} [att] Collection of key:value attributes to send with the request.
|
|
183
209
|
*/
|
|
184
|
-
sendHeartbeat(att) {
|
|
210
|
+
sendHeartbeat(att?: Record<string, any>): void {
|
|
185
211
|
this.sendVideoAction(Tracker.Events.HEARTBEAT, att);
|
|
186
212
|
}
|
|
187
213
|
|
|
@@ -201,7 +227,7 @@ class Tracker extends Emitter {
|
|
|
201
227
|
* @return {object} Filled attributes
|
|
202
228
|
* @final
|
|
203
229
|
*/
|
|
204
|
-
getAttributes(att, eventType) {
|
|
230
|
+
getAttributes(att?: Record<string, any>, eventType?: string): Record<string, any> {
|
|
205
231
|
att = att || {};
|
|
206
232
|
att.trackerName = this.getTrackerName();
|
|
207
233
|
att.trackerVersion = this.getTrackerVersion();
|
|
@@ -220,12 +246,12 @@ class Tracker extends Emitter {
|
|
|
220
246
|
}
|
|
221
247
|
|
|
222
248
|
/** Override to change of the Version of tracker. ie: '1.0.1' */
|
|
223
|
-
getTrackerVersion() {
|
|
249
|
+
getTrackerVersion(): string {
|
|
224
250
|
return pkg.version;
|
|
225
251
|
}
|
|
226
252
|
|
|
227
253
|
/** Override to change of the Name of the tracker. ie: 'custom-html5' */
|
|
228
|
-
getTrackerName() {
|
|
254
|
+
getTrackerName(): string {
|
|
229
255
|
return "base-tracker";
|
|
230
256
|
}
|
|
231
257
|
|
|
@@ -240,20 +266,20 @@ class Tracker extends Emitter {
|
|
|
240
266
|
* @param {object} [att] Key:value dictionary filled with attributes.
|
|
241
267
|
*/
|
|
242
268
|
|
|
243
|
-
sendVideoAction(event, att) {
|
|
269
|
+
sendVideoAction(event: string, att?: Record<string, any>): void {
|
|
244
270
|
this.emit("VideoAction", event, this.getAttributes(att));
|
|
245
271
|
}
|
|
246
272
|
|
|
247
|
-
sendVideoAdAction(event, att) {
|
|
273
|
+
sendVideoAdAction(event: string, att?: Record<string, any>): void {
|
|
248
274
|
this.emit("VideoAdAction", event, this.getAttributes(att));
|
|
249
275
|
}
|
|
250
276
|
|
|
251
|
-
sendVideoErrorAction(event, att) {
|
|
252
|
-
let ev = this.isAd() ? "adError" : "videoError";
|
|
277
|
+
sendVideoErrorAction(event: string, att?: Record<string, any>): void {
|
|
278
|
+
let ev = (this as any).isAd() ? "adError" : "videoError";
|
|
253
279
|
this.emit("VideoErrorAction", event, this.getAttributes(att, ev));
|
|
254
280
|
}
|
|
255
281
|
|
|
256
|
-
sendVideoCustomAction(event, att) {
|
|
282
|
+
sendVideoCustomAction(event: string, att?: Record<string, any>): void {
|
|
257
283
|
this.emit(
|
|
258
284
|
"VideoCustomAction",
|
|
259
285
|
event,
|
|
@@ -267,7 +293,7 @@ class Tracker extends Emitter {
|
|
|
267
293
|
* @returns {void}
|
|
268
294
|
*/
|
|
269
295
|
|
|
270
|
-
setHarvestInterval(interval) {
|
|
296
|
+
setHarvestInterval(interval: number): void {
|
|
271
297
|
const harvester = this.getHarvester?.();
|
|
272
298
|
if (!harvester) {
|
|
273
299
|
Log.error("Tracker has no harvester; setHarvestInterval ignored");
|
|
@@ -276,7 +302,7 @@ class Tracker extends Emitter {
|
|
|
276
302
|
|
|
277
303
|
try {
|
|
278
304
|
harvester.setHarvestInterval(interval);
|
|
279
|
-
} catch (error) {
|
|
305
|
+
} catch (error: any) {
|
|
280
306
|
Log.error("Failed to set harvest interval:", error.message);
|
|
281
307
|
return;
|
|
282
308
|
}
|
|
@@ -288,9 +314,16 @@ class Tracker extends Emitter {
|
|
|
288
314
|
* means a tracker that doesn't override `getHarvester()` will silently no-op
|
|
289
315
|
* on harvester-bound calls (setHarvestInterval, QoE drain wiring).
|
|
290
316
|
*/
|
|
291
|
-
getHarvester() {
|
|
317
|
+
getHarvester(): Harvester | null {
|
|
292
318
|
return null;
|
|
293
319
|
}
|
|
320
|
+
|
|
321
|
+
// Note: base Tracker has no `isAd` member at all (matching original JS —
|
|
322
|
+
// calling sendVideoErrorAction on a bare Tracker throws the native "not a
|
|
323
|
+
// function" error). VideoTracker defines its own `isAd()`; the one call
|
|
324
|
+
// site above (sendVideoErrorAction) casts to `any` rather than declaring
|
|
325
|
+
// an `isAd` member here, since TS treats a `declare`d property and a real
|
|
326
|
+
// subclass method as incompatible override kinds (TS2425).
|
|
294
327
|
}
|
|
295
328
|
|
|
296
329
|
/**
|
|
@@ -2,6 +2,23 @@ import Constants from "../constants";
|
|
|
2
2
|
import Tracker from "../tracker";
|
|
3
3
|
import { getObjectEntriesForKeys } from "./index";
|
|
4
4
|
|
|
5
|
+
export type EventAttributes = Record<string, any>;
|
|
6
|
+
|
|
7
|
+
export interface Harvester {
|
|
8
|
+
addEvent(eventObject: EventAttributes): boolean;
|
|
9
|
+
setHarvestInterval(ms: number): void;
|
|
10
|
+
/** Set (or clear, with `null`) the callback run just before the next harvest drain. */
|
|
11
|
+
setBeforeDrainCallback(callback: (() => void) | null): void;
|
|
12
|
+
/** Merge fresh QoE KPI values into the buffered QOE_AGGREGATE event for a view. */
|
|
13
|
+
refreshQoeKpis(freshKpis: EventAttributes, viewId?: string): void;
|
|
14
|
+
/** Force the next harvest cycle to be treated as a QoE cycle. Only the browser harvester supports this — the connected-device harvester does not implement it. */
|
|
15
|
+
forceNextQoeCycle?(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface BuildEventObjectsOptions {
|
|
19
|
+
addTimeSinceLoad?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
5
22
|
/**
|
|
6
23
|
* Pure helper that constructs the wire-format event objects from `recordEvent`
|
|
7
24
|
* arguments. Used by `src/recordEvent.js`, which is the single shared
|
|
@@ -26,11 +43,11 @@ import { getObjectEntriesForKeys } from "./index";
|
|
|
26
43
|
* @returns {{ eventObject: object, qoeEventObject: object|null }}
|
|
27
44
|
*/
|
|
28
45
|
export function buildEventObjects(
|
|
29
|
-
eventType,
|
|
30
|
-
attributes,
|
|
31
|
-
info,
|
|
32
|
-
{ addTimeSinceLoad = false } = {}
|
|
33
|
-
) {
|
|
46
|
+
eventType: string,
|
|
47
|
+
attributes: EventAttributes,
|
|
48
|
+
info: Record<string, any>,
|
|
49
|
+
{ addTimeSinceLoad = false }: BuildEventObjectsOptions = {}
|
|
50
|
+
): { eventObject: EventAttributes; qoeEventObject: EventAttributes | null } {
|
|
34
51
|
const { appName, applicationID } = info;
|
|
35
52
|
const { qoe, ...eventAttributes } = attributes;
|
|
36
53
|
const qoeAttrs = qoe ? { ...qoe } : {};
|
|
@@ -54,7 +71,7 @@ export function buildEventObjects(
|
|
|
54
71
|
...otherAttrs,
|
|
55
72
|
};
|
|
56
73
|
|
|
57
|
-
let qoeEventObject = null;
|
|
74
|
+
let qoeEventObject: EventAttributes | null = null;
|
|
58
75
|
if (eventType === "VideoAction") {
|
|
59
76
|
const metadataAttributes = getObjectEntriesForKeys(
|
|
60
77
|
Constants.QOE_AGGREGATE_KEYS,
|
|
@@ -94,19 +111,19 @@ export function buildEventObjects(
|
|
|
94
111
|
* @param {object} info - Resolved `info` from the pipeline's config global.
|
|
95
112
|
* @param {{ addEvent: function }} harvester - The registered harvester for
|
|
96
113
|
* the current pipeline. Returns false if null/undefined.
|
|
97
|
-
* @param {boolean} qoeEnabled
|
|
114
|
+
* @param {boolean} qoeEnabled
|
|
98
115
|
* @param {{ addTimeSinceLoad?: boolean }} [opts] - Forwarded to
|
|
99
116
|
* `buildEventObjects`.
|
|
100
117
|
* @returns {boolean}
|
|
101
118
|
*/
|
|
102
119
|
export function dispatchRecordEvent(
|
|
103
|
-
eventType,
|
|
104
|
-
attributes,
|
|
105
|
-
info,
|
|
106
|
-
harvester,
|
|
107
|
-
qoeEnabled,
|
|
108
|
-
{ addTimeSinceLoad = false } = {}
|
|
109
|
-
) {
|
|
120
|
+
eventType: string,
|
|
121
|
+
attributes: EventAttributes,
|
|
122
|
+
info: Record<string, any>,
|
|
123
|
+
harvester: Harvester | null | undefined,
|
|
124
|
+
qoeEnabled: boolean,
|
|
125
|
+
{ addTimeSinceLoad = false }: BuildEventObjectsOptions = {}
|
|
126
|
+
): boolean {
|
|
110
127
|
if (!harvester) return false;
|
|
111
128
|
|
|
112
129
|
const { eventObject, qoeEventObject } = buildEventObjects(
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import Log from "../log";
|
|
2
2
|
|
|
3
|
+
export interface HarvestTimerOptions {
|
|
4
|
+
/** Initial tick interval in ms. */
|
|
5
|
+
interval: number;
|
|
6
|
+
/** Pipeline-specific drain callback. Awaited; the next tick is scheduled in `finally`. */
|
|
7
|
+
onTick: () => Promise<void> | void;
|
|
8
|
+
/** Prefix for the error log when `onTick` throws (e.g. "HarvestScheduler"). */
|
|
9
|
+
errorLabel: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface HarvestTimer {
|
|
13
|
+
start: () => void;
|
|
14
|
+
stop: () => void;
|
|
15
|
+
cancelAndReschedule: () => void;
|
|
16
|
+
updateInterval: (ms: number) => void;
|
|
17
|
+
isRunning: () => boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
3
20
|
/**
|
|
4
21
|
* Periodic harvest timer using chained `setTimeout`. Each tick schedules the
|
|
5
22
|
* next one only after the current `onTick` callback resolves — by construction
|
|
@@ -29,9 +46,9 @@ import Log from "../log";
|
|
|
29
46
|
* isRunning: () => boolean,
|
|
30
47
|
* }}
|
|
31
48
|
*/
|
|
32
|
-
export function createHarvestTimer({ interval, onTick, errorLabel }) {
|
|
49
|
+
export function createHarvestTimer({ interval, onTick, errorLabel }: HarvestTimerOptions): HarvestTimer {
|
|
33
50
|
let isStarted = false;
|
|
34
|
-
let currentTimerId = null;
|
|
51
|
+
let currentTimerId: ReturnType<typeof setTimeout> | null = null;
|
|
35
52
|
let currentInterval = interval;
|
|
36
53
|
|
|
37
54
|
function scheduleNext() {
|
|
@@ -40,7 +57,7 @@ export function createHarvestTimer({ interval, onTick, errorLabel }) {
|
|
|
40
57
|
currentTimerId = null;
|
|
41
58
|
try {
|
|
42
59
|
await onTick();
|
|
43
|
-
} catch (error) {
|
|
60
|
+
} catch (error: any) {
|
|
44
61
|
Log.error(`${errorLabel}: scheduled tick failed:`, error.message);
|
|
45
62
|
} finally {
|
|
46
63
|
if (isStarted) scheduleNext();
|
|
@@ -93,7 +110,7 @@ export function createHarvestTimer({ interval, onTick, errorLabel }) {
|
|
|
93
110
|
* @param {number} ms - New interval in milliseconds. Must be a positive
|
|
94
111
|
* number; invalid values are silently ignored.
|
|
95
112
|
*/
|
|
96
|
-
updateInterval(ms) {
|
|
113
|
+
updateInterval(ms: number) {
|
|
97
114
|
if (typeof ms !== "number" || ms <= 0) return;
|
|
98
115
|
currentInterval = ms;
|
|
99
116
|
if (isStarted && currentTimerId) {
|
|
@@ -6,7 +6,7 @@ import Log from "../log";
|
|
|
6
6
|
* @returns {string} Harvest URL
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
export function buildUrl(fallbackUrl) {
|
|
9
|
+
export function buildUrl(fallbackUrl?: string): string | null {
|
|
10
10
|
try {
|
|
11
11
|
if (!window.NRVIDEO || !window.NRVIDEO.info) {
|
|
12
12
|
throw new Error("NRVIDEO info is not available.");
|
|
@@ -30,7 +30,7 @@ export function buildUrl(fallbackUrl) {
|
|
|
30
30
|
return `https://${
|
|
31
31
|
fallbackUrl ? fallbackUrl : beacon
|
|
32
32
|
}/ins/1/${licenseKey}?&v=${pkg.version}&ref=${window.location.href}&ca=VA`;
|
|
33
|
-
} catch (error) {
|
|
33
|
+
} catch (error: any) {
|
|
34
34
|
console.error(error.message);
|
|
35
35
|
return null; // Return null instead of undefined
|
|
36
36
|
}
|
|
@@ -43,7 +43,7 @@ export function buildUrl(fallbackUrl) {
|
|
|
43
43
|
*/
|
|
44
44
|
const getCircularReplacer = () => {
|
|
45
45
|
const seen = new WeakSet();
|
|
46
|
-
return (key, value) => {
|
|
46
|
+
return (key: string, value: unknown) => {
|
|
47
47
|
if (typeof value === "object" && value !== null) {
|
|
48
48
|
if (seen.has(value)) {
|
|
49
49
|
return;
|
|
@@ -60,16 +60,16 @@ const getCircularReplacer = () => {
|
|
|
60
60
|
* @param {*} val - A value to be converted to a JSON string.
|
|
61
61
|
* @returns {string} A JSON string representation of the value, with circular references handled.
|
|
62
62
|
*/
|
|
63
|
-
function stringify(val) {
|
|
63
|
+
function stringify(val: unknown): string {
|
|
64
64
|
try {
|
|
65
65
|
return JSON.stringify(val, getCircularReplacer()) ?? "";
|
|
66
|
-
} catch (e) {
|
|
66
|
+
} catch (e: any) {
|
|
67
67
|
Log.error("Error stringifying value:", e.message);
|
|
68
68
|
return "";
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function dataSize(data) {
|
|
72
|
+
export function dataSize(data: unknown): number | undefined {
|
|
73
73
|
if (typeof data === "string" && data.length) return data.length;
|
|
74
74
|
if (typeof data !== "object") return undefined;
|
|
75
75
|
// eslint-disable-next-line
|
|
@@ -96,7 +96,7 @@ export function dataSize(data) {
|
|
|
96
96
|
* @param {number} status - HTTP status code
|
|
97
97
|
* @returns {boolean} - True if request should be retried
|
|
98
98
|
*/
|
|
99
|
-
export function shouldRetry(status) {
|
|
99
|
+
export function shouldRetry(status: number): boolean {
|
|
100
100
|
switch (status) {
|
|
101
101
|
case 408: // Request Timeout
|
|
102
102
|
case 429: // Too Many Requests
|
|
@@ -118,7 +118,7 @@ export function shouldRetry(status) {
|
|
|
118
118
|
* @returns {Promise<Blob>} A Promise that resolves with a Blob of the Gzipped data.
|
|
119
119
|
*/
|
|
120
120
|
|
|
121
|
-
export function compressPayload(payload) {
|
|
121
|
+
export function compressPayload(payload: unknown): Promise<Blob> {
|
|
122
122
|
const stringifiedPayload = JSON.stringify(payload);
|
|
123
123
|
const stream = new Blob([stringifiedPayload]).stream();
|
|
124
124
|
const compressionStream = new CompressionStream("gzip");
|
|
@@ -132,7 +132,7 @@ export function compressPayload(payload) {
|
|
|
132
132
|
* @param {Blob|ArrayBuffer|Uint8Array} compressedData - The gzipped data to decompress.
|
|
133
133
|
* @returns {Promise<object>} A Promise that resolves with the decompressed JSON object.
|
|
134
134
|
*/
|
|
135
|
-
export async function decompressPayload(compressedData) {
|
|
135
|
+
export async function decompressPayload(compressedData: Blob | ArrayBuffer | Uint8Array): Promise<unknown> {
|
|
136
136
|
try {
|
|
137
137
|
// Convert different input types to a stream
|
|
138
138
|
let stream;
|
|
@@ -141,7 +141,7 @@ export async function decompressPayload(compressedData) {
|
|
|
141
141
|
} else if (compressedData instanceof ArrayBuffer) {
|
|
142
142
|
stream = new Blob([compressedData]).stream();
|
|
143
143
|
} else if (compressedData instanceof Uint8Array) {
|
|
144
|
-
stream = new Blob([compressedData]).stream();
|
|
144
|
+
stream = new Blob([compressedData as BlobPart]).stream();
|
|
145
145
|
} else {
|
|
146
146
|
throw new Error("Unsupported compressed data type");
|
|
147
147
|
}
|
|
@@ -156,7 +156,7 @@ export async function decompressPayload(compressedData) {
|
|
|
156
156
|
|
|
157
157
|
// Parse JSON
|
|
158
158
|
return JSON.parse(decompressedText);
|
|
159
|
-
} catch (error) {
|
|
159
|
+
} catch (error: any) {
|
|
160
160
|
throw new Error(`Failed to decompress payload: ${error.message}`);
|
|
161
161
|
}
|
|
162
162
|
}
|
|
@@ -173,14 +173,14 @@ export async function decompressPayload(compressedData) {
|
|
|
173
173
|
* const filtered = getObjectEntriesForKeys(['name', 'city'], data);
|
|
174
174
|
* // Returns: { name: 'John', city: 'NYC' }
|
|
175
175
|
*/
|
|
176
|
-
export function getObjectEntriesForKeys(keys, obj) {
|
|
176
|
+
export function getObjectEntriesForKeys(keys: string[], obj: Record<string, unknown>): Record<string, unknown> {
|
|
177
177
|
if(!keys || !Array.isArray(keys) || keys.length === 0) return obj;
|
|
178
178
|
if(!obj || typeof obj !== 'object') return {};
|
|
179
179
|
|
|
180
|
-
return keys.reduce((result, key) => {
|
|
180
|
+
return keys.reduce((result: Record<string, unknown>, key) => {
|
|
181
181
|
if(key in obj) {
|
|
182
182
|
result[key] = obj[key];
|
|
183
183
|
}
|
|
184
184
|
return result;
|
|
185
185
|
}, {});
|
|
186
|
-
}
|
|
186
|
+
}
|
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
import Constants from "../constants";
|
|
18
18
|
import Tracker from "../tracker";
|
|
19
19
|
|
|
20
|
+
type QoeEvent = Record<string, any>;
|
|
21
|
+
type QoeSnapshots = Record<string, Record<string, any>>;
|
|
22
|
+
|
|
23
|
+
interface QoeBuffer {
|
|
24
|
+
addOrReplaceByActionNameAndViewId(actionName: string, viewId: string, event: QoeEvent): boolean;
|
|
25
|
+
addOrReplaceByActionName(actionName: string, event: QoeEvent): boolean;
|
|
26
|
+
add(event: QoeEvent): boolean;
|
|
27
|
+
findByActionNameAndViewId(actionName: string, viewId: string): QoeEvent | null;
|
|
28
|
+
findByActionName(actionName: string): QoeEvent | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Add an event to the buffer with QOE_AGGREGATE dedup. QOE_AGGREGATE events
|
|
22
33
|
* are deduplicated by `(actionName, viewId)` so multiple players sharing one
|
|
@@ -28,7 +39,7 @@ import Tracker from "../tracker";
|
|
|
28
39
|
* @param {object} eventObject
|
|
29
40
|
* @returns {boolean} True if added/replaced.
|
|
30
41
|
*/
|
|
31
|
-
export function bufferEventWithQoeDedup(buffer, eventObject) {
|
|
42
|
+
export function bufferEventWithQoeDedup(buffer: QoeBuffer, eventObject: QoeEvent): boolean {
|
|
32
43
|
if (!eventObject) return false;
|
|
33
44
|
if (eventObject.actionName === Tracker.Events.QOE_AGGREGATE) {
|
|
34
45
|
if (eventObject.viewId) {
|
|
@@ -53,7 +64,7 @@ export function bufferEventWithQoeDedup(buffer, eventObject) {
|
|
|
53
64
|
* @param {object} freshKpis
|
|
54
65
|
* @param {string} [viewId]
|
|
55
66
|
*/
|
|
56
|
-
export function refreshQoeKpisInBuffer(buffer, freshKpis, viewId) {
|
|
67
|
+
export function refreshQoeKpisInBuffer(buffer: QoeBuffer, freshKpis: QoeEvent, viewId?: string): void {
|
|
57
68
|
if (!buffer || !freshKpis) return;
|
|
58
69
|
const existing = viewId
|
|
59
70
|
? buffer.findByActionNameAndViewId(Tracker.Events.QOE_AGGREGATE, viewId)
|
|
@@ -78,7 +89,7 @@ export function refreshQoeKpisInBuffer(buffer, freshKpis, viewId) {
|
|
|
78
89
|
* @param {object} event
|
|
79
90
|
* @returns {boolean}
|
|
80
91
|
*/
|
|
81
|
-
export function qoeKpisUnchanged(snapshots, event) {
|
|
92
|
+
export function qoeKpisUnchanged(snapshots: QoeSnapshots, event: QoeEvent): boolean {
|
|
82
93
|
const snapshot = snapshots[event.viewId];
|
|
83
94
|
if (!snapshot) return false;
|
|
84
95
|
for (const key of Constants.QOE_KPI_KEYS) {
|
|
@@ -94,8 +105,8 @@ export function qoeKpisUnchanged(snapshots, event) {
|
|
|
94
105
|
* @param {Object<string, object>} snapshots - viewId → KPI snapshot (mutated in place)
|
|
95
106
|
* @param {object} event
|
|
96
107
|
*/
|
|
97
|
-
export function saveQoeKpiSnapshot(snapshots, event) {
|
|
98
|
-
const snapshot = {};
|
|
108
|
+
export function saveQoeKpiSnapshot(snapshots: QoeSnapshots, event: QoeEvent): void {
|
|
109
|
+
const snapshot: Record<string, any> = {};
|
|
99
110
|
for (const key of Constants.QOE_KPI_KEYS) {
|
|
100
111
|
snapshot[key] = event[key];
|
|
101
112
|
}
|
|
@@ -112,9 +123,9 @@ export function saveQoeKpiSnapshot(snapshots, event) {
|
|
|
112
123
|
* @param {NrVideoEventAggregator} buffer
|
|
113
124
|
* @returns {object[]}
|
|
114
125
|
*/
|
|
115
|
-
export function partitionByQoeCycle(drained, isQoeCycle, buffer) {
|
|
126
|
+
export function partitionByQoeCycle(drained: QoeEvent[], isQoeCycle: boolean, buffer: QoeBuffer): QoeEvent[] {
|
|
116
127
|
if (isQoeCycle) return drained;
|
|
117
|
-
const filtered = [];
|
|
128
|
+
const filtered: QoeEvent[] = [];
|
|
118
129
|
for (const e of drained) {
|
|
119
130
|
if (e.actionName === Tracker.Events.QOE_AGGREGATE) {
|
|
120
131
|
buffer.add(e);
|
|
@@ -136,7 +147,7 @@ export function partitionByQoeCycle(drained, isQoeCycle, buffer) {
|
|
|
136
147
|
* @param {boolean} isForced - When true, skip the dirty check and ship every
|
|
137
148
|
* QOE_AGGREGATE regardless of KPI sameness (final flush, CONTENT_END, etc.)
|
|
138
149
|
*/
|
|
139
|
-
export function applyQoeDirtyFilter(filtered, snapshots, isForced) {
|
|
150
|
+
export function applyQoeDirtyFilter(filtered: QoeEvent[], snapshots: QoeSnapshots, isForced: boolean): void {
|
|
140
151
|
for (let i = filtered.length - 1; i >= 0; i--) {
|
|
141
152
|
const e = filtered[i];
|
|
142
153
|
if (e.actionName !== Tracker.Events.QOE_AGGREGATE) continue;
|
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
import Log from "./log";
|
|
2
2
|
import Constants from "./constants";
|
|
3
|
-
import { ENDPOINT_URL } from "./connectedDevice/connectedDeviceConstants";
|
|
3
|
+
import { ENDPOINT_URL, VegaEndpoint } from "./connectedDevice/connectedDeviceConstants";
|
|
4
|
+
import { ObfuscationRule } from "./obfuscate";
|
|
4
5
|
|
|
5
6
|
const { COLLECTOR } = Constants;
|
|
6
7
|
|
|
8
|
+
export interface NrVideoBrowserInfo {
|
|
9
|
+
region?: string;
|
|
10
|
+
beacon: string;
|
|
11
|
+
licenseKey: string;
|
|
12
|
+
applicationID?: string;
|
|
13
|
+
appName?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NrVideoBrowserConfig {
|
|
17
|
+
qoeAggregate: boolean;
|
|
18
|
+
qoeIntervalFactor: number;
|
|
19
|
+
obfuscate: ObfuscationRule[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NrVideoCdInfo {
|
|
23
|
+
accountId?: string;
|
|
24
|
+
applicationToken: string;
|
|
25
|
+
endpoint: VegaEndpoint;
|
|
26
|
+
appName?: string;
|
|
27
|
+
applicationID?: string;
|
|
28
|
+
deviceInfo?: unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface NrVideoCdConfig {
|
|
32
|
+
qoeAggregate: boolean;
|
|
33
|
+
qoeIntervalFactor: number;
|
|
34
|
+
obfuscate: ObfuscationRule[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Raw, unvalidated user-supplied config — shape varies by pipeline (`src`). */
|
|
38
|
+
export type RawVideoConfigInfo = Record<string, any>;
|
|
39
|
+
export type RawVideoConfigOptions = Record<string, any>;
|
|
40
|
+
|
|
7
41
|
/**
|
|
8
42
|
* Enhanced video analytics configuration system that extends the existing auth configuration.
|
|
9
43
|
* Provides feature flags, retry policies, and advanced harvesting options.
|
|
@@ -16,7 +50,7 @@ class VideoConfiguration {
|
|
|
16
50
|
* @returns {boolean} True if configuration is valid and set
|
|
17
51
|
*/
|
|
18
52
|
|
|
19
|
-
setConfiguration(userInfo, config, src) {
|
|
53
|
+
setConfiguration(userInfo: RawVideoConfigInfo, config?: RawVideoConfigOptions, src?: string): boolean {
|
|
20
54
|
const validated = src === "Vega"
|
|
21
55
|
? this.validateVegaFields(userInfo)
|
|
22
56
|
: this.validateRequiredFields(userInfo);
|
|
@@ -36,7 +70,7 @@ class VideoConfiguration {
|
|
|
36
70
|
* @param {object} info
|
|
37
71
|
* @returns {boolean} True if valid
|
|
38
72
|
*/
|
|
39
|
-
validateVegaFields(info) {
|
|
73
|
+
validateVegaFields(info: RawVideoConfigInfo): boolean {
|
|
40
74
|
if (!info || typeof info !== "object") {
|
|
41
75
|
Log.error("Configuration must be an object");
|
|
42
76
|
return false;
|
|
@@ -57,7 +91,7 @@ class VideoConfiguration {
|
|
|
57
91
|
* @param {object} config - Configuration to validate
|
|
58
92
|
* @returns {boolean} True if valid
|
|
59
93
|
*/
|
|
60
|
-
validateRequiredFields(info) {
|
|
94
|
+
validateRequiredFields(info: RawVideoConfigInfo): boolean {
|
|
61
95
|
if (!info || typeof info !== "object") {
|
|
62
96
|
Log.error("Configuration must be an object");
|
|
63
97
|
return false;
|
|
@@ -107,7 +141,7 @@ class VideoConfiguration {
|
|
|
107
141
|
* @param {object} config - Config to validate
|
|
108
142
|
* @returns {boolean} True if valid
|
|
109
143
|
*/
|
|
110
|
-
validateConfigFields(config) {
|
|
144
|
+
validateConfigFields(config?: RawVideoConfigOptions): boolean {
|
|
111
145
|
if (config === null || config === undefined) {
|
|
112
146
|
return true;
|
|
113
147
|
}
|
|
@@ -137,7 +171,7 @@ class VideoConfiguration {
|
|
|
137
171
|
* @param {Array} rules - Raw obfuscation rules
|
|
138
172
|
* @returns {Array} Valid rules only
|
|
139
173
|
*/
|
|
140
|
-
filterObfuscateRules(rules) {
|
|
174
|
+
filterObfuscateRules(rules?: any[]): ObfuscationRule[] {
|
|
141
175
|
if (!rules) return [];
|
|
142
176
|
return rules.filter((rule) => {
|
|
143
177
|
const hasRegex = rule.regex !== undefined && (typeof rule.regex === "string" || rule.regex instanceof RegExp);
|
|
@@ -156,7 +190,7 @@ class VideoConfiguration {
|
|
|
156
190
|
* @param {*} value
|
|
157
191
|
* @returns {number}
|
|
158
192
|
*/
|
|
159
|
-
sanitizeQoeIntervalFactor(value) {
|
|
193
|
+
sanitizeQoeIntervalFactor(value: unknown): number {
|
|
160
194
|
if (value === undefined || value === null) return Constants.DEFAULT_QOE_INTERVAL_FACTOR;
|
|
161
195
|
if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
|
|
162
196
|
Log.warn(`Invalid qoeIntervalFactor "${value}" — must be a positive integer. Defaulting to ${Constants.DEFAULT_QOE_INTERVAL_FACTOR}.`);
|
|
@@ -168,7 +202,7 @@ class VideoConfiguration {
|
|
|
168
202
|
* @param {object} userInfo - User provided configuration
|
|
169
203
|
* @param {object} [config] - Optional configuration object
|
|
170
204
|
*/
|
|
171
|
-
initializeGlobalConfig(userInfo, config, src) {
|
|
205
|
+
initializeGlobalConfig(userInfo: RawVideoConfigInfo, config?: RawVideoConfigOptions, src?: string): void {
|
|
172
206
|
// Vega path: write `globalThis.__NRVIDEO_CD__` with info+config only.
|
|
173
207
|
// The harvester is owned by `connectedDeviceAgent.js` as a module singleton — no
|
|
174
208
|
// harvester field on this global.
|
|
@@ -194,7 +228,7 @@ class VideoConfiguration {
|
|
|
194
228
|
let { licenseKey, appName, region, beacon, applicationID } = userInfo;
|
|
195
229
|
|
|
196
230
|
if (region === "US") {
|
|
197
|
-
beacon = Constants.COLLECTOR["US"][0];
|
|
231
|
+
beacon = (Constants.COLLECTOR["US"] as string[])[0];
|
|
198
232
|
} else {
|
|
199
233
|
beacon = beacon || COLLECTOR[region];
|
|
200
234
|
}
|
|
@@ -226,7 +260,7 @@ const videoConfiguration = new VideoConfiguration();
|
|
|
226
260
|
* @param {object} [config] - Optional configuration object
|
|
227
261
|
* @returns {boolean} True if configuration was set successfully
|
|
228
262
|
*/
|
|
229
|
-
export function setVideoConfig(info, config, src) {
|
|
263
|
+
export function setVideoConfig(info: RawVideoConfigInfo, config?: RawVideoConfigOptions, src?: string): boolean {
|
|
230
264
|
return videoConfiguration.setConfiguration(info, config, src);
|
|
231
265
|
}
|
|
232
266
|
|