@newrelic/browser-agent 1.233.0 → 1.233.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.
Files changed (31) hide show
  1. package/dist/cjs/cdn/experimental.js +27 -0
  2. package/dist/cjs/common/config/state/init.js +1 -1
  3. package/dist/cjs/common/constants/env.cdn.js +1 -1
  4. package/dist/cjs/common/constants/env.npm.js +1 -1
  5. package/dist/cjs/common/harvest/harvest-scheduler.js +18 -3
  6. package/dist/cjs/common/harvest/harvest-scheduler.test.js +39 -0
  7. package/dist/cjs/common/harvest/harvest.js +11 -24
  8. package/dist/cjs/features/session_replay/aggregate/index.js +10 -7
  9. package/dist/esm/cdn/experimental.js +24 -0
  10. package/dist/esm/common/config/state/init.js +1 -1
  11. package/dist/esm/common/constants/env.cdn.js +1 -1
  12. package/dist/esm/common/constants/env.npm.js +1 -1
  13. package/dist/esm/common/harvest/harvest-scheduler.js +18 -3
  14. package/dist/esm/common/harvest/harvest-scheduler.test.js +37 -0
  15. package/dist/esm/common/harvest/harvest.js +11 -24
  16. package/dist/esm/features/session_replay/aggregate/index.js +10 -7
  17. package/dist/types/cdn/experimental.d.ts +2 -0
  18. package/dist/types/cdn/experimental.d.ts.map +1 -0
  19. package/dist/types/common/harvest/harvest-scheduler.d.ts +26 -3
  20. package/dist/types/common/harvest/harvest-scheduler.d.ts.map +1 -1
  21. package/dist/types/common/harvest/harvest.d.ts +2 -2
  22. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  23. package/dist/types/features/session_replay/aggregate/index.d.ts +4 -0
  24. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  25. package/package.json +3 -2
  26. package/src/cdn/experimental.js +36 -0
  27. package/src/common/config/state/init.js +1 -1
  28. package/src/common/harvest/harvest-scheduler.js +18 -3
  29. package/src/common/harvest/harvest-scheduler.test.js +25 -0
  30. package/src/common/harvest/harvest.js +11 -12
  31. package/src/features/session_replay/aggregate/index.js +12 -7
@@ -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
+ });
@@ -68,7 +68,7 @@ const model = () => {
68
68
  },
69
69
  session_replay: {
70
70
  // feature settings
71
- enabled: true,
71
+ enabled: false,
72
72
  harvestTimeSeconds: 60,
73
73
  sampleRate: 0.1,
74
74
  errorSampleRate: 0.1,
@@ -12,7 +12,7 @@ exports.VERSION = exports.DIST_METHOD = exports.BUILD_ENV = void 0;
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.233.0";
15
+ const VERSION = "1.233.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -12,7 +12,7 @@ exports.VERSION = exports.DIST_METHOD = exports.BUILD_ENV = void 0;
12
12
  /**
13
13
  * Exposes the version of the agent
14
14
  */
15
- const VERSION = "1.233.0";
15
+ const VERSION = "1.233.1";
16
16
 
17
17
  /**
18
18
  * Exposes the build type of the agent
@@ -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) return;
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
- gzip: this.opts.gzip
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
+ it('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
+ it('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,7 +125,7 @@ class Harvest extends _sharedContext.SharedContext {
125
125
  submitMethod,
126
126
  cbFinished,
127
127
  customUrl,
128
- gzip,
128
+ raw,
129
129
  includeBaseParams = true
130
130
  } = _ref;
131
131
  var info = (0, _config.getInfo)(this.sharedContext.agentIdentifier);
@@ -140,16 +140,18 @@ class Harvest extends _sharedContext.SharedContext {
140
140
  }
141
141
  return false;
142
142
  }
143
- var url = customUrl || this.getScheme() + '://' + info.errorBeacon + '/' + endpoint + '/1/' + info.licenseKey + '?';
144
- var baseParams = includeBaseParams ? this.baseQueryString() : '';
145
- var params = payload.qs ? (0, _encode.obj)(payload.qs, agentRuntime.maxBytes) : '';
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, "/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 + baseParams + params;
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
- if (gzip) {
169
- headers.push({
170
- key: 'content-type',
171
- value: 'application/json'
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),
@@ -95,11 +95,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
95
95
  onFinished: this.onHarvestFinished.bind(this),
96
96
  retryDelay: this.harvestTimeSeconds,
97
97
  getPayload: this.prepareHarvest.bind(this),
98
- // TODO -- this stuff needs a better way to be handled
99
- includeBaseParams: false,
100
- customUrl: 'https://vortex-alb.stg-single-tooth.cell.us.nr-data.net/blob',
101
- raw: true,
102
- gzip: true
98
+ raw: true
103
99
  }, this);
104
100
 
105
101
  // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
@@ -159,7 +155,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
159
155
  if (fullSample) this.mode = MODE.FULL; // full mode has precedence over error mode
160
156
  else if (errorSample) this.mode = MODE.ERROR;
161
157
  // If neither are selected, then don't record (early return)
162
- return;
158
+ else return;
163
159
  }
164
160
 
165
161
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -169,6 +165,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
169
165
  // We only report (harvest) in FULL mode
170
166
  this.scheduler.startTimer(this.harvestTimeSeconds);
171
167
  }
168
+
169
+ // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
170
+ if (this.mode === MODE.ERROR && this.errorNoticed) {
171
+ this.mode = MODE.FULL;
172
+ }
172
173
  // We record in FULL or ERROR mode
173
174
 
174
175
  recorder = (await Promise.resolve().then(() => _interopRequireWildcard(require( /* webpackChunkName: "recorder" */'rrweb')))).record;
@@ -201,7 +202,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
201
202
  const info = (0, _config.getInfo)(this.agentIdentifier);
202
203
  return {
203
204
  qs: {
204
- protocol_version: '0'
205
+ protocol_version: '0',
206
+ content_encoding: 'gzip',
207
+ browser_monitoring_key: info.licenseKey
205
208
  },
206
209
  body: {
207
210
  type: 'SessionReplay',
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file Creates an "EXPERIMENTAL" agent loader bundle composed of the core agent and all available feature modules, including experimental features.
7
+ *
8
+ * 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.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent
10
+ */
11
+ import { Agent } from '../loaders/agent';
12
+ import { Instrument as InstrumentPageViewEvent } from '../features/page_view_event/instrument';
13
+ import { Instrument as InstrumentPageViewTiming } from '../features/page_view_timing/instrument';
14
+ import { Instrument as InstrumentMetrics } from '../features/metrics/instrument';
15
+ import { Instrument as InstrumentErrors } from '../features/jserrors/instrument';
16
+ import { Instrument as InstrumentXhr } from '../features/ajax/instrument';
17
+ import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument';
18
+ import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument';
19
+ import { Instrument as InstrumentSpa } from '../features/spa/instrument';
20
+ import { Instrument as InstrumentPageAction } from '../features/page_action/instrument';
21
+ new Agent({
22
+ features: [InstrumentXhr, InstrumentPageViewEvent, InstrumentPageViewTiming, InstrumentSessionTrace, InstrumentSessionReplay, InstrumentMetrics, InstrumentPageAction, InstrumentErrors, InstrumentSpa],
23
+ loaderType: 'experimental'
24
+ });
@@ -60,7 +60,7 @@ const model = () => {
60
60
  },
61
61
  session_replay: {
62
62
  // feature settings
63
- enabled: true,
63
+ enabled: false,
64
64
  harvestTimeSeconds: 60,
65
65
  sampleRate: 0.1,
66
66
  errorSampleRate: 0.1,
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.233.0";
9
+ export const VERSION = "1.233.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -6,7 +6,7 @@
6
6
  /**
7
7
  * Exposes the version of the agent
8
8
  */
9
- export const VERSION = "1.233.0";
9
+ export const VERSION = "1.233.1";
10
10
 
11
11
  /**
12
12
  * Exposes the build type of the agent
@@ -13,6 +13,17 @@ import { getConfigurationValue } from '../config/config';
13
13
  * Periodically invokes harvest calls and handles retries
14
14
  */
15
15
  export class HarvestScheduler extends SharedContext {
16
+ /**
17
+ * Create a HarvestScheduler
18
+ * @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
19
+ * @param {object} opts - The options used to configure the HarvestScheduler
20
+ * @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
21
+ * @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
22
+ * @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
23
+ * @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
24
+ * @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
25
+ * @param {*} parent - The parent object, whose state can be passed into SharedContext
26
+ */
16
27
  constructor(endpoint, opts, parent) {
17
28
  super(parent); // gets any allowed properties from the parent and stores them in `sharedContext`
18
29
  this.endpoint = endpoint;
@@ -74,7 +85,12 @@ export class HarvestScheduler extends SharedContext {
74
85
  var payload = this.opts.getPayload({
75
86
  retry: retry
76
87
  });
77
- if (!payload) return;
88
+ if (!payload) {
89
+ if (this.started) {
90
+ this.scheduleHarvest();
91
+ }
92
+ return;
93
+ }
78
94
  payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload];
79
95
  harvests.push(...payload);
80
96
  }
@@ -96,9 +112,8 @@ export class HarvestScheduler extends SharedContext {
96
112
  opts,
97
113
  submitMethod,
98
114
  cbFinished: onHarvestFinished,
99
- includeBaseParams: this.opts.includeBaseParams,
100
115
  customUrl: this.opts.customUrl,
101
- gzip: this.opts.gzip
116
+ raw: this.opts.raw
102
117
  });
103
118
  });
104
119
  if (this.started) {
@@ -0,0 +1,37 @@
1
+ import { setConfiguration } from '../config/state/init';
2
+ import { HarvestScheduler } from './harvest-scheduler';
3
+ describe('runHarvest', () => {
4
+ it('should re-schedule harvest even if there is no accumulated data', () => {
5
+ setConfiguration('asdf', {});
6
+ const scheduler = new HarvestScheduler('events', {
7
+ getPayload: jest.fn()
8
+ }, {
9
+ agentIdentifier: 'asdf',
10
+ ee: {
11
+ on: jest.fn()
12
+ }
13
+ });
14
+ scheduler.started = true;
15
+ jest.spyOn(scheduler, 'scheduleHarvest');
16
+ scheduler.runHarvest();
17
+ expect(scheduler.opts.getPayload()).toBeFalsy();
18
+ expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1);
19
+ });
20
+ it('should also re-schedule harvest if there is accumulated data', () => {
21
+ setConfiguration('asdf', {});
22
+ const scheduler = new HarvestScheduler('events', {
23
+ getPayload: jest.fn().mockImplementation(() => 'payload')
24
+ }, {
25
+ agentIdentifier: 'asdf',
26
+ ee: {
27
+ on: jest.fn()
28
+ }
29
+ });
30
+ scheduler.started = true;
31
+ scheduler.harvest._send = () => {};
32
+ jest.spyOn(scheduler, 'scheduleHarvest');
33
+ scheduler.runHarvest();
34
+ expect(scheduler.opts.getPayload()).toBeTruthy();
35
+ expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1);
36
+ });
37
+ });
@@ -119,7 +119,7 @@ export class Harvest extends SharedContext {
119
119
  submitMethod,
120
120
  cbFinished,
121
121
  customUrl,
122
- gzip,
122
+ raw,
123
123
  includeBaseParams = true
124
124
  } = _ref;
125
125
  var info = getInfo(this.sharedContext.agentIdentifier);
@@ -134,16 +134,18 @@ export class Harvest extends SharedContext {
134
134
  }
135
135
  return false;
136
136
  }
137
- var url = customUrl || this.getScheme() + '://' + info.errorBeacon + '/' + endpoint + '/1/' + info.licenseKey + '?';
138
- var baseParams = includeBaseParams ? this.baseQueryString() : '';
139
- var params = payload.qs ? encodeObj(payload.qs, agentRuntime.maxBytes) : '';
137
+ let url = '';
138
+ 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, "/1/").concat(info.licenseKey);
139
+ var baseParams = !raw && includeBaseParams ? this.baseQueryString() : '';
140
+ var payloadParams = payload.qs ? encodeObj(payload.qs, agentRuntime.maxBytes) : '';
140
141
  if (!submitMethod) {
141
142
  submitMethod = getSubmitMethod(endpoint, opts);
142
143
  }
143
144
  var method = submitMethod.method;
144
145
  var useBody = submitMethod.useBody;
145
146
  var body;
146
- var fullUrl = url + baseParams + params;
147
+ var fullUrl = "".concat(url, "?").concat(baseParams).concat(payloadParams);
148
+ const gzip = payload?.qs?.content_encoding === 'gzip';
147
149
  if (!gzip) {
148
150
  if (useBody && endpoint === 'events') {
149
151
  body = payload.body.e;
@@ -159,25 +161,10 @@ export class Harvest extends SharedContext {
159
161
  // Get query bytes harvested per endpoint as a supportability metric. See metrics aggregator (on unload).
160
162
  agentRuntime.queryBytesSent[endpoint] = (agentRuntime.queryBytesSent[endpoint] || 0) + fullUrl.split('?').slice(-1)[0]?.length || 0;
161
163
  const headers = [];
162
- if (gzip) {
163
- headers.push({
164
- key: 'content-type',
165
- value: 'application/json'
166
- });
167
- headers.push({
168
- key: 'X-Browser-Monitoring-Key',
169
- value: info.licenseKey
170
- });
171
- headers.push({
172
- key: 'Content-Encoding',
173
- value: 'gzip'
174
- });
175
- } else {
176
- headers.push({
177
- key: 'content-type',
178
- value: 'text/plain'
179
- });
180
- }
164
+ headers.push({
165
+ key: 'content-type',
166
+ value: 'text/plain'
167
+ });
181
168
 
182
169
  /* Since workers don't support sendBeacon right now, or Image(), they can only use XHR method.
183
170
  Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
@@ -90,11 +90,7 @@ export class Aggregate extends AggregateBase {
90
90
  onFinished: this.onHarvestFinished.bind(this),
91
91
  retryDelay: this.harvestTimeSeconds,
92
92
  getPayload: this.prepareHarvest.bind(this),
93
- // TODO -- this stuff needs a better way to be handled
94
- includeBaseParams: false,
95
- customUrl: 'https://vortex-alb.stg-single-tooth.cell.us.nr-data.net/blob',
96
- raw: true,
97
- gzip: true
93
+ raw: true
98
94
  }, this);
99
95
 
100
96
  // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
@@ -154,7 +150,7 @@ export class Aggregate extends AggregateBase {
154
150
  if (fullSample) this.mode = MODE.FULL; // full mode has precedence over error mode
155
151
  else if (errorSample) this.mode = MODE.ERROR;
156
152
  // If neither are selected, then don't record (early return)
157
- return;
153
+ else return;
158
154
  }
159
155
 
160
156
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -164,6 +160,11 @@ export class Aggregate extends AggregateBase {
164
160
  // We only report (harvest) in FULL mode
165
161
  this.scheduler.startTimer(this.harvestTimeSeconds);
166
162
  }
163
+
164
+ // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
165
+ if (this.mode === MODE.ERROR && this.errorNoticed) {
166
+ this.mode = MODE.FULL;
167
+ }
167
168
  // We record in FULL or ERROR mode
168
169
 
169
170
  recorder = (await import( /* webpackChunkName: "recorder" */'rrweb')).record;
@@ -196,7 +197,9 @@ export class Aggregate extends AggregateBase {
196
197
  const info = getInfo(this.agentIdentifier);
197
198
  return {
198
199
  qs: {
199
- protocol_version: '0'
200
+ protocol_version: '0',
201
+ content_encoding: 'gzip',
202
+ browser_monitoring_key: info.licenseKey
200
203
  },
201
204
  body: {
202
205
  type: 'SessionReplay',
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=experimental.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"experimental.d.ts","sourceRoot":"","sources":["../../../src/cdn/experimental.js"],"names":[],"mappings":""}
@@ -2,9 +2,32 @@
2
2
  * Periodically invokes harvest calls and handles retries
3
3
  */
4
4
  export class HarvestScheduler extends SharedContext {
5
- constructor(endpoint: any, opts: any, parent: any);
6
- endpoint: any;
7
- opts: any;
5
+ /**
6
+ * Create a HarvestScheduler
7
+ * @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
8
+ * @param {object} opts - The options used to configure the HarvestScheduler
9
+ * @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
10
+ * @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
11
+ * @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
12
+ * @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
13
+ * @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
14
+ * @param {*} parent - The parent object, whose state can be passed into SharedContext
15
+ */
16
+ constructor(endpoint: string, opts: {
17
+ onFinished: Function;
18
+ getPayload: Function;
19
+ retryDelay: number;
20
+ raw: boolean;
21
+ customUrl: string;
22
+ }, parent: any);
23
+ endpoint: string;
24
+ opts: {
25
+ onFinished: Function;
26
+ getPayload: Function;
27
+ retryDelay: number;
28
+ raw: boolean;
29
+ customUrl: string;
30
+ };
8
31
  started: boolean;
9
32
  timeoutHandle: NodeJS.Timeout | null;
10
33
  aborted: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"harvest-scheduler.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest-scheduler.js"],"names":[],"mappings":"AAWA;;GAEG;AACH;IACE,mDAeC;IAbC,cAAwB;IACxB,UAAsB;IACtB,iBAAoB;IACpB,qCAAyB;IACzB,iBAAoB;IAEpB,iBAA8C;IAShD,eAKC;IAED,mDAIC;IAHC,cAAwB;IAK1B,uCAMC;IAED,6CAWC;IAED,yCAsDC;IAED,gDAiBC;CACF;8BAtI6B,2BAA2B;wBAChB,WAAW"}
1
+ {"version":3,"file":"harvest-scheduler.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest-scheduler.js"],"names":[],"mappings":"AAWA;;GAEG;AACH;IACE;;;;;;;;;;SAUK;IACL,sBATa,MAAM;QAES,UAAU;QACV,UAAU;QACZ,UAAU,EAAvB,MAAM;QACQ,GAAG,EAAjB,OAAO;QACM,SAAS,EAAtB,MAAM;oBAkBlB;IAbC,iBAAwB;IACxB;;;oBARW,MAAM;aACN,OAAO;mBACP,MAAM;MAMK;IACtB,iBAAoB;IACpB,qCAAyB;IACzB,iBAAoB;IAEpB,iBAA8C;IAShD,eAKC;IAED,mDAIC;IAHC,cAAwB;IAK1B,uCAMC;IAED,6CAWC;IAED,yCA0DC;IAED,gDAiBC;CACF;8BArJ6B,2BAA2B;wBAChB,WAAW"}
@@ -37,14 +37,14 @@ export class Harvest extends SharedContext {
37
37
  * @param {NetworkSendSpec} spec Specification for sending data
38
38
  */
39
39
  obfuscateAndSend(spec: NetworkSendSpec): any;
40
- _send({ endpoint, payload, opts, submitMethod, cbFinished, customUrl, gzip, includeBaseParams }: {
40
+ _send({ endpoint, payload, opts, submitMethod, cbFinished, customUrl, raw, includeBaseParams }: {
41
41
  endpoint: any;
42
42
  payload?: {} | undefined;
43
43
  opts?: {} | undefined;
44
44
  submitMethod: any;
45
45
  cbFinished: any;
46
46
  customUrl: any;
47
- gzip: any;
47
+ raw: any;
48
48
  includeBaseParams?: boolean | undefined;
49
49
  }): any;
50
50
  baseQueryString(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAoOA;;;;;;;;;;;;;;;;;EAsBC;AAnND;IAII,0BAA2H;IAC3H,uBAAoD;IACpD,kCAAsH;IAEtH,YAAiB;IAGnB;;;;OAIG;IACH,YAFW,eAAe,OAYzB;IAED;;;OAGG;IACH,WAFW,eAAe,OAazB;IAED;;;OAGG;IACH,uBAFW,eAAe,OAMzB;IAED;;;;;;;;;YAkFC;IAGD,0BAmBC;IAED;;;MAYC;IAED,mCAGC;IAED,uBAIC;CACF;8BA3MY,MAAM;8BALW,2BAA2B;2BAF9B,mBAAmB"}
1
+ {"version":3,"file":"harvest.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvest.js"],"names":[],"mappings":"AAmOA;;;;;;;;;;;;;;;;;EAsBC;AAlND;IAII,0BAA2H;IAC3H,uBAAoD;IACpD,kCAAsH;IAEtH,YAAiB;IAGnB;;;;OAIG;IACH,YAFW,eAAe,OAYzB;IAED;;;OAGG;IACH,WAFW,eAAe,OAazB;IAED;;;OAGG;IACH,uBAFW,eAAe,OAMzB;IAED;;;;;;;;;YAiFC;IAGD,0BAmBC;IAED;;;MAYC;IAED,mCAGC;IAED,uBAIC;CACF;8BA1MY,MAAM;8BALW,2BAA2B;2BAF9B,mBAAmB"}
@@ -37,6 +37,8 @@ export class Aggregate extends AggregateBase {
37
37
  prepareHarvest(options: any): {
38
38
  qs: {
39
39
  protocol_version: string;
40
+ content_encoding: string;
41
+ browser_monitoring_key: any;
40
42
  };
41
43
  body: {
42
44
  type: string;
@@ -56,6 +58,8 @@ export class Aggregate extends AggregateBase {
56
58
  getHarvestContents(): {
57
59
  qs: {
58
60
  protocol_version: string;
61
+ content_encoding: string;
62
+ browser_monitoring_key: any;
59
63
  };
60
64
  body: {
61
65
  type: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAsBA,4CAA4C;AAiB5C;IACE,2BAAiC;IACjC,mDAuFC;IApFC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,uDAAuD;IACvD,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAIpB,gGAAgG;IAChG,sBAAyB;IACzB;;;MAGE;IACF,qBAAwB;IACxB,+HAA+H;IAC/H,kBAAqB;IAErB,qGAAqG;IACrG,+BAA+B;IAE/B,uIAAuI;IACvI,0BAAyE;IAiBzE,4BASQ;IAiCV;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CAyChB;IAED;;;;;;;;;;;;;;;;;;oBAaC;IAED;;;;;;;;;;;;;;;;;;MAoBC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAMC;IAED,qDAAqD;IACrD,uBAoBC;IAED,yHAAyH;IACzH,yCA0BC;IAED,0HAA0H;IAC1H,yBAIC;IAED,gCAAgC;IAChC,0CAGC;IAED,yDAAyD;IACzD,cAKC;IAED;;;SAGK;IACL,uCAEC;CACF;8BAtS6B,4BAA4B;iCALzB,2CAA2C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/session_replay/aggregate/index.js"],"names":[],"mappings":"AAsBA,4CAA4C;AAiB5C;IACE,2BAAiC;IACjC,mDAmFC;IAhFC,iHAAiH;IACjH,cAAgB;IAChB,8GAA8G;IAC9G,wBAAgH;IAChH,iFAAiF;IACjF,qBAAwB;IACxB,uDAAuD;IACvD,sBAAyB;IACzB,6GAA6G;IAC7G,aAAoB;IAIpB,gGAAgG;IAChG,sBAAyB;IACzB;;;MAGE;IACF,qBAAwB;IACxB,+HAA+H;IAC/H,kBAAqB;IAErB,qGAAqG;IACrG,+BAA+B;IAE/B,uIAAuI;IACvI,0BAAyE;IAiBzE,4BAKQ;IAiCV;;;;;;OAMG;IACH,kCALW,OAAO,eACP,OAAO,cACP,OAAO,GACL,IAAI,CA8ChB;IAED;;;;;;;;;;;;;;;;;;;;oBAaC;IAED;;;;;;;;;;;;;;;;;;;;MAwBC;IAED,qCAOC;IAED,kFAAkF;IAClF,oBAMC;IAED,qDAAqD;IACrD,uBAoBC;IAED,yHAAyH;IACzH,yCA0BC;IAED,0HAA0H;IAC1H,yBAIC;IAED,gCAAgC;IAChC,0CAGC;IAED,yDAAyD;IACzD,cAKC;IAED;;;SAGK;IACL,uCAEC;CACF;8BA3S6B,4BAA4B;iCALzB,2CAA2C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.233.0",
3
+ "version": "1.233.1",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "Tests for the New Relic JavaScript agent",
@@ -126,7 +126,8 @@
126
126
  "npm:build:esm": "npx babel --env-name npm-esm --out-dir dist/esm --out-file-extension .js ./src",
127
127
  "npm:build:cjs": "npx babel --env-name npm-cjs --out-dir dist/cjs --out-file-extension .js ./src",
128
128
  "npm:build:types": "npx tsc -b",
129
- "npm:pack": "mkdir -p temp && export PKG_NAME=$(npm pack --pack-destination temp) && echo ./temp/$PKG_NAME"
129
+ "npm:pack": "mkdir -p temp && export PKG_NAME=$(npm pack --pack-destination temp) && echo ./temp/$PKG_NAME",
130
+ "watch:browser-tests": "jung -r ./src -F '.*\\.test\\.js' --run -- npm run build:browser-tests"
130
131
  },
131
132
  "config": {
132
133
  "unsafe-perm": true
@@ -0,0 +1,36 @@
1
+ /*
2
+ * Copyright 2023 New Relic Corporation. All rights reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * @file Creates an "EXPERIMENTAL" agent loader bundle composed of the core agent and all available feature modules, including experimental features.
7
+ *
8
+ * 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.
9
+ * It is not production ready, and is not intended to be imported or implemented in any build of the browser agent
10
+ */
11
+ import { Agent } from '../loaders/agent'
12
+
13
+ import { Instrument as InstrumentPageViewEvent } from '../features/page_view_event/instrument'
14
+ import { Instrument as InstrumentPageViewTiming } from '../features/page_view_timing/instrument'
15
+ import { Instrument as InstrumentMetrics } from '../features/metrics/instrument'
16
+ import { Instrument as InstrumentErrors } from '../features/jserrors/instrument'
17
+ import { Instrument as InstrumentXhr } from '../features/ajax/instrument'
18
+ import { Instrument as InstrumentSessionTrace } from '../features/session_trace/instrument'
19
+ import { Instrument as InstrumentSessionReplay } from '../features/session_replay/instrument'
20
+ import { Instrument as InstrumentSpa } from '../features/spa/instrument'
21
+ import { Instrument as InstrumentPageAction } from '../features/page_action/instrument'
22
+
23
+ new Agent({
24
+ features: [
25
+ InstrumentXhr,
26
+ InstrumentPageViewEvent,
27
+ InstrumentPageViewTiming,
28
+ InstrumentSessionTrace,
29
+ InstrumentSessionReplay,
30
+ InstrumentMetrics,
31
+ InstrumentPageAction,
32
+ InstrumentErrors,
33
+ InstrumentSpa
34
+ ],
35
+ loaderType: 'experimental'
36
+ })
@@ -34,7 +34,7 @@ const model = () => {
34
34
  session_trace: { enabled: true, harvestTimeSeconds: 10 },
35
35
  session_replay: {
36
36
  // feature settings
37
- enabled: true,
37
+ enabled: false,
38
38
  harvestTimeSeconds: 60,
39
39
  sampleRate: 0.1,
40
40
  errorSampleRate: 0.1,
@@ -13,6 +13,17 @@ import { getConfigurationValue } from '../config/config'
13
13
  * Periodically invokes harvest calls and handles retries
14
14
  */
15
15
  export class HarvestScheduler extends SharedContext {
16
+ /**
17
+ * Create a HarvestScheduler
18
+ * @param {string} endpoint - The base BAM endpoint name -- ex. 'events'
19
+ * @param {object} opts - The options used to configure the HarvestScheduler
20
+ * @param {Function} opts.onFinished - The callback to be fired when a harvest has finished
21
+ * @param {Function} opts.getPayload - A callback which can be triggered to return a payload for harvesting
22
+ * @param {number} opts.retryDelay - The number of seconds to wait before retrying after a network failure
23
+ * @param {boolean} opts.raw - Use a prefabricated payload shape as the harvest payload without the need for formatting
24
+ * @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
25
+ * @param {*} parent - The parent object, whose state can be passed into SharedContext
26
+ */
16
27
  constructor (endpoint, opts, parent) {
17
28
  super(parent) // gets any allowed properties from the parent and stores them in `sharedContext`
18
29
  this.endpoint = endpoint
@@ -78,7 +89,12 @@ export class HarvestScheduler extends SharedContext {
78
89
  const retry = submitMethod.method === submitData.xhr
79
90
  var payload = this.opts.getPayload({ retry: retry })
80
91
 
81
- if (!payload) return
92
+ if (!payload) {
93
+ if (this.started) {
94
+ this.scheduleHarvest()
95
+ }
96
+ return
97
+ }
82
98
 
83
99
  payload = Object.prototype.toString.call(payload) === '[object Array]' ? payload : [payload]
84
100
  harvests.push(...payload)
@@ -103,9 +119,8 @@ export class HarvestScheduler extends SharedContext {
103
119
  opts,
104
120
  submitMethod,
105
121
  cbFinished: onHarvestFinished,
106
- includeBaseParams: this.opts.includeBaseParams,
107
122
  customUrl: this.opts.customUrl,
108
- gzip: this.opts.gzip
123
+ raw: this.opts.raw
109
124
  })
110
125
  })
111
126
 
@@ -0,0 +1,25 @@
1
+ import { setConfiguration } from '../config/state/init'
2
+ import { HarvestScheduler } from './harvest-scheduler'
3
+
4
+ describe('runHarvest', () => {
5
+ it('should re-schedule harvest even if there is no accumulated data', () => {
6
+ setConfiguration('asdf', {})
7
+ const scheduler = new HarvestScheduler('events', { getPayload: jest.fn() }, { agentIdentifier: 'asdf', ee: { on: jest.fn() } })
8
+ scheduler.started = true
9
+ jest.spyOn(scheduler, 'scheduleHarvest')
10
+ scheduler.runHarvest()
11
+ expect(scheduler.opts.getPayload()).toBeFalsy()
12
+ expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1)
13
+ })
14
+
15
+ it('should also re-schedule harvest if there is accumulated data', () => {
16
+ setConfiguration('asdf', {})
17
+ const scheduler = new HarvestScheduler('events', { getPayload: jest.fn().mockImplementation(() => 'payload') }, { agentIdentifier: 'asdf', ee: { on: jest.fn() } })
18
+ scheduler.started = true
19
+ scheduler.harvest._send = () => {}
20
+ jest.spyOn(scheduler, 'scheduleHarvest')
21
+ scheduler.runHarvest()
22
+ expect(scheduler.opts.getPayload()).toBeTruthy()
23
+ expect(scheduler.scheduleHarvest).toHaveBeenCalledTimes(1)
24
+ })
25
+ })
@@ -92,7 +92,7 @@ export class Harvest extends SharedContext {
92
92
  return this._send({ ...spec, payload })
93
93
  }
94
94
 
95
- _send ({ endpoint, payload = {}, opts = {}, submitMethod, cbFinished, customUrl, gzip, includeBaseParams = true }) {
95
+ _send ({ endpoint, payload = {}, opts = {}, submitMethod, cbFinished, customUrl, raw, includeBaseParams = true }) {
96
96
  var info = getInfo(this.sharedContext.agentIdentifier)
97
97
  if (!info.errorBeacon) return false
98
98
 
@@ -105,10 +105,13 @@ export class Harvest extends SharedContext {
105
105
  return false
106
106
  }
107
107
 
108
- var url = customUrl || this.getScheme() + '://' + info.errorBeacon + '/' + endpoint + '/1/' + info.licenseKey + '?'
108
+ let url = ''
109
+ if (customUrl) url = customUrl
110
+ else if (raw) url = `${this.getScheme()}://${info.errorBeacon}/${endpoint}`
111
+ else url = `${this.getScheme()}://${info.errorBeacon}/${endpoint}/1/${info.licenseKey}`
109
112
 
110
- var baseParams = includeBaseParams ? this.baseQueryString() : ''
111
- var params = payload.qs ? encodeObj(payload.qs, agentRuntime.maxBytes) : ''
113
+ var baseParams = !raw && includeBaseParams ? this.baseQueryString() : ''
114
+ var payloadParams = payload.qs ? encodeObj(payload.qs, agentRuntime.maxBytes) : ''
112
115
  if (!submitMethod) {
113
116
  submitMethod = getSubmitMethod(endpoint, opts)
114
117
  }
@@ -116,7 +119,9 @@ export class Harvest extends SharedContext {
116
119
  var useBody = submitMethod.useBody
117
120
 
118
121
  var body
119
- var fullUrl = url + baseParams + params
122
+ var fullUrl = `${url}?${baseParams}${payloadParams}`
123
+
124
+ const gzip = payload?.qs?.content_encoding === 'gzip'
120
125
 
121
126
  if (!gzip) {
122
127
  if (useBody && endpoint === 'events') {
@@ -135,13 +140,7 @@ export class Harvest extends SharedContext {
135
140
 
136
141
  const headers = []
137
142
 
138
- if (gzip) {
139
- headers.push({ key: 'content-type', value: 'application/json' })
140
- headers.push({ key: 'X-Browser-Monitoring-Key', value: info.licenseKey })
141
- headers.push({ key: 'Content-Encoding', value: 'gzip' })
142
- } else {
143
- headers.push({ key: 'content-type', value: 'text/plain' })
144
- }
143
+ headers.push({ key: 'content-type', value: 'text/plain' })
145
144
 
146
145
  /* Since workers don't support sendBeacon right now, or Image(), they can only use XHR method.
147
146
  Because they still do permit synch XHR, the idea is that at final harvest time (worker is closing),
@@ -90,11 +90,7 @@ export class Aggregate extends AggregateBase {
90
90
  onFinished: this.onHarvestFinished.bind(this),
91
91
  retryDelay: this.harvestTimeSeconds,
92
92
  getPayload: this.prepareHarvest.bind(this),
93
- // TODO -- this stuff needs a better way to be handled
94
- includeBaseParams: false,
95
- customUrl: 'https://vortex-alb.stg-single-tooth.cell.us.nr-data.net/blob',
96
- raw: true,
97
- gzip: true
93
+ raw: true
98
94
  }, this)
99
95
 
100
96
  // Wait for an error to be reported. This currently is wrapped around the "Error" feature. This is a feature-feature dependency.
@@ -153,7 +149,7 @@ export class Aggregate extends AggregateBase {
153
149
  if (fullSample) this.mode = MODE.FULL // full mode has precedence over error mode
154
150
  else if (errorSample) this.mode = MODE.ERROR
155
151
  // If neither are selected, then don't record (early return)
156
- return
152
+ else return
157
153
  }
158
154
 
159
155
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
@@ -163,6 +159,11 @@ export class Aggregate extends AggregateBase {
163
159
  // We only report (harvest) in FULL mode
164
160
  this.scheduler.startTimer(this.harvestTimeSeconds)
165
161
  }
162
+
163
+ // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
164
+ if (this.mode === MODE.ERROR && this.errorNoticed) {
165
+ this.mode = MODE.FULL
166
+ }
166
167
  // We record in FULL or ERROR mode
167
168
 
168
169
  recorder = (await import(/* webpackChunkName: "recorder" */'rrweb')).record
@@ -195,7 +196,11 @@ export class Aggregate extends AggregateBase {
195
196
  const agentRuntime = getRuntime(this.agentIdentifier)
196
197
  const info = getInfo(this.agentIdentifier)
197
198
  return {
198
- qs: { protocol_version: '0' },
199
+ qs: {
200
+ protocol_version: '0',
201
+ content_encoding: 'gzip',
202
+ browser_monitoring_key: info.licenseKey
203
+ },
199
204
  body: {
200
205
  type: 'SessionReplay',
201
206
  appId: Number(info.applicationID),