@newrelic/video-core 3.1.0 → 3.2.0-beta-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 +14 -2
- package/{LICENSE.txt → LICENSE} +2 -2
- package/README.md +22 -16
- package/THIRD_PARTY_NOTICES.md +5 -5
- package/package.json +5 -3
- 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/dist/cjs/index.js +0 -1
- package/dist/cjs/index.js.LICENSE.txt +0 -6
- package/dist/esm/index.js +0 -1
- package/dist/esm/index.js.LICENSE.txt +0 -6
- package/dist/umd/nrvideo.min.js +0 -1
- package/dist/umd/nrvideo.min.js.LICENSE.txt +0 -6
|
@@ -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;
|
package/src/log.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static Log class
|
|
3
|
+
*
|
|
4
|
+
* @class
|
|
5
|
+
* @static
|
|
6
|
+
*/
|
|
7
|
+
class Log {
|
|
8
|
+
/**
|
|
9
|
+
* Sends an error console log.
|
|
10
|
+
* @param {...any} [msg] Message to show
|
|
11
|
+
* @static
|
|
12
|
+
*/
|
|
13
|
+
static error(...msg) {
|
|
14
|
+
_report(msg, Log.Levels.ERROR, "darkred");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sends a warning console log.
|
|
19
|
+
* @method Log.warn
|
|
20
|
+
* @static
|
|
21
|
+
* @param {...any} msg Message to show
|
|
22
|
+
*/
|
|
23
|
+
static warn(...msg) {
|
|
24
|
+
_report(msg, Log.Levels.WARNING, "darkorange");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sends a notice console log.
|
|
29
|
+
* @method Log.notice
|
|
30
|
+
* @static
|
|
31
|
+
* @param {...any} msg Message to show
|
|
32
|
+
*/
|
|
33
|
+
static notice(...msg) {
|
|
34
|
+
_report([].slice.call(arguments), Log.Levels.NOTICE, "darkcyan");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Sends a debug message to console.
|
|
39
|
+
* @method Log.debug
|
|
40
|
+
* @static
|
|
41
|
+
* @param {...any} msg Message to show
|
|
42
|
+
*/
|
|
43
|
+
static debug(...msg) {
|
|
44
|
+
_report(msg, Log.Levels.DEBUG, "indigo");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* This utility method will add most of the HTML5 common event listeners to the player sent.
|
|
49
|
+
* Events will be reported as DEBUG level messages.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* // Already included events:
|
|
53
|
+
* ['canplay', 'buffering', 'waiting', 'ended', 'play', 'playing', 'pause', 'resume', 'error',
|
|
54
|
+
* 'abort', 'seek', 'seeking', 'seeked', 'stalled', 'dispose', 'loadeddata', 'loadstart',
|
|
55
|
+
* 'loadedmetadata']
|
|
56
|
+
*
|
|
57
|
+
* @method Log.debugCommonVideoEvents
|
|
58
|
+
* @static
|
|
59
|
+
* @param {object|function} o Object to attach the events.
|
|
60
|
+
* @param {array} [extraEvents]
|
|
61
|
+
* An array of extra events to watch. ie: ['timeupdate', 'progress'].
|
|
62
|
+
* If the first item is null, no common events will be added.
|
|
63
|
+
* @param {function} [report] Callback function called to report events.
|
|
64
|
+
* Default calls Log.debug()
|
|
65
|
+
*/
|
|
66
|
+
static debugCommonVideoEvents(o, extraEvents, report) {
|
|
67
|
+
try {
|
|
68
|
+
if (Log.level <= Log.Levels.DEBUG) {
|
|
69
|
+
report =
|
|
70
|
+
report ||
|
|
71
|
+
function (e) {
|
|
72
|
+
Log.debug("Event: " + e.type);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
var playerEvents = [
|
|
76
|
+
"canplay",
|
|
77
|
+
"buffering",
|
|
78
|
+
"waiting",
|
|
79
|
+
"ended",
|
|
80
|
+
"play",
|
|
81
|
+
"playing",
|
|
82
|
+
"pause",
|
|
83
|
+
"resume",
|
|
84
|
+
"error",
|
|
85
|
+
"abort",
|
|
86
|
+
"seek",
|
|
87
|
+
"seeking",
|
|
88
|
+
"seeked",
|
|
89
|
+
"stalled",
|
|
90
|
+
"dispose",
|
|
91
|
+
"loadeddata",
|
|
92
|
+
"loadstart",
|
|
93
|
+
"loadedmetadata",
|
|
94
|
+
];
|
|
95
|
+
if (extraEvents) {
|
|
96
|
+
if (extraEvents[0] === null) {
|
|
97
|
+
extraEvents.shift();
|
|
98
|
+
playerEvents = extraEvents;
|
|
99
|
+
} else {
|
|
100
|
+
playerEvents = playerEvents.concat(extraEvents);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (var i = 0; i < playerEvents.length; i++) {
|
|
105
|
+
if (typeof o === "function") {
|
|
106
|
+
o.call(window, playerEvents[i], report);
|
|
107
|
+
} else if (o.on) {
|
|
108
|
+
o.on(playerEvents[i], report);
|
|
109
|
+
} else if (o.addEventListener) {
|
|
110
|
+
o.addEventListener(playerEvents[i], report);
|
|
111
|
+
} else if (o.addEventHandler) {
|
|
112
|
+
o.addEventHandler(playerEvents[i], report);
|
|
113
|
+
} else {
|
|
114
|
+
Log.warn(
|
|
115
|
+
"debugCommonVideoEvents: No common listener function found for ",
|
|
116
|
+
o
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
Log.warn(err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Enum for log levels
|
|
129
|
+
* @enum {integer}
|
|
130
|
+
* @static
|
|
131
|
+
* @var
|
|
132
|
+
*/
|
|
133
|
+
Log.Levels = {
|
|
134
|
+
/** No console outputs */
|
|
135
|
+
SILENT: 5,
|
|
136
|
+
/** Console will show errors */
|
|
137
|
+
ERROR: 4,
|
|
138
|
+
/** Console will show warnings */
|
|
139
|
+
WARNING: 3,
|
|
140
|
+
/** Console will show notices (ie: life-cyrcle logs) */
|
|
141
|
+
NOTICE: 2,
|
|
142
|
+
/** Console will show debug messages. */
|
|
143
|
+
DEBUG: 1,
|
|
144
|
+
/** Show all messages. */
|
|
145
|
+
ALL: 0,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Only logs of this imporance or higher will be shown.
|
|
150
|
+
* @example Log.level = Log.Levels.ALL
|
|
151
|
+
* @default Log.Levels.ERROR
|
|
152
|
+
* @static
|
|
153
|
+
*/
|
|
154
|
+
Log.level = Log.Levels.ERROR;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* If true, logs will be outputed with colors.
|
|
158
|
+
* @default true
|
|
159
|
+
* @static
|
|
160
|
+
*/
|
|
161
|
+
Log.colorful = true;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* If true, logs will include the time mark.
|
|
165
|
+
* @default true
|
|
166
|
+
* @static
|
|
167
|
+
*/
|
|
168
|
+
Log.includeTime = true;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Prefix included at the start of every log.
|
|
172
|
+
* @default '[New Relic]'
|
|
173
|
+
* @static
|
|
174
|
+
*/
|
|
175
|
+
Log.prefix = "[nrvideo]";
|
|
176
|
+
|
|
177
|
+
// PRIVATE MEMBERS
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Returns a console message
|
|
181
|
+
*
|
|
182
|
+
* @private
|
|
183
|
+
* @param {array} msg Message array, error object or array of messages.
|
|
184
|
+
* @param {Log.Level} [level=Log.Levels.NOTICE] Defines the level of the error sent.
|
|
185
|
+
* Only errors with higher or equal level than Log.logLevel will be displayed.
|
|
186
|
+
* @param {string} [color='darkgreen'] Color of the header
|
|
187
|
+
* @see {@link Log.level}
|
|
188
|
+
*/
|
|
189
|
+
function _report(msg, level, color) {
|
|
190
|
+
level = level || Log.Levels.NOTICE;
|
|
191
|
+
color = color || "darkcyan";
|
|
192
|
+
|
|
193
|
+
var prefix = Log.prefix;
|
|
194
|
+
if (Log.includeTime) prefix += _getCurrentTime() + " ";
|
|
195
|
+
prefix += _level2letter(level) + ":";
|
|
196
|
+
|
|
197
|
+
// Show messages in actual console if level is enought
|
|
198
|
+
if (Log.level <= level && level !== Log.Levels.SILENT) {
|
|
199
|
+
if (
|
|
200
|
+
!Log.colorful ||
|
|
201
|
+
(typeof document !== "undefined" && document.documentMode)
|
|
202
|
+
) {
|
|
203
|
+
// document.documentMode exits only in IE
|
|
204
|
+
_plainReport(msg, prefix);
|
|
205
|
+
} else {
|
|
206
|
+
// choose log method
|
|
207
|
+
var logMethod;
|
|
208
|
+
if (level === Log.Levels.ERROR && console.error) {
|
|
209
|
+
logMethod = console.error;
|
|
210
|
+
} else if (level === Log.Levels.WARNING && console.warn) {
|
|
211
|
+
logMethod = console.warn;
|
|
212
|
+
} else if (level === Log.Levels.DEBUG && console.debug) {
|
|
213
|
+
// NOTE: for some reason console.debug doesn't work on CAF Receivers.
|
|
214
|
+
if (window.cast == undefined) {
|
|
215
|
+
logMethod = console.debug;
|
|
216
|
+
} else {
|
|
217
|
+
logMethod = console.log;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
logMethod = console.log;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// print message
|
|
224
|
+
prefix = "%c" + prefix;
|
|
225
|
+
msg.splice(0, 0, prefix, "color: " + color);
|
|
226
|
+
logMethod.apply(console, msg);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Returns the current time in format hh:mm:ss.mmm (with trailing 0s)
|
|
233
|
+
* @private
|
|
234
|
+
* @return {string} Current time.
|
|
235
|
+
*/
|
|
236
|
+
function _getCurrentTime() {
|
|
237
|
+
var d = new Date();
|
|
238
|
+
var hh = ("0" + d.getDate()).slice(-2);
|
|
239
|
+
var mm = ("0" + d.getMinutes()).slice(-2);
|
|
240
|
+
var ss = ("0" + d.getSeconds()).slice(-2);
|
|
241
|
+
var mmm = ("00" + d.getMilliseconds()).slice(-3);
|
|
242
|
+
return "[" + hh + ":" + mm + ":" + ss + "." + mmm + "]";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Returns a console message without style
|
|
247
|
+
*
|
|
248
|
+
* @private
|
|
249
|
+
* @param {(string|object|array)} msg Message string, object or array of messages.
|
|
250
|
+
* @param {string} prefix Prefix of the message.
|
|
251
|
+
*/
|
|
252
|
+
function _plainReport(msg, prefix) {
|
|
253
|
+
if (msg instanceof Array) {
|
|
254
|
+
for (var m in msg) {
|
|
255
|
+
_plainReport(msg[m], prefix);
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
if (typeof msg === "string") {
|
|
259
|
+
console.log(prefix + " " + msg);
|
|
260
|
+
} else {
|
|
261
|
+
console.log(prefix + "↵");
|
|
262
|
+
console.log(msg);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const _letters = {
|
|
268
|
+
4: "e", // Error
|
|
269
|
+
3: "w", // Warning
|
|
270
|
+
2: "n", // Notice
|
|
271
|
+
1: "d", // Debug
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Transforms a level to a letter to identify every message.
|
|
276
|
+
*
|
|
277
|
+
* @private
|
|
278
|
+
* @param {sLog.Level} level Level of the message
|
|
279
|
+
*/
|
|
280
|
+
function _level2letter(level) {
|
|
281
|
+
return _letters[level];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* This function is automatically executed at load.
|
|
286
|
+
* Will search inside window.location.search for attribute 'nrvideo-debug=X'.
|
|
287
|
+
* X can have one of these values, that will modify Log.Levels.
|
|
288
|
+
* 5: SILENT,
|
|
289
|
+
* 4: ERROR,
|
|
290
|
+
* 3: WARNING,
|
|
291
|
+
* 2: NOTICE,
|
|
292
|
+
* 1: DEBUG,
|
|
293
|
+
*
|
|
294
|
+
* If nrvideo-colors=false is present, Log.colorful will be set to false.
|
|
295
|
+
*
|
|
296
|
+
* @private
|
|
297
|
+
*/
|
|
298
|
+
function _loadLevelFromUrl() {
|
|
299
|
+
if (
|
|
300
|
+
typeof window !== "undefined" &&
|
|
301
|
+
window.location &&
|
|
302
|
+
window.location.search
|
|
303
|
+
) {
|
|
304
|
+
var m = /\?.*&*nrvideo-debug=(.+)/i.exec(window.location.search);
|
|
305
|
+
if (m !== null) {
|
|
306
|
+
if (m[1] === "true") {
|
|
307
|
+
Log.level = Log.Levels.ALL;
|
|
308
|
+
} else {
|
|
309
|
+
Log.level = m[1];
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
var m2 = /\?.*&*nrvideo-colors=false/i.exec(window.location.search);
|
|
314
|
+
if (m2 !== null) {
|
|
315
|
+
Log.colorful = false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Execute load level
|
|
321
|
+
_loadLevelFromUrl();
|
|
322
|
+
|
|
323
|
+
export default Log;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { customEventAggregator } from "./agent.js";
|
|
2
|
+
import { getPayloadSize } from "./utils.js";
|
|
3
|
+
import Constants from "./constants";
|
|
4
|
+
|
|
5
|
+
const { VALID_EVENT_TYPES, MAX_EVENT_SIZE } = Constants;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Records a video event with the specified type and attributes
|
|
9
|
+
* @param {string} eventType - The type of event to record
|
|
10
|
+
* @param {Object} attributes - Additional attributes to include with the event
|
|
11
|
+
* @returns {boolean} - True if event was recorded successfully, false otherwise
|
|
12
|
+
*/
|
|
13
|
+
export function recordEvent(eventType, attributes = {}) {
|
|
14
|
+
// Input validation
|
|
15
|
+
if (
|
|
16
|
+
typeof eventType !== "string" ||
|
|
17
|
+
eventType.length === 0 ||
|
|
18
|
+
!VALID_EVENT_TYPES.includes(eventType)
|
|
19
|
+
) {
|
|
20
|
+
console.warn("recordEvent: Invalid eventType provided:", eventType);
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate attributes parameter
|
|
25
|
+
if (
|
|
26
|
+
attributes !== null &&
|
|
27
|
+
(typeof attributes !== "object" || Array.isArray(attributes))
|
|
28
|
+
) {
|
|
29
|
+
console.warn("recordEvent: attributes must be a plain object");
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Ensure attributes is an object (handle null case)
|
|
34
|
+
attributes = attributes || {};
|
|
35
|
+
|
|
36
|
+
// Check if NRVIDEO is properly initialized
|
|
37
|
+
if (!window.NRVIDEO || !window.NRVIDEO.info) {
|
|
38
|
+
console.error("recordEvent: NRVIDEO not properly initialized");
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const { appName } = window.NRVIDEO.info;
|
|
44
|
+
|
|
45
|
+
const eventObject = {
|
|
46
|
+
...attributes,
|
|
47
|
+
eventType,
|
|
48
|
+
appName,
|
|
49
|
+
timestamp: Date.now(),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Check event size to prevent oversized payloads
|
|
53
|
+
const eventSize = getPayloadSize(eventObject);
|
|
54
|
+
|
|
55
|
+
if (eventSize > MAX_EVENT_SIZE) {
|
|
56
|
+
console.warn(
|
|
57
|
+
`recordEvent: Event size (${eventSize} bytes) exceeds maximum (${MAX_EVENT_SIZE} bytes)`
|
|
58
|
+
);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
customEventAggregator.add(eventObject);
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("recordEvent: Error recording event:", error);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|