@newrelic/video-core 3.2.0-beta-1 → 4.0.1
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 -3
- package/README.md +17 -39
- 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/esm/index.js +1 -1
- package/dist/esm/index.js.LICENSE.txt +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/umd/nrvideo.min.js +1 -1
- package/dist/umd/nrvideo.min.js.LICENSE.txt +3 -1
- package/dist/umd/nrvideo.min.js.map +1 -1
- package/package.json +6 -3
- package/src/agent.js +74 -5
- package/src/constants.js +15 -13
- package/src/core.js +59 -15
- package/src/eventAggregator.js +189 -37
- package/src/harvestScheduler.js +416 -0
- package/src/index.js +21 -0
- package/src/optimizedHttpClient.js +165 -0
- package/src/recordEvent.js +23 -50
- package/src/retryQueueHandler.js +124 -0
- package/src/tracker.js +23 -6
- package/src/utils.js +124 -63
- package/src/videoConfiguration.js +113 -0
- package/src/videotrackerstate.js +25 -1
- package/src/authConfiguration.js +0 -138
- package/src/harvester.js +0 -171
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import Log from "./log";
|
|
2
|
+
import { dataSize } from "./utils";
|
|
3
|
+
import Constants from "./constants";
|
|
4
|
+
|
|
5
|
+
const { MAX_PAYLOAD_SIZE, MAX_EVENTS_PER_BATCH } = Constants;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Retry Queue Handler for managing failed events with retry logic,
|
|
9
|
+
* backoff strategies, and persistent storage capabilities.
|
|
10
|
+
*/
|
|
11
|
+
export class RetryQueueHandler {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.retryQueue = [];
|
|
14
|
+
this.maxQueueSize = MAX_EVENTS_PER_BATCH; // Max 1000 events
|
|
15
|
+
this.maxQueueSizeBytes = MAX_PAYLOAD_SIZE; // Max 1MB
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Adds failed events to the retry queue for retry processing.
|
|
20
|
+
* @param {Array|object} events - Failed event(s) to add to retry queue
|
|
21
|
+
*/
|
|
22
|
+
addFailedEvents(events) {
|
|
23
|
+
try {
|
|
24
|
+
const eventsArray = Array.isArray(events) ? events : [events];
|
|
25
|
+
|
|
26
|
+
Log.notice(`Adding ${eventsArray.length} failed events to retry queue`, {
|
|
27
|
+
queueSizeBefore: this.retryQueue.length,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
for (const event of eventsArray) {
|
|
31
|
+
// Check queue size and make room if necessary
|
|
32
|
+
if (this.retryQueue.length >= this.maxQueueSize) {
|
|
33
|
+
this.evictOldestEvent();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check queue memory size and make room if necessary
|
|
37
|
+
const eventSize = dataSize(event);
|
|
38
|
+
while (dataSize(this.retryQueue) + eventSize > this.maxQueueSizeBytes) {
|
|
39
|
+
this.evictOldestEvent();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Store event directly - no wrapper needed
|
|
43
|
+
this.retryQueue.push({ ...event });
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
Log.error("Failed to add events to retry queue:", err.message);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Discards an event that cannot be retried.
|
|
52
|
+
* @param {object} event - Event to discard
|
|
53
|
+
* @param {string} reason - Reason for discarding
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
discardEvent(event, reason) {
|
|
57
|
+
Log.warn(`Discarded event`, {
|
|
58
|
+
reason,
|
|
59
|
+
eventType: event.eventType,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Evicts the oldest event from the queue to make room.
|
|
65
|
+
* @private
|
|
66
|
+
*/
|
|
67
|
+
evictOldestEvent() {
|
|
68
|
+
if (this.retryQueue.length > 0) {
|
|
69
|
+
const oldest = this.retryQueue.shift();
|
|
70
|
+
this.discardEvent(oldest, "Queue full - evicted oldest");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* For unified harvesting - get retry events that fit within payload limits
|
|
76
|
+
* Removes the selected events from the retry queue since they're being retried
|
|
77
|
+
* @param {number} availableSpace - Available payload space in bytes
|
|
78
|
+
* @param {number} availableEventCount - Available event count
|
|
79
|
+
* @returns {Array} Array of events that fit within limits
|
|
80
|
+
*/
|
|
81
|
+
getRetryEventsToFit(availableSpace, availableEventCount) {
|
|
82
|
+
const retryEvents = [];
|
|
83
|
+
let usedSpace = 0;
|
|
84
|
+
let eventCount = 0;
|
|
85
|
+
|
|
86
|
+
// Process retry queue in chronological order (oldest first) by iterating backwards
|
|
87
|
+
// This allows us to remove elements immediately without index shifting issues
|
|
88
|
+
for (let i = this.retryQueue.length - 1; i >= 0; i--) {
|
|
89
|
+
const event = this.retryQueue[i]; // 1000
|
|
90
|
+
|
|
91
|
+
if (eventCount >= availableEventCount) break;
|
|
92
|
+
|
|
93
|
+
const eventSize = dataSize(event);
|
|
94
|
+
if (usedSpace + eventSize > availableSpace) break;
|
|
95
|
+
|
|
96
|
+
// Add to beginning of retryEvents to maintain chronological order (oldest first)
|
|
97
|
+
retryEvents.unshift(event);
|
|
98
|
+
usedSpace += eventSize;
|
|
99
|
+
eventCount++;
|
|
100
|
+
|
|
101
|
+
// Remove immediately - safe because we're iterating backwards
|
|
102
|
+
this.retryQueue.splice(i, 1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return retryEvents;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Gets the current retry queue size.
|
|
110
|
+
* @returns {number} Queue size
|
|
111
|
+
*/
|
|
112
|
+
getQueueSize() {
|
|
113
|
+
return this.retryQueue.length;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Clears the retry queue.
|
|
118
|
+
*/
|
|
119
|
+
clear() {
|
|
120
|
+
this.retryQueue = [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default RetryQueueHandler;
|
package/src/tracker.js
CHANGED
|
@@ -2,6 +2,8 @@ import pkg from "../package.json";
|
|
|
2
2
|
import Emitter from "./emitter";
|
|
3
3
|
import Chrono from "./chrono";
|
|
4
4
|
import Constants from "./constants";
|
|
5
|
+
import { videoAnalyticsHarvester } from "./agent";
|
|
6
|
+
import Log from "./log";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Tracker class provides the basic logic to extend Newrelic's Browser Agent capabilities.
|
|
@@ -233,17 +235,12 @@ class Tracker extends Emitter {
|
|
|
233
235
|
* Internally, this will call {@see Emitter#emit}, so you could listen any event fired.
|
|
234
236
|
*
|
|
235
237
|
* @example
|
|
236
|
-
* tracker.
|
|
238
|
+
* tracker.sendVideoAction('BANNER_CLICK', { url: 'http....' })
|
|
237
239
|
*
|
|
238
240
|
* @param {string} event Event name
|
|
239
241
|
* @param {object} [att] Key:value dictionary filled with attributes.
|
|
240
242
|
*/
|
|
241
243
|
|
|
242
|
-
/**
|
|
243
|
-
* getElapsedTime: Calculate the time elapsed between two same actions
|
|
244
|
-
*
|
|
245
|
-
*/
|
|
246
|
-
|
|
247
244
|
sendVideoAction(event, att) {
|
|
248
245
|
this.emit("VideoAction", event, this.getAttributes(att));
|
|
249
246
|
}
|
|
@@ -264,6 +261,26 @@ class Tracker extends Emitter {
|
|
|
264
261
|
this.getAttributes(att, "customAction")
|
|
265
262
|
);
|
|
266
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Sets the harvest interval for video tracking.
|
|
267
|
+
* @param {*} interval - The interval in milliseconds.
|
|
268
|
+
* @returns {void}
|
|
269
|
+
*/
|
|
270
|
+
|
|
271
|
+
setHarvestInterval(interval) {
|
|
272
|
+
if (!videoAnalyticsHarvester) {
|
|
273
|
+
Log.error("VideoAnalyticsHarvester is not available");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
videoAnalyticsHarvester.setHarvestInterval(interval);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
Log.error("Failed to set harvest interval:", error.message);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
267
284
|
}
|
|
268
285
|
|
|
269
286
|
/**
|
package/src/utils.js
CHANGED
|
@@ -1,64 +1,94 @@
|
|
|
1
|
+
import pkg from "../package.json";
|
|
2
|
+
import Log from "./log";
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
* @
|
|
4
|
-
* @param {string} params.url - The URL to send the request to
|
|
5
|
-
* @param {Object} params.payload - The payload object containing body data
|
|
6
|
-
* @param {Object} params.options - Request options
|
|
7
|
-
* @param {boolean} params.options.isFinalHarvest - Whether this is a final harvest on page unload
|
|
8
|
-
* @param {Function} callback - Callback function to handle the response
|
|
5
|
+
* Builds the harvest URL with proper query parameters.
|
|
6
|
+
* @returns {string} Harvest URL
|
|
9
7
|
*/
|
|
10
|
-
export function callApi({ url, payload, options = {} }, callback) {
|
|
11
|
-
// Input validation
|
|
12
|
-
if (!url || !payload || !callback) {
|
|
13
|
-
console.error("callApi: Missing required parameters");
|
|
14
|
-
if (callback) callback({ retry: false, status: 0 });
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
8
|
|
|
18
|
-
|
|
19
|
-
let body;
|
|
9
|
+
export function buildUrl(fallbackUrl) {
|
|
20
10
|
try {
|
|
21
|
-
|
|
11
|
+
if (!window.NRVIDEO || !window.NRVIDEO.info) {
|
|
12
|
+
throw new Error("NRVIDEO info is not available.");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { beacon, licenseKey, applicationID } = window.NRVIDEO.info;
|
|
16
|
+
|
|
17
|
+
if (!beacon || !licenseKey)
|
|
18
|
+
throw new Error(
|
|
19
|
+
"Options object provided by New Relic is not correctly initialized"
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (applicationID) {
|
|
23
|
+
return `https://${
|
|
24
|
+
fallbackUrl ? fallbackUrl : beacon
|
|
25
|
+
}/ins/1/${licenseKey}?a=${applicationID}&v=${pkg.version}&ref=${
|
|
26
|
+
window.location.href
|
|
27
|
+
}&ca=VA`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return `https://${
|
|
31
|
+
fallbackUrl ? fallbackUrl : beacon
|
|
32
|
+
}/ins/1/${licenseKey}?&v=${pkg.version}&ref=${window.location.href}&ca=VA`;
|
|
22
33
|
} catch (error) {
|
|
23
|
-
console.error(
|
|
24
|
-
|
|
25
|
-
return;
|
|
34
|
+
console.error(error.message);
|
|
35
|
+
return null; // Return null instead of undefined
|
|
26
36
|
}
|
|
37
|
+
}
|
|
27
38
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Returns a function for use as a replacer parameter in JSON.stringify() to handle circular references.
|
|
41
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value MDN - Cyclical object value}
|
|
42
|
+
* @returns {Function} A function that filters out values it has seen before.
|
|
43
|
+
*/
|
|
44
|
+
const getCircularReplacer = () => {
|
|
45
|
+
const seen = new WeakSet();
|
|
46
|
+
return (key, value) => {
|
|
47
|
+
if (typeof value === "object" && value !== null) {
|
|
48
|
+
if (seen.has(value)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
seen.add(value);
|
|
37
52
|
}
|
|
38
|
-
return;
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The native JSON.stringify method augmented with a replacer function to handle circular references.
|
|
59
|
+
* Circular references will be excluded from the JSON output rather than triggering errors.
|
|
60
|
+
* @param {*} val - A value to be converted to a JSON string.
|
|
61
|
+
* @returns {string} A JSON string representation of the value, with circular references handled.
|
|
62
|
+
*/
|
|
63
|
+
function stringify(val) {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.stringify(val, getCircularReplacer()) ?? "";
|
|
66
|
+
} catch (e) {
|
|
67
|
+
Log.error("Error stringifying value:", e.message);
|
|
68
|
+
return "";
|
|
39
69
|
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function dataSize(data) {
|
|
73
|
+
if (typeof data === "string" && data.length) return data.length;
|
|
74
|
+
if (typeof data !== "object") return undefined;
|
|
75
|
+
// eslint-disable-next-line
|
|
76
|
+
if (
|
|
77
|
+
typeof ArrayBuffer !== "undefined" &&
|
|
78
|
+
data instanceof ArrayBuffer &&
|
|
79
|
+
data.byteLength
|
|
80
|
+
)
|
|
81
|
+
return data.byteLength;
|
|
82
|
+
if (typeof Blob !== "undefined" && data instanceof Blob && data.size)
|
|
83
|
+
return data.size;
|
|
84
|
+
if (typeof FormData !== "undefined" && data instanceof FormData)
|
|
85
|
+
return undefined;
|
|
40
86
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
keepalive: options.isFinalHarvest, // Important for final harvest fallback
|
|
48
|
-
})
|
|
49
|
-
.then((response) => {
|
|
50
|
-
// Check for statuses that indicate a retry is needed.
|
|
51
|
-
const isRetry = shouldRetry(response.status);
|
|
52
|
-
callback({
|
|
53
|
-
retry: isRetry,
|
|
54
|
-
status: response.status,
|
|
55
|
-
ok: response.ok,
|
|
56
|
-
});
|
|
57
|
-
})
|
|
58
|
-
.catch(() => {
|
|
59
|
-
// Any network failure (e.g., no internet) should also trigger a retry.
|
|
60
|
-
callback({ retry: true, status: 0 });
|
|
61
|
-
});
|
|
87
|
+
try {
|
|
88
|
+
return stringify(data).length;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
/**
|
|
@@ -66,7 +96,7 @@ export function callApi({ url, payload, options = {} }, callback) {
|
|
|
66
96
|
* @param {number} status - HTTP status code
|
|
67
97
|
* @returns {boolean} - True if request should be retried
|
|
68
98
|
*/
|
|
69
|
-
function shouldRetry(status) {
|
|
99
|
+
export function shouldRetry(status) {
|
|
70
100
|
switch (status) {
|
|
71
101
|
case 408: // Request Timeout
|
|
72
102
|
case 429: // Too Many Requests
|
|
@@ -82,20 +112,51 @@ function shouldRetry(status) {
|
|
|
82
112
|
}
|
|
83
113
|
|
|
84
114
|
/**
|
|
85
|
-
*
|
|
86
|
-
* @
|
|
87
|
-
* @
|
|
115
|
+
* Compresses a JSON payload using the Compression Streams API with Gzip.
|
|
116
|
+
* see @description(https://developer.mozilla.org/en-US/docs/Web/API/Compression_Streams_API)
|
|
117
|
+
* @param {object} payload - The JSON object to compress.
|
|
118
|
+
* @returns {Promise<Blob>} A Promise that resolves with a Blob of the Gzipped data.
|
|
88
119
|
*/
|
|
89
|
-
export function getPayloadSize(obj) {
|
|
90
|
-
if (!obj || typeof obj !== "object") {
|
|
91
|
-
return 0;
|
|
92
|
-
}
|
|
93
120
|
|
|
121
|
+
export function compressPayload(payload) {
|
|
122
|
+
const stringifiedPayload = JSON.stringify(payload);
|
|
123
|
+
const stream = new Blob([stringifiedPayload]).stream();
|
|
124
|
+
const compressionStream = new CompressionStream("gzip");
|
|
125
|
+
const compressedStream = stream.pipeThrough(compressionStream);
|
|
126
|
+
|
|
127
|
+
return new Response(compressedStream).blob();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Decompresses a gzipped Blob back to a JSON object using the Compression Streams API.
|
|
132
|
+
* @param {Blob|ArrayBuffer|Uint8Array} compressedData - The gzipped data to decompress.
|
|
133
|
+
* @returns {Promise<object>} A Promise that resolves with the decompressed JSON object.
|
|
134
|
+
*/
|
|
135
|
+
export async function decompressPayload(compressedData) {
|
|
94
136
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
137
|
+
// Convert different input types to a stream
|
|
138
|
+
let stream;
|
|
139
|
+
if (compressedData instanceof Blob) {
|
|
140
|
+
stream = compressedData.stream();
|
|
141
|
+
} else if (compressedData instanceof ArrayBuffer) {
|
|
142
|
+
stream = new Blob([compressedData]).stream();
|
|
143
|
+
} else if (compressedData instanceof Uint8Array) {
|
|
144
|
+
stream = new Blob([compressedData]).stream();
|
|
145
|
+
} else {
|
|
146
|
+
throw new Error("Unsupported compressed data type");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Decompress using DecompressionStream
|
|
150
|
+
const decompressionStream = new DecompressionStream("gzip");
|
|
151
|
+
const decompressedStream = stream.pipeThrough(decompressionStream);
|
|
152
|
+
|
|
153
|
+
// Convert back to text
|
|
154
|
+
const response = new Response(decompressedStream);
|
|
155
|
+
const decompressedText = await response.text();
|
|
156
|
+
|
|
157
|
+
// Parse JSON
|
|
158
|
+
return JSON.parse(decompressedText);
|
|
97
159
|
} catch (error) {
|
|
98
|
-
|
|
99
|
-
return 0;
|
|
160
|
+
throw new Error(`Failed to decompress payload: ${error.message}`);
|
|
100
161
|
}
|
|
101
162
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import Log from "./log";
|
|
2
|
+
import Constants from "./constants";
|
|
3
|
+
|
|
4
|
+
const { COLLECTOR } = Constants;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Enhanced video analytics configuration system that extends the existing auth configuration.
|
|
8
|
+
* Provides feature flags, retry policies, and advanced harvesting options.
|
|
9
|
+
*/
|
|
10
|
+
class VideoConfiguration {
|
|
11
|
+
/**
|
|
12
|
+
* Validates and sets the video analytics configuration.
|
|
13
|
+
* @param {object} userConfig - User provided configuration
|
|
14
|
+
* @returns {boolean} True if configuration is valid and set
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
setConfiguration(userInfo) {
|
|
18
|
+
this.initializeGlobalConfig(userInfo);
|
|
19
|
+
Log.notice("Video analytics configuration initialized successfully");
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Validates required configuration fields.
|
|
25
|
+
* @param {object} config - Configuration to validate
|
|
26
|
+
* @returns {boolean} True if valid
|
|
27
|
+
*/
|
|
28
|
+
validateRequiredFields(info) {
|
|
29
|
+
if (!info || typeof info !== "object") {
|
|
30
|
+
Log.error("Configuration must be an object");
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { licenseKey, appName, region, applicationID, beacon } = info;
|
|
35
|
+
|
|
36
|
+
if (!licenseKey) {
|
|
37
|
+
Log.error("licenseKey is required");
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (applicationID) {
|
|
42
|
+
if (!beacon) {
|
|
43
|
+
Log.error("beacon is required when applicationID is provided");
|
|
44
|
+
return false;
|
|
45
|
+
} else {
|
|
46
|
+
const validBeacons = Object.values(COLLECTOR).flatMap((el) => el);
|
|
47
|
+
if (!validBeacons.includes(beacon)) {
|
|
48
|
+
Log.error(`Invalid beacon: ${beacon}`);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
if (!appName || !region) {
|
|
54
|
+
Log.error(
|
|
55
|
+
"appName and region are required when applicationID is not provided"
|
|
56
|
+
);
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!COLLECTOR[region]) {
|
|
61
|
+
Log.error(
|
|
62
|
+
`Invalid region: ${region}. Valid regions are: ${Object.keys(
|
|
63
|
+
COLLECTOR
|
|
64
|
+
).join(", ")}`
|
|
65
|
+
);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Initializes the global NRVIDEO configuration object.
|
|
75
|
+
*/
|
|
76
|
+
initializeGlobalConfig(userInfo) {
|
|
77
|
+
if (!this.validateRequiredFields(userInfo)) return;
|
|
78
|
+
|
|
79
|
+
let { licenseKey, appName, region, beacon, applicationID } = userInfo;
|
|
80
|
+
|
|
81
|
+
if (region === "US") {
|
|
82
|
+
beacon = Constants.COLLECTOR["US"][0];
|
|
83
|
+
} else {
|
|
84
|
+
beacon = beacon || COLLECTOR[region];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
window.NRVIDEO = {
|
|
88
|
+
// Existing format for backward compatibility
|
|
89
|
+
info: {
|
|
90
|
+
...(region ? { region } : {}), // Only include region if available
|
|
91
|
+
beacon,
|
|
92
|
+
licenseKey,
|
|
93
|
+
applicationID,
|
|
94
|
+
...(applicationID ? {} : { appName }), // Only include appName when no applicationID
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create singleton instance
|
|
101
|
+
const videoConfiguration = new VideoConfiguration();
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Sets the video analytics configuration.
|
|
105
|
+
* @param {object} config - Configuration object
|
|
106
|
+
* @returns {boolean} True if configuration was set successfully
|
|
107
|
+
*/
|
|
108
|
+
export function setVideoConfig(info) {
|
|
109
|
+
return videoConfiguration.setConfiguration(info);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { videoConfiguration };
|
|
113
|
+
export default VideoConfiguration;
|
package/src/videotrackerstate.js
CHANGED
|
@@ -135,6 +135,12 @@ class VideoTrackerState {
|
|
|
135
135
|
/** Content only. Chrono that counts time since last AD_END. */
|
|
136
136
|
this.timeSinceLastAd = new Chrono();
|
|
137
137
|
|
|
138
|
+
/** Chrono that counts time since last error event. */
|
|
139
|
+
this.timeSinceLastError = new Chrono();
|
|
140
|
+
|
|
141
|
+
/** Chrono that counts time since last ad error event. */
|
|
142
|
+
this.timeSinceLastAdError = new Chrono();
|
|
143
|
+
|
|
138
144
|
/** Chrono that counts time since last *_RESUME. Only for buffering events. */
|
|
139
145
|
this.timeSinceResumed = new Chrono();
|
|
140
146
|
|
|
@@ -231,6 +237,12 @@ class VideoTrackerState {
|
|
|
231
237
|
att.timeSinceAdSeekBegin = this.timeSinceSeekBegin.getDeltaTime();
|
|
232
238
|
if (this.isAdBreak)
|
|
233
239
|
att.timeSinceAdBreakBegin = this.timeSinceAdBreakStart.getDeltaTime();
|
|
240
|
+
|
|
241
|
+
// Only include timeSinceLastAdError if an ad error has occurred
|
|
242
|
+
if (this.numberOfErrors > 0 && this.timeSinceLastAdError.startTime > 0) {
|
|
243
|
+
att.timeSinceLastAdError = this.timeSinceLastAdError.getDeltaTime();
|
|
244
|
+
}
|
|
245
|
+
|
|
234
246
|
att.numberOfAds = this.numberOfAds;
|
|
235
247
|
} else {
|
|
236
248
|
// Content only
|
|
@@ -247,6 +259,12 @@ class VideoTrackerState {
|
|
|
247
259
|
if (this.isSeeking)
|
|
248
260
|
att.timeSinceSeekBegin = this.timeSinceSeekBegin.getDeltaTime();
|
|
249
261
|
att.timeSinceLastAd = this.timeSinceLastAd.getDeltaTime();
|
|
262
|
+
|
|
263
|
+
// Only include timeSinceLastError if a content error has occurred
|
|
264
|
+
if (this.numberOfErrors > 0 && this.timeSinceLastError.startTime > 0) {
|
|
265
|
+
att.timeSinceLastError = this.timeSinceLastError.getDeltaTime();
|
|
266
|
+
}
|
|
267
|
+
|
|
250
268
|
att.numberOfVideos = this.numberOfVideos;
|
|
251
269
|
}
|
|
252
270
|
att.numberOfErrors = this.numberOfErrors;
|
|
@@ -556,11 +574,17 @@ class VideoTrackerState {
|
|
|
556
574
|
}
|
|
557
575
|
|
|
558
576
|
/**
|
|
559
|
-
* Increments error counter.
|
|
577
|
+
* Increments error counter and starts appropriate error timer.
|
|
560
578
|
*/
|
|
561
579
|
goError() {
|
|
562
580
|
this.isError = true;
|
|
563
581
|
this.numberOfErrors++;
|
|
582
|
+
|
|
583
|
+
if (this.isAd()) {
|
|
584
|
+
this.timeSinceLastAdError.start();
|
|
585
|
+
} else {
|
|
586
|
+
this.timeSinceLastError.start();
|
|
587
|
+
}
|
|
564
588
|
}
|
|
565
589
|
|
|
566
590
|
/**
|