@newrelic/browser-agent 1.233.0 → 1.234.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/dist/cjs/cdn/experimental.js +27 -0
- package/dist/cjs/common/config/state/init.js +1 -1
- package/dist/cjs/common/constants/env.cdn.js +1 -1
- package/dist/cjs/common/constants/env.npm.js +1 -1
- package/dist/cjs/common/event-emitter/contextual-ee.test.js +10 -10
- package/dist/cjs/common/harvest/harvest-scheduler.js +18 -3
- package/dist/cjs/common/harvest/harvest-scheduler.test.js +39 -0
- package/dist/cjs/common/harvest/harvest.js +14 -34
- package/dist/cjs/common/harvest/harvest.test.js +224 -0
- package/dist/cjs/common/url/encode.js +2 -2
- package/dist/cjs/common/util/console.test.js +30 -0
- package/dist/cjs/common/util/get-or-set.js +8 -1
- package/dist/cjs/common/util/get-or-set.test.js +47 -0
- package/dist/cjs/common/util/stringify.test.js +48 -0
- package/dist/cjs/common/util/submit-data.js +15 -15
- package/dist/cjs/common/util/submit-data.test.js +221 -0
- package/dist/cjs/common/util/traverse.js +19 -27
- package/dist/cjs/common/util/traverse.test.js +44 -0
- package/dist/cjs/features/metrics/aggregate/endpoint-map.js +14 -0
- package/dist/cjs/features/metrics/aggregate/index.js +3 -2
- package/dist/cjs/features/metrics/instrument/index.js +0 -2
- package/dist/cjs/features/page_view_event/aggregate/index.js +58 -44
- package/dist/cjs/features/session_replay/aggregate/index.js +10 -7
- package/dist/cjs/loaders/configure/configure.js +0 -1
- package/dist/esm/cdn/experimental.js +24 -0
- package/dist/esm/common/config/state/init.js +1 -1
- package/dist/esm/common/constants/env.cdn.js +1 -1
- package/dist/esm/common/constants/env.npm.js +1 -1
- package/dist/esm/common/event-emitter/contextual-ee.test.js +10 -10
- package/dist/esm/common/harvest/harvest-scheduler.js +18 -3
- package/dist/esm/common/harvest/harvest-scheduler.test.js +37 -0
- package/dist/esm/common/harvest/harvest.js +14 -34
- package/dist/esm/common/harvest/harvest.test.js +222 -0
- package/dist/esm/common/url/encode.js +2 -2
- package/dist/esm/common/util/console.test.js +28 -0
- package/dist/esm/common/util/get-or-set.js +8 -1
- package/dist/esm/common/util/get-or-set.test.js +45 -0
- package/dist/esm/common/util/stringify.test.js +46 -0
- package/dist/esm/common/util/submit-data.js +15 -15
- package/dist/esm/common/util/submit-data.test.js +219 -0
- package/dist/esm/common/util/traverse.js +19 -27
- package/dist/esm/common/util/traverse.test.js +42 -0
- package/dist/esm/features/metrics/aggregate/endpoint-map.js +7 -0
- package/dist/esm/features/metrics/aggregate/index.js +3 -2
- package/dist/esm/features/metrics/instrument/index.js +0 -2
- package/dist/esm/features/page_view_event/aggregate/index.js +58 -44
- package/dist/esm/features/session_replay/aggregate/index.js +10 -7
- package/dist/esm/loaders/configure/configure.js +0 -1
- package/dist/types/cdn/experimental.d.ts +2 -0
- package/dist/types/cdn/experimental.d.ts.map +1 -0
- package/dist/types/common/config/state/init.d.ts.map +1 -1
- package/dist/types/common/context/shared-context.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest-scheduler.d.ts +26 -3
- package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
- package/dist/types/common/harvest/harvest.d.ts +2 -2
- package/dist/types/common/harvest/harvest.d.ts.map +1 -1
- package/dist/types/common/timer/timer.d.ts.map +1 -1
- package/dist/types/common/util/get-or-set.d.ts +9 -1
- package/dist/types/common/util/get-or-set.d.ts.map +1 -1
- package/dist/types/common/util/submit-data.d.ts +14 -10
- package/dist/types/common/util/submit-data.d.ts.map +1 -1
- package/dist/types/common/util/traverse.d.ts +10 -1
- package/dist/types/common/util/traverse.d.ts.map +1 -1
- package/dist/types/common/window/nreum.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts +8 -0
- package/dist/types/features/metrics/aggregate/endpoint-map.d.ts.map +1 -0
- package/dist/types/features/metrics/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/metrics/aggregate/polyfill-detection.es5.d.ts.map +1 -1
- package/dist/types/features/metrics/instrument/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/page_view_event/instrument/index.d.ts.map +1 -1
- package/dist/types/features/session_replay/aggregate/index.d.ts +4 -0
- package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
- package/dist/types/features/utils/handler-cache.d.ts.map +1 -1
- package/dist/types/loaders/configure/configure.d.ts.map +1 -1
- package/package.json +8 -3
- package/src/cdn/experimental.js +36 -0
- package/src/common/config/state/init.js +1 -2
- package/src/common/context/shared-context.js +0 -1
- package/src/common/event-emitter/contextual-ee.test.js +10 -10
- package/src/common/harvest/harvest-scheduler.js +18 -3
- package/src/common/harvest/harvest-scheduler.test.js +25 -0
- package/src/common/harvest/harvest.js +15 -20
- package/src/common/harvest/harvest.test.js +169 -0
- package/src/common/session/session-entity.test.js +0 -1
- package/src/common/timer/timer.js +0 -1
- package/src/common/url/encode.js +2 -2
- package/src/common/util/console.test.js +34 -0
- package/src/common/util/get-or-set.js +8 -1
- package/src/common/util/get-or-set.test.js +58 -0
- package/src/common/util/stringify.test.js +49 -0
- package/src/common/util/submit-data.js +15 -16
- package/src/common/util/submit-data.test.js +218 -0
- package/src/common/util/traverse.js +18 -27
- package/src/common/util/traverse.test.js +50 -0
- package/src/common/window/nreum.js +0 -1
- package/src/features/metrics/aggregate/endpoint-map.js +7 -0
- package/src/features/metrics/aggregate/index.js +3 -2
- package/src/features/metrics/aggregate/polyfill-detection.es5.js +0 -1
- package/src/features/metrics/instrument/index.js +0 -2
- package/src/features/page_view_event/aggregate/index.js +48 -51
- package/src/features/page_view_event/instrument/index.js +0 -1
- package/src/features/session_replay/aggregate/index.js +12 -7
- package/src/features/utils/handler-cache.js +0 -1
- package/src/loaders/configure/configure.js +0 -1
- package/dist/cjs/common/util/s-hash.js +0 -19
- package/dist/cjs/features/metrics/instrument/workers-helper.js +0 -124
- package/dist/esm/common/util/s-hash.js +0 -13
- package/dist/esm/features/metrics/instrument/workers-helper.js +0 -119
- package/dist/types/common/util/s-hash.d.ts +0 -2
- package/dist/types/common/util/s-hash.d.ts.map +0 -1
- package/dist/types/features/metrics/instrument/workers-helper.d.ts +0 -7
- package/dist/types/features/metrics/instrument/workers-helper.d.ts.map +0 -1
- package/src/common/util/s-hash.js +0 -14
- package/src/features/metrics/instrument/workers-helper.js +0 -113
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _agent = require("../loaders/agent");
|
|
4
|
+
var _instrument = require("../features/page_view_event/instrument");
|
|
5
|
+
var _instrument2 = require("../features/page_view_timing/instrument");
|
|
6
|
+
var _instrument3 = require("../features/metrics/instrument");
|
|
7
|
+
var _instrument4 = require("../features/jserrors/instrument");
|
|
8
|
+
var _instrument5 = require("../features/ajax/instrument");
|
|
9
|
+
var _instrument6 = require("../features/session_trace/instrument");
|
|
10
|
+
var _instrument7 = require("../features/session_replay/instrument");
|
|
11
|
+
var _instrument8 = require("../features/spa/instrument");
|
|
12
|
+
var _instrument9 = require("../features/page_action/instrument");
|
|
13
|
+
/*
|
|
14
|
+
* Copyright 2023 New Relic Corporation. All rights reserved.
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @file Creates an "EXPERIMENTAL" agent loader bundle composed of the core agent and all available feature modules, including experimental features.
|
|
19
|
+
*
|
|
20
|
+
* NOTE: This loader is ONLY used for internal testing. The code contained within is likely under development and dormant. It will not download to instrumented pages or record any data.
|
|
21
|
+
* It is not production ready, and is not intended to be imported or implemented in any build of the browser agent
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
new _agent.Agent({
|
|
25
|
+
features: [_instrument5.Instrument, _instrument.Instrument, _instrument2.Instrument, _instrument6.Instrument, _instrument7.Instrument, _instrument3.Instrument, _instrument9.Instrument, _instrument4.Instrument, _instrument8.Instrument],
|
|
26
|
+
loaderType: 'experimental'
|
|
27
|
+
});
|
|
@@ -93,7 +93,7 @@ describe('event-emitter context', () => {
|
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
describe('event-emitter buffer', () => {
|
|
96
|
-
|
|
96
|
+
test('it should create a new buffer for the given group', async () => {
|
|
97
97
|
const {
|
|
98
98
|
ee
|
|
99
99
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -105,7 +105,7 @@ describe('event-emitter buffer', () => {
|
|
|
105
105
|
}));
|
|
106
106
|
expect(ee.isBuffering(eventType)).toEqual(true);
|
|
107
107
|
});
|
|
108
|
-
|
|
108
|
+
test('it should default group to "feature"', async () => {
|
|
109
109
|
const {
|
|
110
110
|
ee
|
|
111
111
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -116,7 +116,7 @@ describe('event-emitter buffer', () => {
|
|
|
116
116
|
}));
|
|
117
117
|
expect(ee.isBuffering(eventType)).toEqual(true);
|
|
118
118
|
});
|
|
119
|
-
|
|
119
|
+
test('it should not create buffer if event-emitter is aborted', async () => {
|
|
120
120
|
const {
|
|
121
121
|
ee
|
|
122
122
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -156,7 +156,7 @@ describe('event-emitter abort', () => {
|
|
|
156
156
|
});
|
|
157
157
|
});
|
|
158
158
|
describe('event-emitter emit', () => {
|
|
159
|
-
|
|
159
|
+
test('should execute the listener', async () => {
|
|
160
160
|
const {
|
|
161
161
|
ee
|
|
162
162
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -167,7 +167,7 @@ describe('event-emitter emit', () => {
|
|
|
167
167
|
ee.emit(eventType, eventArgs);
|
|
168
168
|
expect(mockListener).toHaveBeenCalledWith(eventArgs[0], eventArgs[1], eventArgs[2]);
|
|
169
169
|
});
|
|
170
|
-
|
|
170
|
+
test('should not execute the listener after removal', async () => {
|
|
171
171
|
const {
|
|
172
172
|
ee
|
|
173
173
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -180,7 +180,7 @@ describe('event-emitter emit', () => {
|
|
|
180
180
|
ee.emit(eventType, eventArgs);
|
|
181
181
|
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
182
182
|
});
|
|
183
|
-
|
|
183
|
+
test('should return early if global event-emitter is aborted', async () => {
|
|
184
184
|
const {
|
|
185
185
|
ee
|
|
186
186
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -195,7 +195,7 @@ describe('event-emitter emit', () => {
|
|
|
195
195
|
ee.emit(eventType, eventArgs);
|
|
196
196
|
expect(mockListener).toHaveBeenCalledTimes(0);
|
|
197
197
|
});
|
|
198
|
-
|
|
198
|
+
test('should still emit if global event-emitter is aborted but force flag is true', async () => {
|
|
199
199
|
const {
|
|
200
200
|
ee
|
|
201
201
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -211,7 +211,7 @@ describe('event-emitter emit', () => {
|
|
|
211
211
|
scopeEE.emit(eventType, eventArgs, {}, true);
|
|
212
212
|
expect(mockScopeListener).toHaveBeenCalledTimes(1);
|
|
213
213
|
});
|
|
214
|
-
|
|
214
|
+
test('should bubble the event if bubble flag is true', async () => {
|
|
215
215
|
const {
|
|
216
216
|
ee
|
|
217
217
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -226,7 +226,7 @@ describe('event-emitter emit', () => {
|
|
|
226
226
|
expect(mockScopeListener).toHaveBeenCalledTimes(1);
|
|
227
227
|
expect(mockListener).toHaveBeenCalledTimes(1);
|
|
228
228
|
});
|
|
229
|
-
|
|
229
|
+
test('should not bubble the event if bubble flag is false', async () => {
|
|
230
230
|
const {
|
|
231
231
|
ee
|
|
232
232
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -241,7 +241,7 @@ describe('event-emitter emit', () => {
|
|
|
241
241
|
expect(mockScopeListener).toHaveBeenCalledTimes(1);
|
|
242
242
|
expect(mockListener).not.toHaveBeenCalled();
|
|
243
243
|
});
|
|
244
|
-
|
|
244
|
+
test('should buffer the event on the scoped event-emitter', async () => {
|
|
245
245
|
const {
|
|
246
246
|
ee
|
|
247
247
|
} = await Promise.resolve().then(() => _interopRequireWildcard(require('./contextual-ee')));
|
|
@@ -18,6 +18,17 @@ var _config = require("../config/config");
|
|
|
18
18
|
* Periodically invokes harvest calls and handles retries
|
|
19
19
|
*/
|
|
20
20
|
class HarvestScheduler extends _sharedContext.SharedContext {
|
|
21
|
+
/**
|
|
22
|
+
* Create a HarvestScheduler
|
|
23
|
+
* @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
|
|
24
|
+
* @param {object} opts - The options used to configure the HarvestScheduler
|
|
25
|
+
* @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
|
|
26
|
+
* @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
|
|
27
|
+
* @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
|
|
28
|
+
* @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
|
|
29
|
+
* @param {string} opts.customUrl - A custom url that falls outside of the shape of the standard BAM harvester url pattern. Will use directly instead of concatenating various pieces
|
|
30
|
+
* @param {*} parent - The parent object, whose state can be passed into SharedContext
|
|
31
|
+
*/
|
|
21
32
|
constructor(endpoint, opts, parent) {
|
|
22
33
|
super(parent); // gets any allowed properties from the parent and stores them in `sharedContext`
|
|
23
34
|
this.endpoint = endpoint;
|
|
@@ -79,7 +90,12 @@ class HarvestScheduler extends _sharedContext.SharedContext {
|
|
|
79
90
|
var payload = this.opts.getPayload({
|
|
80
91
|
retry: retry
|
|
81
92
|
});
|
|
82
|
-
if (!payload)
|
|
93
|
+
if (!payload) {
|
|
94
|
+
if (this.started) {
|
|
95
|
+
this.scheduleHarvest();
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
83
99
|
payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload];
|
|
84
100
|
harvests.push(...payload);
|
|
85
101
|
}
|
|
@@ -101,9 +117,8 @@ class HarvestScheduler extends _sharedContext.SharedContext {
|
|
|
101
117
|
opts,
|
|
102
118
|
submitMethod,
|
|
103
119
|
cbFinished: onHarvestFinished,
|
|
104
|
-
includeBaseParams: this.opts.includeBaseParams,
|
|
105
120
|
customUrl: this.opts.customUrl,
|
|
106
|
-
|
|
121
|
+
raw: this.opts.raw
|
|
107
122
|
});
|
|
108
123
|
});
|
|
109
124
|
if (this.started) {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _init = require("../config/state/init");
|
|
4
|
+
var _harvestScheduler = require("./harvest-scheduler");
|
|
5
|
+
describe('runHarvest', () => {
|
|
6
|
+
test('should re-schedule harvest even if there is no accumulated data', () => {
|
|
7
|
+
(0, _init.setConfiguration)('asdf', {});
|
|
8
|
+
const scheduler = new _harvestScheduler.HarvestScheduler('events', {
|
|
9
|
+
getPayload: jest.fn()
|
|
10
|
+
}, {
|
|
11
|
+
agentIdentifier: 'asdf',
|
|
12
|
+
ee: {
|
|
13
|
+
on: jest.fn()
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
scheduler.started = true;
|
|
17
|
+
jest.spyOn(scheduler, 'scheduleHarvest');
|
|
18
|
+
scheduler.runHarvest();
|
|
19
|
+
expect(scheduler.opts.getPayload()).toBeFalsy();
|
|
20
|
+
expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1);
|
|
21
|
+
});
|
|
22
|
+
test('should also re-schedule harvest if there is accumulated data', () => {
|
|
23
|
+
(0, _init.setConfiguration)('asdf', {});
|
|
24
|
+
const scheduler = new _harvestScheduler.HarvestScheduler('events', {
|
|
25
|
+
getPayload: jest.fn().mockImplementation(() => 'payload')
|
|
26
|
+
}, {
|
|
27
|
+
agentIdentifier: 'asdf',
|
|
28
|
+
ee: {
|
|
29
|
+
on: jest.fn()
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
scheduler.started = true;
|
|
33
|
+
scheduler.harvest._send = () => {};
|
|
34
|
+
jest.spyOn(scheduler, 'scheduleHarvest');
|
|
35
|
+
scheduler.runHarvest();
|
|
36
|
+
expect(scheduler.opts.getPayload()).toBeTruthy();
|
|
37
|
+
expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -125,13 +125,13 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
125
125
|
submitMethod,
|
|
126
126
|
cbFinished,
|
|
127
127
|
customUrl,
|
|
128
|
-
|
|
128
|
+
raw,
|
|
129
129
|
includeBaseParams = true
|
|
130
130
|
} = _ref;
|
|
131
131
|
var info = (0, _config.getInfo)(this.sharedContext.agentIdentifier);
|
|
132
132
|
if (!info.errorBeacon) return false;
|
|
133
133
|
var agentRuntime = (0, _config.getRuntime)(this.sharedContext.agentIdentifier);
|
|
134
|
-
if (!payload.body) {
|
|
134
|
+
if (!payload.body && !opts?.sendEmptyBody) {
|
|
135
135
|
// no payload body? nothing to send, just run onfinish stuff and return
|
|
136
136
|
if (cbFinished) {
|
|
137
137
|
cbFinished({
|
|
@@ -140,16 +140,18 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
140
140
|
}
|
|
141
141
|
return false;
|
|
142
142
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
var
|
|
143
|
+
let url = '';
|
|
144
|
+
if (customUrl) url = customUrl;else if (raw) url = "".concat(this.getScheme(), "://").concat(info.errorBeacon, "/").concat(endpoint);else url = "".concat(this.getScheme(), "://").concat(info.errorBeacon).concat(endpoint !== 'rum' ? "/".concat(endpoint) : '', "/1/").concat(info.licenseKey);
|
|
145
|
+
var baseParams = !raw && includeBaseParams ? this.baseQueryString() : '';
|
|
146
|
+
var payloadParams = payload.qs ? (0, _encode.obj)(payload.qs, agentRuntime.maxBytes) : '';
|
|
146
147
|
if (!submitMethod) {
|
|
147
148
|
submitMethod = getSubmitMethod(endpoint, opts);
|
|
148
149
|
}
|
|
149
150
|
var method = submitMethod.method;
|
|
150
151
|
var useBody = submitMethod.useBody;
|
|
151
152
|
var body;
|
|
152
|
-
var fullUrl = url
|
|
153
|
+
var fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
|
|
154
|
+
const gzip = payload?.qs?.content_encoding === 'gzip';
|
|
153
155
|
if (!gzip) {
|
|
154
156
|
if (useBody && endpoint === 'events') {
|
|
155
157
|
body = payload.body.e;
|
|
@@ -165,25 +167,10 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
165
167
|
// Get query bytes harvested per endpoint as a supportability metric. See metrics aggregator (on unload).
|
|
166
168
|
agentRuntime.queryBytesSent[endpoint] = (agentRuntime.queryBytesSent[endpoint] || 0) + fullUrl.split('?').slice(-1)[0]?.length || 0;
|
|
167
169
|
const headers = [];
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
});
|
|
173
|
-
headers.push({
|
|
174
|
-
key: 'X-Browser-Monitoring-Key',
|
|
175
|
-
value: info.licenseKey
|
|
176
|
-
});
|
|
177
|
-
headers.push({
|
|
178
|
-
key: 'Content-Encoding',
|
|
179
|
-
value: 'gzip'
|
|
180
|
-
});
|
|
181
|
-
} else {
|
|
182
|
-
headers.push({
|
|
183
|
-
key: 'content-type',
|
|
184
|
-
value: 'text/plain'
|
|
185
|
-
});
|
|
186
|
-
}
|
|
170
|
+
headers.push({
|
|
171
|
+
key: 'content-type',
|
|
172
|
+
value: 'text/plain'
|
|
173
|
+
});
|
|
187
174
|
|
|
188
175
|
/* Since workers don't support sendBeacon right now, or Image(), they can only use XHR method.
|
|
189
176
|
Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
|
|
@@ -263,18 +250,11 @@ class Harvest extends _sharedContext.SharedContext {
|
|
|
263
250
|
}
|
|
264
251
|
}
|
|
265
252
|
exports.Harvest = Harvest;
|
|
266
|
-
function or(a, b) {
|
|
267
|
-
return a || b;
|
|
268
|
-
}
|
|
269
253
|
function getSubmitMethod(endpoint, opts) {
|
|
270
254
|
opts = opts || {};
|
|
271
255
|
var method;
|
|
272
256
|
var useBody;
|
|
273
|
-
if (opts.
|
|
274
|
-
// currently: only STN needs a response
|
|
275
|
-
useBody = true;
|
|
276
|
-
method = _submitData.submitData.xhr;
|
|
277
|
-
} else if (opts.unload && _globalScope.isBrowserScope) {
|
|
257
|
+
if (opts.unload && _globalScope.isBrowserScope) {
|
|
278
258
|
// all the features' final harvest; neither methods work outside window context
|
|
279
259
|
useBody = haveSendBeacon;
|
|
280
260
|
method = haveSendBeacon ? _submitData.submitData.beacon : _submitData.submitData.img; // really only IE doesn't have Beacon API for web browsers
|
|
@@ -304,7 +284,7 @@ function createAccumulator() {
|
|
|
304
284
|
var accumulator = {};
|
|
305
285
|
var hasData = false;
|
|
306
286
|
return function (key, val) {
|
|
307
|
-
if (val !== null && val !== undefined && val.length) {
|
|
287
|
+
if (val !== null && val !== undefined && val.toString()?.length) {
|
|
308
288
|
accumulator[key] = val;
|
|
309
289
|
hasData = true;
|
|
310
290
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _submitData = require("../util/submit-data");
|
|
4
|
+
var _harvest = require("./harvest");
|
|
5
|
+
jest.mock('../context/shared-context', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
SharedContext: function () {
|
|
8
|
+
this.sharedContext = {
|
|
9
|
+
agentIdentifier: 'abcd'
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}));
|
|
13
|
+
jest.mock('../config/config', () => ({
|
|
14
|
+
__esModule: true,
|
|
15
|
+
getConfigurationValue: jest.fn(),
|
|
16
|
+
getInfo: jest.fn().mockReturnValue({
|
|
17
|
+
errorBeacon: 'example.com',
|
|
18
|
+
licenseKey: 'abcd'
|
|
19
|
+
}),
|
|
20
|
+
getRuntime: jest.fn().mockReturnValue({
|
|
21
|
+
bytesSent: {},
|
|
22
|
+
queryBytesSent: {}
|
|
23
|
+
})
|
|
24
|
+
}));
|
|
25
|
+
jest.mock('../util/submit-data', () => ({
|
|
26
|
+
__esModule: true,
|
|
27
|
+
submitData: {
|
|
28
|
+
xhr: jest.fn(() => ({
|
|
29
|
+
addEventListener: jest.fn()
|
|
30
|
+
})),
|
|
31
|
+
beacon: jest.fn(),
|
|
32
|
+
img: jest.fn()
|
|
33
|
+
}
|
|
34
|
+
}));
|
|
35
|
+
describe('sendX', () => {
|
|
36
|
+
test.each([null, undefined, false])('should not send request when body is empty and sendEmptyBody is %s', sendEmptyBody => {
|
|
37
|
+
const sendCallback = jest.fn();
|
|
38
|
+
const harvester = new _harvest.Harvest();
|
|
39
|
+
harvester.on('jserrors', () => ({
|
|
40
|
+
body: {},
|
|
41
|
+
qs: {}
|
|
42
|
+
}));
|
|
43
|
+
harvester.sendX({
|
|
44
|
+
endpoint: 'jserrors',
|
|
45
|
+
cbFinished: sendCallback
|
|
46
|
+
});
|
|
47
|
+
expect(sendCallback).toHaveBeenCalledWith({
|
|
48
|
+
sent: false
|
|
49
|
+
});
|
|
50
|
+
expect(_submitData.submitData.xhr).not.toHaveBeenCalled();
|
|
51
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
52
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
test('should send request when body is empty and sendEmptyBody is true', () => {
|
|
55
|
+
const harvester = new _harvest.Harvest();
|
|
56
|
+
harvester.on('jserrors', () => ({
|
|
57
|
+
body: {},
|
|
58
|
+
qs: {}
|
|
59
|
+
}));
|
|
60
|
+
harvester.sendX({
|
|
61
|
+
endpoint: 'jserrors',
|
|
62
|
+
opts: {
|
|
63
|
+
sendEmptyBody: true
|
|
64
|
+
},
|
|
65
|
+
cbFinished: jest.fn()
|
|
66
|
+
});
|
|
67
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
68
|
+
url: expect.stringContaining('https://example.com/jserrors/1/abcd?'),
|
|
69
|
+
body: undefined
|
|
70
|
+
}));
|
|
71
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
72
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
test.each([null, undefined, []])('should remove %s values from the body and query string when sending', emptyValue => {
|
|
75
|
+
const harvester = new _harvest.Harvest();
|
|
76
|
+
harvester.on('jserrors', () => ({
|
|
77
|
+
body: {
|
|
78
|
+
bar: 'foo',
|
|
79
|
+
empty: emptyValue
|
|
80
|
+
},
|
|
81
|
+
qs: {
|
|
82
|
+
foo: 'bar',
|
|
83
|
+
empty: emptyValue
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
harvester.sendX({
|
|
87
|
+
endpoint: 'jserrors',
|
|
88
|
+
cbFinished: jest.fn()
|
|
89
|
+
});
|
|
90
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
91
|
+
url: expect.stringContaining('&foo=bar'),
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
bar: 'foo'
|
|
94
|
+
})
|
|
95
|
+
}));
|
|
96
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
97
|
+
url: expect.not.stringContaining('&empty'),
|
|
98
|
+
body: expect.not.stringContaining('empty')
|
|
99
|
+
}));
|
|
100
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
101
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
test.each([1, false, true])('should not remove value %s (when it doesn\'t have a length) from the body and query string when sending', nonStringValue => {
|
|
104
|
+
const harvester = new _harvest.Harvest();
|
|
105
|
+
harvester.on('jserrors', () => ({
|
|
106
|
+
body: {
|
|
107
|
+
bar: 'foo',
|
|
108
|
+
nonString: nonStringValue
|
|
109
|
+
},
|
|
110
|
+
qs: {
|
|
111
|
+
foo: 'bar',
|
|
112
|
+
nonString: nonStringValue
|
|
113
|
+
}
|
|
114
|
+
}));
|
|
115
|
+
harvester.sendX({
|
|
116
|
+
endpoint: 'jserrors',
|
|
117
|
+
cbFinished: jest.fn()
|
|
118
|
+
});
|
|
119
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
120
|
+
url: expect.stringContaining("&nonString=".concat(nonStringValue)),
|
|
121
|
+
body: JSON.stringify({
|
|
122
|
+
bar: 'foo',
|
|
123
|
+
nonString: nonStringValue
|
|
124
|
+
})
|
|
125
|
+
}));
|
|
126
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
127
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
describe('send', () => {
|
|
131
|
+
test.each([null, undefined, false])('should not send request when body is empty and sendEmptyBody is %s', sendEmptyBody => {
|
|
132
|
+
const sendCallback = jest.fn();
|
|
133
|
+
const harvester = new _harvest.Harvest();
|
|
134
|
+
harvester.send({
|
|
135
|
+
endpoint: 'rum',
|
|
136
|
+
payload: {
|
|
137
|
+
qs: {},
|
|
138
|
+
body: {}
|
|
139
|
+
},
|
|
140
|
+
opts: {
|
|
141
|
+
sendEmptyBody
|
|
142
|
+
},
|
|
143
|
+
cbFinished: sendCallback
|
|
144
|
+
});
|
|
145
|
+
expect(sendCallback).toHaveBeenCalledWith({
|
|
146
|
+
sent: false
|
|
147
|
+
});
|
|
148
|
+
expect(_submitData.submitData.xhr).not.toHaveBeenCalled();
|
|
149
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
150
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
151
|
+
});
|
|
152
|
+
test('should send request when body is empty and sendEmptyBody is true', () => {
|
|
153
|
+
const harvester = new _harvest.Harvest();
|
|
154
|
+
harvester.send({
|
|
155
|
+
endpoint: 'rum',
|
|
156
|
+
payload: {
|
|
157
|
+
qs: {},
|
|
158
|
+
body: {}
|
|
159
|
+
},
|
|
160
|
+
opts: {
|
|
161
|
+
sendEmptyBody: true
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
165
|
+
url: expect.stringContaining('https://example.com/1/abcd?'),
|
|
166
|
+
body: undefined
|
|
167
|
+
}));
|
|
168
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
169
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
170
|
+
});
|
|
171
|
+
test.each([null, undefined, []])('should remove %s values from the body and query string when sending', emptyValue => {
|
|
172
|
+
const harvester = new _harvest.Harvest();
|
|
173
|
+
harvester.send({
|
|
174
|
+
endpoint: 'rum',
|
|
175
|
+
payload: {
|
|
176
|
+
qs: {
|
|
177
|
+
foo: 'bar',
|
|
178
|
+
empty: emptyValue
|
|
179
|
+
},
|
|
180
|
+
body: {
|
|
181
|
+
bar: 'foo',
|
|
182
|
+
empty: emptyValue
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
187
|
+
url: expect.stringContaining('&foo=bar'),
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
bar: 'foo'
|
|
190
|
+
})
|
|
191
|
+
}));
|
|
192
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
193
|
+
url: expect.not.stringContaining('&empty'),
|
|
194
|
+
body: expect.not.stringContaining('empty')
|
|
195
|
+
}));
|
|
196
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
197
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
test.each([1, false, true])('should not remove value %s (when it doesn\'t have a length) from the body and query string when sending', nonStringValue => {
|
|
200
|
+
const harvester = new _harvest.Harvest();
|
|
201
|
+
harvester.send({
|
|
202
|
+
endpoint: 'rum',
|
|
203
|
+
payload: {
|
|
204
|
+
qs: {
|
|
205
|
+
foo: 'bar',
|
|
206
|
+
nonString: nonStringValue
|
|
207
|
+
},
|
|
208
|
+
body: {
|
|
209
|
+
bar: 'foo',
|
|
210
|
+
nonString: nonStringValue
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
expect(_submitData.submitData.xhr).toHaveBeenCalledWith(expect.objectContaining({
|
|
215
|
+
url: expect.stringContaining("&nonString=".concat(nonStringValue)),
|
|
216
|
+
body: JSON.stringify({
|
|
217
|
+
bar: 'foo',
|
|
218
|
+
nonString: nonStringValue
|
|
219
|
+
})
|
|
220
|
+
}));
|
|
221
|
+
expect(_submitData.submitData.img).not.toHaveBeenCalled();
|
|
222
|
+
expect(_submitData.submitData.beacon).not.toHaveBeenCalled();
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -52,11 +52,11 @@ function obj(payload, maxBytes) {
|
|
|
52
52
|
var intermediate = [];
|
|
53
53
|
var next;
|
|
54
54
|
var i;
|
|
55
|
-
if (typeof dataArray === 'string') {
|
|
55
|
+
if (typeof dataArray === 'string' || !Array.isArray(dataArray) && dataArray !== null && dataArray !== undefined && dataArray.toString().length) {
|
|
56
56
|
next = '&' + feature + '=' + qs(dataArray);
|
|
57
57
|
total += next.length;
|
|
58
58
|
result += next;
|
|
59
|
-
} else if (dataArray.length) {
|
|
59
|
+
} else if (Array.isArray(dataArray) && dataArray.length) {
|
|
60
60
|
total += 9;
|
|
61
61
|
for (i = 0; i < dataArray.length; i++) {
|
|
62
62
|
next = qs((0, _stringify.stringify)(dataArray[i]));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _console = require("./console");
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
console.warn = jest.fn();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
jest.restoreAllMocks();
|
|
9
|
+
});
|
|
10
|
+
describe('warn', () => {
|
|
11
|
+
test('should not call console.warn if it is not a function', () => {
|
|
12
|
+
const spy = jest.spyOn(console, 'warn');
|
|
13
|
+
console.warn = undefined;
|
|
14
|
+
(0, _console.warn)('test message');
|
|
15
|
+
expect(spy).not.toHaveBeenCalled();
|
|
16
|
+
});
|
|
17
|
+
test('should call console.warn with a prefixed message', () => {
|
|
18
|
+
(0, _console.warn)('test message');
|
|
19
|
+
expect(console.warn).toHaveBeenCalledWith('New Relic: test message');
|
|
20
|
+
});
|
|
21
|
+
test('should call console.warn with secondary argument if provided', () => {
|
|
22
|
+
const secondary = 'secondary value';
|
|
23
|
+
(0, _console.warn)('test message', secondary);
|
|
24
|
+
expect(console.warn).toHaveBeenCalledWith(secondary);
|
|
25
|
+
});
|
|
26
|
+
test('should not call console.warn with secondary argument if not provided', () => {
|
|
27
|
+
(0, _console.warn)('test message');
|
|
28
|
+
expect(console.warn).toHaveBeenCalledTimes(1);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -11,7 +11,14 @@ exports.getOrSet = getOrSet;
|
|
|
11
11
|
|
|
12
12
|
var has = Object.prototype.hasOwnProperty;
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Always returns the current value of obj[prop], even if it has to set it first. Sets properties as non-enumerable if possible.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} obj - The object to get or set the property on.
|
|
18
|
+
* @param {string} prop - The name of the property.
|
|
19
|
+
* @param {Function} getVal - A function that returns the value to be set if the property does not exist.
|
|
20
|
+
* @returns {*} The value of the property in the object.
|
|
21
|
+
*/
|
|
15
22
|
function getOrSet(obj, prop, getVal) {
|
|
16
23
|
// If the value exists return it.
|
|
17
24
|
if (has.call(obj, prop)) return obj[prop];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _getOrSet = require("./get-or-set");
|
|
4
|
+
test('should return the current value of an existing property', () => {
|
|
5
|
+
const obj = {
|
|
6
|
+
foo: 'bar'
|
|
7
|
+
};
|
|
8
|
+
const prop = 'foo';
|
|
9
|
+
const getVal = jest.fn();
|
|
10
|
+
const result = (0, _getOrSet.getOrSet)(obj, prop, getVal);
|
|
11
|
+
expect(result).toBe('bar');
|
|
12
|
+
expect(getVal).not.toHaveBeenCalled();
|
|
13
|
+
});
|
|
14
|
+
test('should set and return the value from getVal if the property does not exist', () => {
|
|
15
|
+
const obj = {};
|
|
16
|
+
const prop = 'foo';
|
|
17
|
+
const getVal = jest.fn().mockReturnValue('baz');
|
|
18
|
+
const result = (0, _getOrSet.getOrSet)(obj, prop, getVal);
|
|
19
|
+
expect(result).toBe('baz');
|
|
20
|
+
expect(getVal).toHaveBeenCalled();
|
|
21
|
+
expect(obj.foo).toBe('baz');
|
|
22
|
+
});
|
|
23
|
+
test('should set the property as non-enumerable if Object.defineProperty is supported', () => {
|
|
24
|
+
const obj = {};
|
|
25
|
+
const prop = 'foo';
|
|
26
|
+
const getVal = jest.fn().mockReturnValue('baz');
|
|
27
|
+
jest.spyOn(Object, 'defineProperty');
|
|
28
|
+
const result = (0, _getOrSet.getOrSet)(obj, prop, getVal);
|
|
29
|
+
expect(result).toBe('baz');
|
|
30
|
+
expect(Object.defineProperty).toHaveBeenCalledWith(obj, prop, {
|
|
31
|
+
value: 'baz',
|
|
32
|
+
writable: true,
|
|
33
|
+
enumerable: false
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
test('should set the property from getVal if Object.defineProperty and Object.keys are not supported', () => {
|
|
37
|
+
const obj = {};
|
|
38
|
+
const prop = 'foo';
|
|
39
|
+
const getVal = jest.fn(() => 'baz');
|
|
40
|
+
const originalFn = Object.defineProperty;
|
|
41
|
+
Object.defineProperty = null;
|
|
42
|
+
const result = (0, _getOrSet.getOrSet)(obj, prop, getVal);
|
|
43
|
+
expect(result).toBe('baz');
|
|
44
|
+
expect(obj.foo).toBe('baz');
|
|
45
|
+
expect(Object.prototype.propertyIsEnumerable.call(obj, 'foo')).toBe(true);
|
|
46
|
+
Object.defineProperty = originalFn;
|
|
47
|
+
});
|