@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.
Files changed (78) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +13 -0
  3. package/dist/cjs/browser/index.js +1 -1
  4. package/dist/cjs/browser/index.js.LICENSE.txt +1 -1
  5. package/dist/cjs/browser/index.js.map +1 -1
  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 +1 -1
  10. package/dist/cjs/vega/index.js.LICENSE.txt +1 -1
  11. package/dist/cjs/vega/index.js.map +1 -1
  12. package/dist/esm/browser/index.js +1 -1
  13. package/dist/esm/browser/index.js.LICENSE.txt +1 -1
  14. package/dist/esm/browser/index.js.map +1 -1
  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 +1 -1
  19. package/dist/esm/vega/index.js.LICENSE.txt +1 -1
  20. package/dist/esm/vega/index.js.map +1 -1
  21. package/dist/types/browser/agent.d.ts +46 -0
  22. package/dist/types/browser/harvestScheduler.d.ts +125 -0
  23. package/dist/types/browser/index.d.ts +34 -0
  24. package/dist/types/chrono.d.ts +52 -0
  25. package/dist/types/connectedDevice/connectedDeviceAgent.d.ts +63 -0
  26. package/dist/types/connectedDevice/connectedDeviceConstants.d.ts +51 -0
  27. package/dist/types/connectedDevice/connectedDeviceHarvester.d.ts +198 -0
  28. package/dist/types/connectedDevice/index.d.ts +30 -0
  29. package/dist/types/constants.d.ts +30 -0
  30. package/dist/types/core.d.ts +54 -0
  31. package/dist/types/emitter.d.ts +42 -0
  32. package/dist/types/eventAggregator.d.ts +97 -0
  33. package/dist/types/index.d.ts +33 -0
  34. package/dist/types/log.d.ts +61 -0
  35. package/dist/types/obfuscate.d.ts +13 -0
  36. package/dist/types/optimizedHttpClient.d.ts +83 -0
  37. package/dist/types/recordEvent.d.ts +30 -0
  38. package/dist/types/retryQueueHandler.d.ts +46 -0
  39. package/dist/types/tracker.d.ts +176 -0
  40. package/dist/types/utils/eventBuilder.d.ts +69 -0
  41. package/dist/types/utils/harvestTimer.d.ts +45 -0
  42. package/dist/types/utils/index.d.ts +38 -0
  43. package/dist/types/utils/qoeFilters.d.ts +88 -0
  44. package/dist/types/videoConfiguration.d.ts +90 -0
  45. package/dist/types/videotracker.d.ts +366 -0
  46. package/dist/types/videotrackerstate.d.ts +228 -0
  47. package/dist/umd/nrvideo.min.js +1 -1
  48. package/dist/umd/nrvideo.min.js.LICENSE.txt +1 -1
  49. package/dist/umd/nrvideo.min.js.map +1 -1
  50. package/package.json +14 -2
  51. package/src/browser/{agent.js → agent.ts} +21 -16
  52. package/src/browser/{harvestScheduler.js → harvestScheduler.ts} +51 -24
  53. package/src/{chrono.js → chrono.ts} +29 -24
  54. package/src/connectedDevice/{connectedDeviceAgent.js → connectedDeviceAgent.ts} +14 -10
  55. package/src/connectedDevice/{connectedDeviceConstants.js → connectedDeviceConstants.ts} +5 -3
  56. package/src/connectedDevice/{connectedDeviceHarvester.js → connectedDeviceHarvester.ts} +71 -34
  57. package/src/constants.ts +62 -0
  58. package/src/{core.js → core.ts} +31 -18
  59. package/src/{emitter.js → emitter.ts} +16 -5
  60. package/src/{eventAggregator.js → eventAggregator.ts} +33 -19
  61. package/src/global.d.ts +25 -0
  62. package/src/{log.js → log.ts} +23 -17
  63. package/src/{obfuscate.js → obfuscate.ts} +6 -1
  64. package/src/{optimizedHttpClient.js → optimizedHttpClient.ts} +37 -13
  65. package/src/{recordEvent.js → recordEvent.ts} +11 -9
  66. package/src/{retryQueueHandler.js → retryQueueHandler.ts} +17 -12
  67. package/src/{tracker.js → tracker.ts} +56 -23
  68. package/src/utils/{eventBuilder.js → eventBuilder.ts} +31 -14
  69. package/src/utils/{harvestTimer.js → harvestTimer.ts} +21 -4
  70. package/src/utils/{index.js → index.ts} +14 -14
  71. package/src/utils/{qoeFilters.js → qoeFilters.ts} +19 -8
  72. package/src/{videoConfiguration.js → videoConfiguration.ts} +44 -10
  73. package/src/{videotracker.js → videotracker.ts} +119 -78
  74. package/src/{videotrackerstate.js → videotrackerstate.ts} +113 -49
  75. package/src/constants.js +0 -63
  76. /package/src/browser/{index.js → index.ts} +0 -0
  77. /package/src/connectedDevice/{index.js → index.ts} +0 -0
  78. /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._isAd) {
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