@newrelic/video-core 3.1.1 → 3.2.0-beta-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 +7 -2
- package/README.md +15 -2
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.LICENSE.txt +1 -1
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.LICENSE.txt +1 -1
- package/dist/esm/index.js.map +1 -0
- package/dist/umd/nrvideo.min.js +3 -1
- package/dist/umd/nrvideo.min.js.LICENSE.txt +1 -1
- package/dist/umd/nrvideo.min.js.map +1 -0
- package/package.json +5 -5
- package/src/agent.js +6 -0
- package/src/authConfiguration.js +138 -0
- package/src/chrono.js +78 -0
- package/src/constants.js +43 -0
- package/src/core.js +100 -0
- package/src/emitter.js +81 -0
- package/src/eventAggregator.js +66 -0
- package/src/harvester.js +171 -0
- package/src/index.js +22 -0
- package/src/log.js +323 -0
- package/src/recordEvent.js +68 -0
- package/src/tracker.js +281 -0
- package/src/utils.js +101 -0
- package/src/videotracker.js +1060 -0
- package/src/videotrackerstate.js +574 -0
package/src/chrono.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This class calculates time lapses between two points on time.
|
|
3
|
+
*/
|
|
4
|
+
class Chrono {
|
|
5
|
+
/**
|
|
6
|
+
* Constructor
|
|
7
|
+
*/
|
|
8
|
+
constructor() {
|
|
9
|
+
this.reset();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Reset chrono values. */
|
|
13
|
+
reset() {
|
|
14
|
+
/** Start time */
|
|
15
|
+
this.startTime = 0;
|
|
16
|
+
|
|
17
|
+
/** Stop time */
|
|
18
|
+
this.stopTime = 0;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* If you set an offset in a chrono, its value will be added getDeltaTime and stop.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* let chrono = new Chrono()
|
|
25
|
+
* chrono.offset = 500
|
|
26
|
+
* chrono.start()
|
|
27
|
+
* process.sleep(500)
|
|
28
|
+
* chrono.stop() // Will return 1000
|
|
29
|
+
*
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
this.offset = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Returns the time between start() and the last stop() in ms. Returns null if start wasn't
|
|
37
|
+
* called.
|
|
38
|
+
* @return {(number|null)} Time lapse in ms.
|
|
39
|
+
*/
|
|
40
|
+
getDeltaTime() {
|
|
41
|
+
if (this.startTime) {
|
|
42
|
+
return this.offset + (new Date().getTime() - this.startTime);
|
|
43
|
+
} else {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Starts the chrono.
|
|
50
|
+
*/
|
|
51
|
+
start() {
|
|
52
|
+
this.startTime = new Date().getTime();
|
|
53
|
+
this.stopTime = 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Stops the timer and returns delta time.
|
|
58
|
+
* @return {(number|null)} Returns the delta time
|
|
59
|
+
*/
|
|
60
|
+
stop() {
|
|
61
|
+
this.stopTime = new Date().getTime();
|
|
62
|
+
return this.getDeltaTime();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Creates a copy of the chrono.
|
|
67
|
+
* @returns {Chrono} Cloned chrono
|
|
68
|
+
*/
|
|
69
|
+
clone() {
|
|
70
|
+
var chrono = new Chrono();
|
|
71
|
+
chrono.startTime = this.startTime;
|
|
72
|
+
chrono.stopTime = this.stopTime;
|
|
73
|
+
chrono.offset = this.offset;
|
|
74
|
+
return chrono;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default Chrono;
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for the library.
|
|
3
|
+
* @class Constants
|
|
4
|
+
* @static
|
|
5
|
+
*/
|
|
6
|
+
class Constants {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Enum for types/positions of ads.
|
|
10
|
+
* @example var type = Constants.AdPositions.PRE
|
|
11
|
+
* @enum {String}
|
|
12
|
+
*/
|
|
13
|
+
Constants.AdPositions = {
|
|
14
|
+
/** For ads shown before the content. */
|
|
15
|
+
PRE: "pre",
|
|
16
|
+
/** For ads shown during the content. */
|
|
17
|
+
MID: "mid",
|
|
18
|
+
/** For ads shown after the content. */
|
|
19
|
+
POST: "post",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
Constants.INTERVAL = 10000;
|
|
23
|
+
Constants.MAX_EVENTS_PER_BATCH = 1000;
|
|
24
|
+
Constants.MAX_PAYLOAD_SIZE = 1; // 1mb
|
|
25
|
+
Constants.MAX_BEACON_SIZE = 0.0625; // 64kb
|
|
26
|
+
Constants.MAX_EVENT_SIZE = 0.0625; // 64kb
|
|
27
|
+
Constants.VALID_EVENT_TYPES = [
|
|
28
|
+
"VideoAction",
|
|
29
|
+
"VideoAdAction",
|
|
30
|
+
"VideoErrorAction",
|
|
31
|
+
"VideoCustomAction",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
Constants.COLLECTOR = {
|
|
35
|
+
US: "bam-cell.nr-data.net",
|
|
36
|
+
EU: "bam.eu01.nr-data.net",
|
|
37
|
+
Stage: "staging-bam-cell.nr-data.net",
|
|
38
|
+
GOV: "gov-bam.nr-data.net",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// "bam.nr-data.net",
|
|
42
|
+
|
|
43
|
+
export default Constants;
|
package/src/core.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import Log from "./log";
|
|
2
|
+
import { recordEvent } from "./recordEvent";
|
|
3
|
+
import { setAuthConfig } from "./authConfiguration";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Static class that sums up core functionalities of the library.
|
|
7
|
+
* @static
|
|
8
|
+
*/
|
|
9
|
+
class Core {
|
|
10
|
+
/**
|
|
11
|
+
* Add a tracker to the system. Trackers added will start reporting its events to NR's backend.
|
|
12
|
+
*
|
|
13
|
+
* @param {(Emitter|Tracker)} tracker Tracker instance to add.
|
|
14
|
+
*/
|
|
15
|
+
static addTracker(tracker, options) {
|
|
16
|
+
setAuthConfig(options.info);
|
|
17
|
+
if (tracker.on && tracker.emit) {
|
|
18
|
+
trackers.push(tracker);
|
|
19
|
+
tracker.on("*", eventHandler);
|
|
20
|
+
if (typeof tracker.trackerInit == "function") {
|
|
21
|
+
tracker.trackerInit();
|
|
22
|
+
}
|
|
23
|
+
} else {
|
|
24
|
+
Log.error("Tried to load a non-tracker.", tracker);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Disposes and remove given tracker. Removes its listeners.
|
|
30
|
+
*
|
|
31
|
+
* @param {Tracker} tracker Tracker to remove.
|
|
32
|
+
*/
|
|
33
|
+
static removeTracker(tracker) {
|
|
34
|
+
tracker.off("*", eventHandler);
|
|
35
|
+
tracker.dispose();
|
|
36
|
+
let index = trackers.indexOf(tracker);
|
|
37
|
+
if (index !== -1) trackers.splice(index, 1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Returns the array of trackers.
|
|
42
|
+
*
|
|
43
|
+
* @returns {Tracker[]} Array of trackers.
|
|
44
|
+
*/
|
|
45
|
+
static getTrackers() {
|
|
46
|
+
return trackers;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static send(eventType, actionName, data) {
|
|
50
|
+
data["timeSinceLoad"] = window.performance.now() / 1000;
|
|
51
|
+
recordEvent(eventType, { actionName, ...data });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sends an error event. This may be used for external errors launched by the app, the network or
|
|
56
|
+
* any external factor. Note that errors within the player are normally reported with
|
|
57
|
+
* tracker.sendError, so this method should not be used to report those.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} att attributes to be sent along the error.
|
|
60
|
+
*/
|
|
61
|
+
static sendError(att) {
|
|
62
|
+
Core.send("ERROR", att);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let trackers = [];
|
|
67
|
+
let isErrorShown = false;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Logs and sends given event.
|
|
71
|
+
*
|
|
72
|
+
* @private
|
|
73
|
+
* @param {Event} e Event
|
|
74
|
+
*/
|
|
75
|
+
function eventHandler(e) {
|
|
76
|
+
let data = cleanData(e.data);
|
|
77
|
+
if (Log.level <= Log.Levels.DEBUG) {
|
|
78
|
+
Log.notice("Sent", e.type, data);
|
|
79
|
+
} else {
|
|
80
|
+
Log.notice("Sent", e.type);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Core.send(e.eventType, e.type, data);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Cleans given object, removing all items with value === null.
|
|
88
|
+
* @private
|
|
89
|
+
* @param {Object} data Data to clean
|
|
90
|
+
* @returns {Object} Cleaned object
|
|
91
|
+
*/
|
|
92
|
+
function cleanData(data) {
|
|
93
|
+
let ret = {};
|
|
94
|
+
for (let i in data) {
|
|
95
|
+
if (data[i] !== null && typeof data[i] !== "undefined") ret[i] = data[i];
|
|
96
|
+
}
|
|
97
|
+
return ret;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export default Core;
|
package/src/emitter.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This base class implements a basic behavior of listeners and events. Extend this object to have
|
|
3
|
+
* this feature built-in inside your classes.
|
|
4
|
+
*
|
|
5
|
+
* @class Emitter
|
|
6
|
+
*/
|
|
7
|
+
class Emitter {
|
|
8
|
+
/**
|
|
9
|
+
* Sets a listener to a given event. Use {@link emit} to trigger those events.
|
|
10
|
+
* Pass '*' to listen ALL events.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} event Name of the event.
|
|
13
|
+
* @param {function} callback Callback of the event. Receives event and data.
|
|
14
|
+
* @return this
|
|
15
|
+
*/
|
|
16
|
+
on(event, callback) {
|
|
17
|
+
this._listeners = this._listeners || {};
|
|
18
|
+
if (typeof callback === "function") {
|
|
19
|
+
this._listeners[event] = this._listeners[event] || [];
|
|
20
|
+
this._listeners[event].push(callback);
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Removes given callback from the listeners of this object.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} event Name of the event.
|
|
29
|
+
* @param {function} callback Callback of the event.
|
|
30
|
+
* @return this
|
|
31
|
+
*/
|
|
32
|
+
off(event, callback) {
|
|
33
|
+
this._listeners = this._listeners || {};
|
|
34
|
+
|
|
35
|
+
if (this._listeners[event]) {
|
|
36
|
+
var index = this._listeners[event].indexOf(callback);
|
|
37
|
+
if (index !== -1) {
|
|
38
|
+
this._listeners[event].splice(index, 1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Emits given event, triggering all the associated callbacks.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} event Name of the event.
|
|
48
|
+
* @param {object} [data] Custom data to be sent to the callbacks.
|
|
49
|
+
* @return this
|
|
50
|
+
*/
|
|
51
|
+
emit(eventType, event, data) {
|
|
52
|
+
this._listeners = this._listeners || {};
|
|
53
|
+
data = data || {};
|
|
54
|
+
|
|
55
|
+
if (Array.isArray(this._listeners[event])) {
|
|
56
|
+
this._listeners[event].forEach((callback) => {
|
|
57
|
+
callback.call(this, {
|
|
58
|
+
eventType,
|
|
59
|
+
type: event,
|
|
60
|
+
data: data,
|
|
61
|
+
target: this,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (Array.isArray(this._listeners["*"])) {
|
|
67
|
+
this._listeners["*"].forEach((callback) => {
|
|
68
|
+
callback.call(this, {
|
|
69
|
+
eventType,
|
|
70
|
+
type: event,
|
|
71
|
+
data: data,
|
|
72
|
+
target: this,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default Emitter;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { getPayloadSize } from "./utils";
|
|
2
|
+
import Constants from "./constants";
|
|
3
|
+
const { MAX_EVENTS_PER_BATCH, MAX_PAYLOAD_SIZE } = Constants;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A simple aggregator that queues raw events without any statistical aggregation.
|
|
7
|
+
* It includes the necessary save/reload logic for the harvester's retry mechanism.
|
|
8
|
+
*/
|
|
9
|
+
export class NRVideoEventAggregator {
|
|
10
|
+
#queue = [];
|
|
11
|
+
#retryQueue = [];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Checks if the event queue is empty.
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
isEmpty() {
|
|
18
|
+
return this.#queue.length === 0 && this.#retryQueue.length === 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Drains the entire queue and returns all events.
|
|
23
|
+
* Called by the harvester to begin the chunking process.
|
|
24
|
+
*/
|
|
25
|
+
drain() {
|
|
26
|
+
const allEvents = [...this.#retryQueue, ...this.#queue];
|
|
27
|
+
this.#queue = []; // Clear the active queue
|
|
28
|
+
this.#retryQueue = []; // Clear the retry queue
|
|
29
|
+
|
|
30
|
+
return allEvents;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Adds a complete, enriched event object to the queue.
|
|
35
|
+
* @param {object} eventObject - The event to queue.
|
|
36
|
+
*/
|
|
37
|
+
add(eventObject) {
|
|
38
|
+
this.#queue.push(eventObject);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// --- Methods for the Harvester ---
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Cleans up the queue after a harvest attempt, based on the result.
|
|
45
|
+
* @param {object} result - The result from the harvester, containing a 'retry' flag.
|
|
46
|
+
*/
|
|
47
|
+
postHarvestCleanup(result) {
|
|
48
|
+
if (!result.retry || !result.chunk?.length) {
|
|
49
|
+
this.#retryQueue = [];
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
while (
|
|
54
|
+
this.#retryQueue.length > 0 &&
|
|
55
|
+
(getPayloadSize(this.#retryQueue) + getPayloadSize(result.chunk) >
|
|
56
|
+
MAX_PAYLOAD_SIZE ||
|
|
57
|
+
this.#retryQueue.length + result.chunk.length > MAX_EVENTS_PER_BATCH)
|
|
58
|
+
) {
|
|
59
|
+
// Removes the oldest item from the retry queue to make space
|
|
60
|
+
this.#retryQueue.shift();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Add the entire failed chunk to the retry queue.
|
|
64
|
+
this.#retryQueue.push(...result.chunk); // result.chunk will be never greater than 1mb or 1000
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/harvester.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import Constants from "./constants";
|
|
2
|
+
import pkg from "../package.json";
|
|
3
|
+
import { callApi, getPayloadSize } from "./utils";
|
|
4
|
+
|
|
5
|
+
const { INTERVAL, MAX_EVENTS_PER_BATCH, MAX_PAYLOAD_SIZE, MAX_BEACON_SIZE } =
|
|
6
|
+
Constants;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A scheduler and dispatcher for sending raw event data to the New Relic 'ins' endpoint.
|
|
10
|
+
* It manages the harvest cycle, URL construction, and retries.
|
|
11
|
+
*/
|
|
12
|
+
export class NRVideoHarvester {
|
|
13
|
+
#started = false;
|
|
14
|
+
#aggregate; // EventAggregator instance
|
|
15
|
+
#timerId = null; // Timer ID for cleanup
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {object} agentController - The agent's configuration object.
|
|
19
|
+
* @param {object} aggregate - The aggregator instance (e.g., EventAggregator).
|
|
20
|
+
*/
|
|
21
|
+
constructor(aggregate) {
|
|
22
|
+
this.#aggregate = aggregate;
|
|
23
|
+
// Ensure any queued data is sent when the user navigates away.
|
|
24
|
+
window.addEventListener("pagehide", () =>
|
|
25
|
+
this.triggerHarvest({ isFinalHarvest: true })
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Starts the periodic harvest timer.
|
|
31
|
+
*/
|
|
32
|
+
startTimer() {
|
|
33
|
+
if (this.#started) return;
|
|
34
|
+
this.#started = true;
|
|
35
|
+
const onHarvestInterval = () => {
|
|
36
|
+
this.triggerHarvest({});
|
|
37
|
+
if (this.#started) {
|
|
38
|
+
this.#timerId = setTimeout(onHarvestInterval, INTERVAL);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
this.#timerId = setTimeout(onHarvestInterval, INTERVAL);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Stops the harvest timer and cleans up resources.
|
|
46
|
+
*/
|
|
47
|
+
stopTimer() {
|
|
48
|
+
this.#started = false;
|
|
49
|
+
if (this.#timerId) {
|
|
50
|
+
clearTimeout(this.#timerId);
|
|
51
|
+
this.#timerId = null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Executes a harvest cycle by draining the queue and sending it in chunks.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
triggerHarvest(options = {}) {
|
|
60
|
+
if (this.#aggregate.isEmpty()) return;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// 1. Drain the entire queue to get all pending events.
|
|
64
|
+
const allEvents = this.#aggregate.drain();
|
|
65
|
+
|
|
66
|
+
// 2. Determine the correct size limit for this harvest.
|
|
67
|
+
const maxChunkSize = options.isFinalHarvest
|
|
68
|
+
? MAX_BEACON_SIZE
|
|
69
|
+
: MAX_PAYLOAD_SIZE;
|
|
70
|
+
|
|
71
|
+
// 3. Split the events into chunks that respect size and count limits.
|
|
72
|
+
const chunks = this.chunkEvents(allEvents, maxChunkSize);
|
|
73
|
+
|
|
74
|
+
// 4. Send each chunk sequentially.
|
|
75
|
+
chunks.forEach((chunk, index) => {
|
|
76
|
+
const isLastChunk = index === chunks.length - 1;
|
|
77
|
+
this.sendChunk(chunk, options, isLastChunk);
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("Error during harvest:", error);
|
|
81
|
+
// Re-add events to the queue if something went wrong
|
|
82
|
+
// This is a failsafe to prevent data loss
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Splits an array of events into multiple smaller arrays (chunks).
|
|
88
|
+
*/
|
|
89
|
+
chunkEvents(events, maxChunkSize) {
|
|
90
|
+
const chunks = [];
|
|
91
|
+
let currentChunk = [];
|
|
92
|
+
|
|
93
|
+
for (const event of events) {
|
|
94
|
+
if (currentChunk.length >= MAX_EVENTS_PER_BATCH) {
|
|
95
|
+
chunks.push(currentChunk);
|
|
96
|
+
currentChunk = [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
currentChunk.push(event);
|
|
100
|
+
const payloadSize = getPayloadSize({ ins: currentChunk });
|
|
101
|
+
// Use the maxChunkSize passed into the function
|
|
102
|
+
if (payloadSize > maxChunkSize) {
|
|
103
|
+
const lastEvent = currentChunk.pop();
|
|
104
|
+
if (currentChunk.length > 0) {
|
|
105
|
+
chunks.push(currentChunk);
|
|
106
|
+
}
|
|
107
|
+
currentChunk = [lastEvent];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (currentChunk.length > 0) {
|
|
112
|
+
chunks.push(currentChunk);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return chunks;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sends a single chunk of events.
|
|
120
|
+
*/
|
|
121
|
+
sendChunk(chunk, options, isLastChunk) {
|
|
122
|
+
const url = this.#buildUrl();
|
|
123
|
+
if (!url) {
|
|
124
|
+
// If URL construction failed, treat as a failed request that shouldn't be retried
|
|
125
|
+
this.#aggregate.postHarvestCleanup({ retry: false, status: 0 });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const payload = { body: { ins: chunk } };
|
|
130
|
+
|
|
131
|
+
callApi(
|
|
132
|
+
{
|
|
133
|
+
url: url,
|
|
134
|
+
payload: payload,
|
|
135
|
+
options: options,
|
|
136
|
+
},
|
|
137
|
+
(result) => {
|
|
138
|
+
// Pass the failed chunk back to the aggregator for re-queuing.
|
|
139
|
+
if (result.retry) {
|
|
140
|
+
result.chunk = chunk;
|
|
141
|
+
}
|
|
142
|
+
this.#aggregate.postHarvestCleanup(result);
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Constructs the specific URL for the New Relic 'ins' endpoint with all required parameters.
|
|
149
|
+
* @private
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
#buildUrl() {
|
|
153
|
+
try {
|
|
154
|
+
if (!window.NRVIDEO || !window.NRVIDEO.info) {
|
|
155
|
+
throw new Error("NRVIDEO info is not available.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { beacon, licenseKey, applicationID, sa } = window.NRVIDEO.info;
|
|
159
|
+
|
|
160
|
+
if (!beacon || !licenseKey || !applicationID)
|
|
161
|
+
throw new Error(
|
|
162
|
+
"Options object provided by New Relic is not correctly initialized"
|
|
163
|
+
);
|
|
164
|
+
const url = `https://${beacon}/ins/1/${licenseKey}?a=${applicationID}&v=${pkg.version}&ref=${window.location.href}&ca=VA`;
|
|
165
|
+
return url;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(error.message);
|
|
168
|
+
return null; // Return null instead of undefined
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Core from "./core";
|
|
2
|
+
import Constants from "./constants";
|
|
3
|
+
import Chrono from "./chrono";
|
|
4
|
+
import Log from "./log";
|
|
5
|
+
import Emitter from "./emitter";
|
|
6
|
+
import Tracker from "./tracker";
|
|
7
|
+
import VideoTracker from "./videotracker";
|
|
8
|
+
import VideoTrackerState from "./videotrackerstate";
|
|
9
|
+
import { version } from "../package.json";
|
|
10
|
+
|
|
11
|
+
const nrvideo = {
|
|
12
|
+
Constants,
|
|
13
|
+
Chrono,
|
|
14
|
+
Log,
|
|
15
|
+
Emitter,
|
|
16
|
+
Tracker,
|
|
17
|
+
VideoTracker,
|
|
18
|
+
VideoTrackerState,
|
|
19
|
+
Core,
|
|
20
|
+
version,
|
|
21
|
+
};
|
|
22
|
+
export default nrvideo;
|