@newrelic/browser-agent 1.302.0-rc.6 → 1.302.0-rc.7

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.
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.302.0-rc.6";
20
+ const VERSION = exports.VERSION = "1.302.0-rc.7";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -17,7 +17,7 @@ exports.VERSION = exports.RRWEB_VERSION = exports.RRWEB_PACKAGE_NAME = exports.D
17
17
  /**
18
18
  * Exposes the version of the agent
19
19
  */
20
- const VERSION = exports.VERSION = "1.302.0-rc.6";
20
+ const VERSION = exports.VERSION = "1.302.0-rc.7";
21
21
 
22
22
  /**
23
23
  * Exposes the build type of the agent
@@ -25,8 +25,10 @@ var _globalEvent = require("../dispatch/global-event");
25
25
  * SPDX-License-Identifier: Apache-2.0
26
26
  */
27
27
 
28
- const RETRY_FAILED = 'Harvester/Retry/Failed/';
29
- const RETRY_SUCCEEDED = 'Harvester/Retry/Succeeded/';
28
+ const RETRY = 'Harvester/Retry/';
29
+ const RETRY_ATTEMPTED = RETRY + 'Attempted/';
30
+ const RETRY_FAILED = RETRY + 'Failed/';
31
+ const RETRY_SUCCEEDED = RETRY + 'Succeeded/';
30
32
  class Harvester {
31
33
  #started = false;
32
34
  initializedAggregates = [];
@@ -70,8 +72,7 @@ class Harvester {
70
72
  const submitMethod = (0, _submitData.getSubmitMethod)(localOpts);
71
73
  if (!submitMethod) return output;
72
74
  const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === _submitData.xhr; // always retry all features harvests except for final
73
- output.payload = !localOpts.directSend ? aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts) : localOpts.directSend?.payload; // features like PVE can define the payload directly, bypassing the makeHarvestPayload logic
74
-
75
+ output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts);
75
76
  if (!output.payload) return output;
76
77
  send(this.agentRef, {
77
78
  endpoint: _features.FEATURE_TO_ENDPOINT[aggregateInst.featureName],
@@ -93,7 +94,9 @@ class Harvester {
93
94
  function cbFinished(result) {
94
95
  if (aggregateInst.harvestOpts.prevAttemptCode) {
95
96
  // this means we just retried a harvest that last failed
96
- (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, [(result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode], undefined, _features.FEATURE_NAMES.metrics, aggregateInst.ee);
97
+ const reportSM = message => (0, _handle.handle)(_constants.SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, _features.FEATURE_NAMES.metrics, aggregateInst.ee);
98
+ reportSM(RETRY_ATTEMPTED + aggregateInst.featureName);
99
+ reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode);
97
100
  delete aggregateInst.harvestOpts.prevAttemptCode; // always reset last observation so we don't falsely report again next harvest
98
101
  // In case this re-attempt failed again, that'll be handled (re-marked again) next.
99
102
  }
@@ -183,9 +186,9 @@ function send(agentRef, {
183
186
  status: this.status,
184
187
  retry: shouldRetry(this.status),
185
188
  fullUrl,
186
- xhr: this
189
+ xhr: this,
190
+ responseText: this.responseText
187
191
  };
188
- if (localOpts.needResponse) cbResult.responseText = this.responseText;
189
192
  cbFinished(cbResult);
190
193
 
191
194
  /** temporary audit of consistency of harvest metadata flags */
@@ -199,9 +202,9 @@ function send(agentRef, {
199
202
  status,
200
203
  retry: shouldRetry(status),
201
204
  fullUrl,
202
- fetchResponse: response
205
+ fetchResponse: response,
206
+ responseText: await response.text()
203
207
  };
204
- if (localOpts.needResponse) cbResult.responseText = await response.text();
205
208
  cbFinished(cbResult);
206
209
  /** temporary audit of consistency of harvest metadata flags */
207
210
  if (!shouldRetry(status)) trackHarvestMetadata();
@@ -30,7 +30,6 @@ exports.unused = void 0;
30
30
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
31
31
  * @property {HarvestPayload} payload Object representing payload.
32
32
  * @property {object} localOpts Additional options for sending data
33
- * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
34
33
  * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
35
34
  * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
36
35
  * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
@@ -35,7 +35,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
35
35
  this.timeToFirstByte = 0;
36
36
  this.firstByteToWindowLoad = 0; // our "frontend" duration
37
37
  this.firstByteToDomContent = 0; // our "dom processing" duration
38
-
38
+ this.retries = 0;
39
39
  if (!(0, _info.isValid)(agentRef.info)) {
40
40
  this.ee.abort();
41
41
  return (0, _console.warn)(43);
@@ -63,12 +63,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
63
63
  *
64
64
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
65
65
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
66
- * @param {*} target The target to harvest to
67
66
  */
68
- sendRum(customAttributes = this.agentRef.info.jsAttributes, target = {
69
- licenseKey: this.agentRef.info.licenseKey,
70
- applicationID: this.agentRef.info.applicationID
71
- }) {
67
+ sendRum(customAttributes = this.agentRef.info.jsAttributes) {
72
68
  const info = this.agentRef.info;
73
69
  const measures = {};
74
70
  if (info.queueTime) measures.qt = info.queueTime;
@@ -119,27 +115,30 @@ class Aggregate extends _aggregateBase.AggregateBase {
119
115
  }
120
116
  queryParameters.fp = _firstPaint.firstPaint.current.value;
121
117
  queryParameters.fcp = _firstContentfulPaint.firstContentfulPaint.current.value;
122
- const timeKeeper = this.agentRef.runtime.timeKeeper;
123
- if (timeKeeper?.ready) {
124
- queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp((0, _now.now)()));
125
- }
126
- this.rumStartTime = (0, _now.now)();
118
+ this.queryStringsBuilder = () => {
119
+ // this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
120
+ this.rumStartTime = (0, _now.now)(); // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
121
+ const timeKeeper = this.agentRef.runtime.timeKeeper;
122
+ if (timeKeeper?.ready) {
123
+ queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime));
124
+ }
125
+ return queryParameters;
126
+ };
127
+ this.events.add(body);
127
128
  this.agentRef.runtime.harvester.triggerHarvestFor(this, {
128
- directSend: {
129
- target,
130
- payload: {
131
- qs: queryParameters,
132
- body
133
- }
134
- },
135
- needResponse: true,
136
129
  sendEmptyBody: true
137
130
  });
138
131
  }
132
+ serializer(eventBuffer) {
133
+ // this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
134
+ return eventBuffer[0];
135
+ }
139
136
  postHarvestCleanup({
137
+ sent,
140
138
  status,
141
139
  responseText,
142
- xhr
140
+ xhr,
141
+ retry
143
142
  }) {
144
143
  const rumEndTime = (0, _now.now)();
145
144
  let app, flags;
@@ -152,8 +151,20 @@ class Aggregate extends _aggregateBase.AggregateBase {
152
151
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
153
152
  (0, _console.warn)(53, error);
154
153
  }
154
+ super.postHarvestCleanup({
155
+ sent,
156
+ retry
157
+ }); // this will set isRetrying & re-buffer the body if request is to be retried
158
+ if (this.isRetrying && this.retries++ < 1) {
159
+ // Only retry once
160
+ setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
161
+ sendEmptyBody: true
162
+ }), 5000); // Retry sending the RUM event after 5 seconds
163
+ return;
164
+ }
155
165
  if (status >= 400 || status === 0) {
156
166
  (0, _console.warn)(18, status);
167
+ this.blocked = true;
157
168
 
158
169
  // Get estimated payload size of our backlog
159
170
  const textEncoder = new TextEncoder();
@@ -217,6 +228,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
217
228
  }
218
229
  } catch (error) {
219
230
  this.ee.abort();
231
+ this.blocked = true;
220
232
  (0, _console.warn)(17, error);
221
233
  return;
222
234
  }
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.302.0-rc.6";
14
+ export const VERSION = "1.302.0-rc.7";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -11,7 +11,7 @@
11
11
  /**
12
12
  * Exposes the version of the agent
13
13
  */
14
- export const VERSION = "1.302.0-rc.6";
14
+ export const VERSION = "1.302.0-rc.7";
15
15
 
16
16
  /**
17
17
  * Exposes the build type of the agent
@@ -17,8 +17,10 @@ import { stringify } from '../util/stringify';
17
17
  import { getSubmitMethod, xhr as xhrMethod, xhrFetch as fetchMethod } from '../util/submit-data';
18
18
  import { activatedFeatures } from '../util/feature-flags';
19
19
  import { dispatchGlobalEvent } from '../dispatch/global-event';
20
- const RETRY_FAILED = 'Harvester/Retry/Failed/';
21
- const RETRY_SUCCEEDED = 'Harvester/Retry/Succeeded/';
20
+ const RETRY = 'Harvester/Retry/';
21
+ const RETRY_ATTEMPTED = RETRY + 'Attempted/';
22
+ const RETRY_FAILED = RETRY + 'Failed/';
23
+ const RETRY_SUCCEEDED = RETRY + 'Succeeded/';
22
24
  export class Harvester {
23
25
  #started = false;
24
26
  initializedAggregates = [];
@@ -62,8 +64,7 @@ export class Harvester {
62
64
  const submitMethod = getSubmitMethod(localOpts);
63
65
  if (!submitMethod) return output;
64
66
  const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod; // always retry all features harvests except for final
65
- output.payload = !localOpts.directSend ? aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts) : localOpts.directSend?.payload; // features like PVE can define the payload directly, bypassing the makeHarvestPayload logic
66
-
67
+ output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts);
67
68
  if (!output.payload) return output;
68
69
  send(this.agentRef, {
69
70
  endpoint: FEATURE_TO_ENDPOINT[aggregateInst.featureName],
@@ -85,7 +86,9 @@ export class Harvester {
85
86
  function cbFinished(result) {
86
87
  if (aggregateInst.harvestOpts.prevAttemptCode) {
87
88
  // this means we just retried a harvest that last failed
88
- handle(SUPPORTABILITY_METRIC_CHANNEL, [(result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode], undefined, FEATURE_NAMES.metrics, aggregateInst.ee);
89
+ const reportSM = message => handle(SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, FEATURE_NAMES.metrics, aggregateInst.ee);
90
+ reportSM(RETRY_ATTEMPTED + aggregateInst.featureName);
91
+ reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode);
89
92
  delete aggregateInst.harvestOpts.prevAttemptCode; // always reset last observation so we don't falsely report again next harvest
90
93
  // In case this re-attempt failed again, that'll be handled (re-marked again) next.
91
94
  }
@@ -175,9 +178,9 @@ export function send(agentRef, {
175
178
  status: this.status,
176
179
  retry: shouldRetry(this.status),
177
180
  fullUrl,
178
- xhr: this
181
+ xhr: this,
182
+ responseText: this.responseText
179
183
  };
180
- if (localOpts.needResponse) cbResult.responseText = this.responseText;
181
184
  cbFinished(cbResult);
182
185
 
183
186
  /** temporary audit of consistency of harvest metadata flags */
@@ -191,9 +194,9 @@ export function send(agentRef, {
191
194
  status,
192
195
  retry: shouldRetry(status),
193
196
  fullUrl,
194
- fetchResponse: response
197
+ fetchResponse: response,
198
+ responseText: await response.text()
195
199
  };
196
- if (localOpts.needResponse) cbResult.responseText = await response.text();
197
200
  cbFinished(cbResult);
198
201
  /** temporary audit of consistency of harvest metadata flags */
199
202
  if (!shouldRetry(status)) trackHarvestMetadata();
@@ -24,7 +24,6 @@
24
24
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
25
25
  * @property {HarvestPayload} payload Object representing payload.
26
26
  * @property {object} localOpts Additional options for sending data
27
- * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
28
27
  * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
29
28
  * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
30
29
  * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
@@ -27,7 +27,7 @@ export class Aggregate extends AggregateBase {
27
27
  this.timeToFirstByte = 0;
28
28
  this.firstByteToWindowLoad = 0; // our "frontend" duration
29
29
  this.firstByteToDomContent = 0; // our "dom processing" duration
30
-
30
+ this.retries = 0;
31
31
  if (!isValid(agentRef.info)) {
32
32
  this.ee.abort();
33
33
  return warn(43);
@@ -55,12 +55,8 @@ export class Aggregate extends AggregateBase {
55
55
  *
56
56
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
57
57
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
58
- * @param {*} target The target to harvest to
59
58
  */
60
- sendRum(customAttributes = this.agentRef.info.jsAttributes, target = {
61
- licenseKey: this.agentRef.info.licenseKey,
62
- applicationID: this.agentRef.info.applicationID
63
- }) {
59
+ sendRum(customAttributes = this.agentRef.info.jsAttributes) {
64
60
  const info = this.agentRef.info;
65
61
  const measures = {};
66
62
  if (info.queueTime) measures.qt = info.queueTime;
@@ -111,27 +107,30 @@ export class Aggregate extends AggregateBase {
111
107
  }
112
108
  queryParameters.fp = firstPaint.current.value;
113
109
  queryParameters.fcp = firstContentfulPaint.current.value;
114
- const timeKeeper = this.agentRef.runtime.timeKeeper;
115
- if (timeKeeper?.ready) {
116
- queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(now()));
117
- }
118
- this.rumStartTime = now();
110
+ this.queryStringsBuilder = () => {
111
+ // this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
112
+ this.rumStartTime = now(); // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
113
+ const timeKeeper = this.agentRef.runtime.timeKeeper;
114
+ if (timeKeeper?.ready) {
115
+ queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime));
116
+ }
117
+ return queryParameters;
118
+ };
119
+ this.events.add(body);
119
120
  this.agentRef.runtime.harvester.triggerHarvestFor(this, {
120
- directSend: {
121
- target,
122
- payload: {
123
- qs: queryParameters,
124
- body
125
- }
126
- },
127
- needResponse: true,
128
121
  sendEmptyBody: true
129
122
  });
130
123
  }
124
+ serializer(eventBuffer) {
125
+ // this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
126
+ return eventBuffer[0];
127
+ }
131
128
  postHarvestCleanup({
129
+ sent,
132
130
  status,
133
131
  responseText,
134
- xhr
132
+ xhr,
133
+ retry
135
134
  }) {
136
135
  const rumEndTime = now();
137
136
  let app, flags;
@@ -144,8 +143,20 @@ export class Aggregate extends AggregateBase {
144
143
  // wont set entity stuff here, if main agent will later abort, if registered agent, nothing will happen
145
144
  warn(53, error);
146
145
  }
146
+ super.postHarvestCleanup({
147
+ sent,
148
+ retry
149
+ }); // this will set isRetrying & re-buffer the body if request is to be retried
150
+ if (this.isRetrying && this.retries++ < 1) {
151
+ // Only retry once
152
+ setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
153
+ sendEmptyBody: true
154
+ }), 5000); // Retry sending the RUM event after 5 seconds
155
+ return;
156
+ }
147
157
  if (status >= 400 || status === 0) {
148
158
  warn(18, status);
159
+ this.blocked = true;
149
160
 
150
161
  // Get estimated payload size of our backlog
151
162
  const textEncoder = new TextEncoder();
@@ -209,6 +220,7 @@ export class Aggregate extends AggregateBase {
209
220
  }
210
221
  } catch (error) {
211
222
  this.ee.abort();
223
+ this.blocked = true;
212
224
  warn(17, error);
213
225
  return;
214
226
  }
@@ -1 +1 @@
1
- {"version":3,"file":"harvester.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvester.js"],"names":[],"mappings":"AAyGA;;;;IAII;AACJ;;;;;;;;;IAFc,OAAO,CAyHpB;AA9MD;IAIE,2BAUC;IAZD,6BAA0B;IAGxB,cAAwB;IAW1B,wCASC;IAED;;;;;OAKG;IACH,iCAJW,MAAM,cACN,MAAM,GACJ,OAAO,CA2CnB;;CACF;8BAGY,OAAO,YAAY,EAAE,eAAe"}
1
+ {"version":3,"file":"harvester.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/harvester.js"],"names":[],"mappings":"AA6GA;;;;IAII;AACJ;;;;;;;;;IAFc,OAAO,CAuHpB;AA9MD;IAIE,2BAUC;IAZD,6BAA0B;IAGxB,cAAwB;IAW1B,wCASC;IAED;;;;;OAKG;IACH,iCAJW,MAAM,cACN,MAAM,GACJ,OAAO,CA6CnB;;CACF;8BAGY,OAAO,YAAY,EAAE,eAAe"}
@@ -20,7 +20,6 @@
20
20
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
21
21
  * @property {HarvestPayload} payload Object representing payload.
22
22
  * @property {object} localOpts Additional options for sending data
23
- * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
24
23
  * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
25
24
  * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
26
25
  * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
@@ -51,7 +50,6 @@ export type NetworkSendSpec = {
51
50
  * Additional options for sending data
52
51
  */
53
52
  localOpts: {
54
- needResponse: boolean;
55
53
  isFinalHarvest: boolean;
56
54
  sendEmptyBody: boolean;
57
55
  forceNoRetry: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/types.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;;;;;;;;;GAUG;AAEH,wBAAwB;wCArBX,KAAK,GAAC,UAAU,GAAC,QAAQ,GAAC,KAAK,GAAC,WAAW,GAAC,MAAM;;;;;QAKjD,MAAM;;;;UACN,MAAM;;;;;;cAKN,yBAAyB;;;;aACzB,cAAc;;;;eAEzB;QAA8B,YAAY,EAA/B,OAAO;QACY,cAAc,EAAjC,OAAO;QACY,aAAa,EAAhC,OAAO;QACY,YAAY,EAA/B,OAAO;KAClB;;;;kBAAW,OAAO,wBAAwB,EAAE,cAAc"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/common/harvest/types.js"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;GAIG;AAEH;;GAEG;AAEH;;;;GAIG;AAEH;;;;;;;;;GASG;AAEH,wBAAwB;wCApBX,KAAK,GAAC,UAAU,GAAC,QAAQ,GAAC,KAAK,GAAC,WAAW,GAAC,MAAM;;;;;QAKjD,MAAM;;;;UACN,MAAM;;;;;;cAKN,yBAAyB;;;;aACzB,cAAc;;;;eAEzB;QAA8B,cAAc,EAAjC,OAAO;QACY,aAAa,EAAhC,OAAO;QACY,YAAY,EAA/B,OAAO;KAClB;;;;kBAAW,OAAO,wBAAwB,EAAE,cAAc"}
@@ -4,18 +4,36 @@ export class Aggregate extends AggregateBase {
4
4
  timeToFirstByte: number;
5
5
  firstByteToWindowLoad: number;
6
6
  firstByteToDomContent: number;
7
+ retries: number;
7
8
  /**
8
9
  *
9
10
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
10
11
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
11
- * @param {*} target The target to harvest to
12
12
  */
13
- sendRum(customAttributes?: any, target?: any): void;
13
+ sendRum(customAttributes?: any): void;
14
+ queryStringsBuilder: (() => {
15
+ xx: any;
16
+ ua: any;
17
+ at: any;
18
+ qt: any;
19
+ ap: any;
20
+ be: number;
21
+ fe: number;
22
+ dc: number;
23
+ tt: any;
24
+ us: any;
25
+ ac: any;
26
+ pr: any;
27
+ af: string;
28
+ }) | undefined;
14
29
  rumStartTime: number | undefined;
15
- postHarvestCleanup({ status, responseText, xhr }: {
30
+ serializer(eventBuffer: any): any;
31
+ postHarvestCleanup({ sent, status, responseText, xhr, retry }: {
32
+ sent: any;
16
33
  status: any;
17
34
  responseText: any;
18
35
  xhr: any;
36
+ retry: any;
19
37
  }): void;
20
38
  }
21
39
  import { AggregateBase } from '../../utils/aggregate-base';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAA2C;IAC3C,2BA0BC;IAvBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAuBhC;;;;;OAKG;IACH,2BAHW,GAAC,WACD,GAAC,QAsEX;IAVC,iCAAyB;IAY3B;;;;aAwFC;CACF;8BA9M6B,4BAA4B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/features/page_view_event/aggregate/index.js"],"names":[],"mappings":"AAuBA;IACE,2BAA2C;IAC3C,2BA2BC;IAxBC,wBAAwB;IACxB,8BAA8B;IAC9B,8BAA8B;IAC9B,gBAAgB;IAuBlB;;;;OAIG;IACH,2BAFW,GAAC,QAoEX;IAbC;;;;;;;;;;;;;;mBAOC;IANC,iCAAyB;IAc7B,kCAEC;IAED;;;;;;aAiGC;CACF;8BAzN6B,4BAA4B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newrelic/browser-agent",
3
- "version": "1.302.0-rc.6",
3
+ "version": "1.302.0-rc.7",
4
4
  "private": false,
5
5
  "author": "New Relic Browser Agent Team <browser-agent@newrelic.com>",
6
6
  "description": "New Relic Browser Agent",
@@ -18,8 +18,10 @@ import { getSubmitMethod, xhr as xhrMethod, xhrFetch as fetchMethod } from '../u
18
18
  import { activatedFeatures } from '../util/feature-flags'
19
19
  import { dispatchGlobalEvent } from '../dispatch/global-event'
20
20
 
21
- const RETRY_FAILED = 'Harvester/Retry/Failed/'
22
- const RETRY_SUCCEEDED = 'Harvester/Retry/Succeeded/'
21
+ const RETRY = 'Harvester/Retry/'
22
+ const RETRY_ATTEMPTED = RETRY + 'Attempted/'
23
+ const RETRY_FAILED = RETRY + 'Failed/'
24
+ const RETRY_SUCCEEDED = RETRY + 'Succeeded/'
23
25
 
24
26
  export class Harvester {
25
27
  #started = false
@@ -62,7 +64,7 @@ export class Harvester {
62
64
  if (!submitMethod) return output
63
65
 
64
66
  const shouldRetryOnFail = !localOpts.isFinalHarvest && submitMethod === xhrMethod // always retry all features harvests except for final
65
- output.payload = !localOpts.directSend ? aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts) : localOpts.directSend?.payload // features like PVE can define the payload directly, bypassing the makeHarvestPayload logic
67
+ output.payload = aggregateInst.makeHarvestPayload(shouldRetryOnFail, localOpts)
66
68
 
67
69
  if (!output.payload) return output
68
70
 
@@ -86,7 +88,9 @@ export class Harvester {
86
88
  */
87
89
  function cbFinished (result) {
88
90
  if (aggregateInst.harvestOpts.prevAttemptCode) { // this means we just retried a harvest that last failed
89
- handle(SUPPORTABILITY_METRIC_CHANNEL, [(result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode], undefined, FEATURE_NAMES.metrics, aggregateInst.ee)
91
+ const reportSM = (message) => handle(SUPPORTABILITY_METRIC_CHANNEL, [message], undefined, FEATURE_NAMES.metrics, aggregateInst.ee)
92
+ reportSM(RETRY_ATTEMPTED + aggregateInst.featureName)
93
+ reportSM((result.retry ? RETRY_FAILED : RETRY_SUCCEEDED) + aggregateInst.harvestOpts.prevAttemptCode)
90
94
  delete aggregateInst.harvestOpts.prevAttemptCode // always reset last observation so we don't falsely report again next harvest
91
95
  // In case this re-attempt failed again, that'll be handled (re-marked again) next.
92
96
  }
@@ -155,8 +159,7 @@ export function send (agentRef, { endpoint, payload, localOpts = {}, submitMetho
155
159
  result.addEventListener('loadend', function () {
156
160
  // `this` here in block refers to the XHR object in this scope, do not change the anon function to an arrow function
157
161
  // status 0 refers to a local error, such as CORS or network failure, or a blocked request by the browser (e.g. adblocker)
158
- const cbResult = { sent: this.status !== 0, status: this.status, retry: shouldRetry(this.status), fullUrl, xhr: this }
159
- if (localOpts.needResponse) cbResult.responseText = this.responseText
162
+ const cbResult = { sent: this.status !== 0, status: this.status, retry: shouldRetry(this.status), fullUrl, xhr: this, responseText: this.responseText }
160
163
  cbFinished(cbResult)
161
164
 
162
165
  /** temporary audit of consistency of harvest metadata flags */
@@ -165,8 +168,7 @@ export function send (agentRef, { endpoint, payload, localOpts = {}, submitMetho
165
168
  } else if (submitMethod === fetchMethod) {
166
169
  result.then(async function (response) {
167
170
  const status = response.status
168
- const cbResult = { sent: true, status, retry: shouldRetry(status), fullUrl, fetchResponse: response }
169
- if (localOpts.needResponse) cbResult.responseText = await response.text()
171
+ const cbResult = { sent: true, status, retry: shouldRetry(status), fullUrl, fetchResponse: response, responseText: await response.text() }
170
172
  cbFinished(cbResult)
171
173
  /** temporary audit of consistency of harvest metadata flags */
172
174
  if (!shouldRetry(status)) trackHarvestMetadata()
@@ -24,7 +24,6 @@
24
24
  * @property {HarvestEndpointIdentifier} endpoint The endpoint to use (jserrors, events, resources etc.)
25
25
  * @property {HarvestPayload} payload Object representing payload.
26
26
  * @property {object} localOpts Additional options for sending data
27
- * @property {boolean} localOpts.needResponse Specify whether the caller expects a response data.
28
27
  * @property {boolean} localOpts.isFinalHarvest Specify whether the call is a final harvest during page unload.
29
28
  * @property {boolean} localOpts.sendEmptyBody Specify whether the call should be made even if the body is empty. Useful for rum calls.
30
29
  * @property {boolean} localOpts.forceNoRetry Don't save the buffered data in the case of a need to retry the transmission.
@@ -29,6 +29,7 @@ export class Aggregate extends AggregateBase {
29
29
  this.timeToFirstByte = 0
30
30
  this.firstByteToWindowLoad = 0 // our "frontend" duration
31
31
  this.firstByteToDomContent = 0 // our "dom processing" duration
32
+ this.retries = 0
32
33
 
33
34
  if (!isValid(agentRef.info)) {
34
35
  this.ee.abort()
@@ -55,9 +56,8 @@ export class Aggregate extends AggregateBase {
55
56
  *
56
57
  * @param {Function} cb A function to run once the RUM call has finished - Defaults to activateFeatures
57
58
  * @param {*} customAttributes custom attributes to attach to the RUM call - Defaults to info.js
58
- * @param {*} target The target to harvest to
59
59
  */
60
- sendRum (customAttributes = this.agentRef.info.jsAttributes, target = { licenseKey: this.agentRef.info.licenseKey, applicationID: this.agentRef.info.applicationID }) {
60
+ sendRum (customAttributes = this.agentRef.info.jsAttributes) {
61
61
  const info = this.agentRef.info
62
62
  const measures = {}
63
63
 
@@ -110,24 +110,26 @@ export class Aggregate extends AggregateBase {
110
110
  queryParameters.fp = firstPaint.current.value
111
111
  queryParameters.fcp = firstContentfulPaint.current.value
112
112
 
113
- const timeKeeper = this.agentRef.runtime.timeKeeper
114
- if (timeKeeper?.ready) {
115
- queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(now()))
113
+ this.queryStringsBuilder = () => { // this will be called by AggregateBase.makeHarvestPayload every time harvest is triggered to be qs
114
+ this.rumStartTime = now() // this should be reset at the beginning of each RUM call for proper timeKeeper calculation in coordination with postHarvestCleanup
115
+ const timeKeeper = this.agentRef.runtime.timeKeeper
116
+ if (timeKeeper?.ready) {
117
+ queryParameters.timestamp = Math.floor(timeKeeper.correctRelativeTimestamp(this.rumStartTime))
118
+ }
119
+ return queryParameters
116
120
  }
117
-
118
- this.rumStartTime = now()
121
+ this.events.add(body)
119
122
 
120
123
  this.agentRef.runtime.harvester.triggerHarvestFor(this, {
121
- directSend: {
122
- target,
123
- payload: { qs: queryParameters, body }
124
- },
125
- needResponse: true,
126
124
  sendEmptyBody: true
127
125
  })
128
126
  }
129
127
 
130
- postHarvestCleanup ({ status, responseText, xhr }) {
128
+ serializer (eventBuffer) { // this is necessary because PVE sends a single item rather than an array; in the case of undefined, this prevents sending [null] as body
129
+ return eventBuffer[0]
130
+ }
131
+
132
+ postHarvestCleanup ({ sent, status, responseText, xhr, retry }) {
131
133
  const rumEndTime = now()
132
134
  let app, flags
133
135
  try {
@@ -137,8 +139,16 @@ export class Aggregate extends AggregateBase {
137
139
  warn(53, error)
138
140
  }
139
141
 
142
+ super.postHarvestCleanup({ sent, retry }) // this will set isRetrying & re-buffer the body if request is to be retried
143
+ if (this.isRetrying && this.retries++ < 1) { // Only retry once
144
+ setTimeout(() => this.agentRef.runtime.harvester.triggerHarvestFor(this, {
145
+ sendEmptyBody: true
146
+ }), 5000) // Retry sending the RUM event after 5 seconds
147
+ return
148
+ }
140
149
  if (status >= 400 || status === 0) {
141
150
  warn(18, status)
151
+ this.blocked = true
142
152
 
143
153
  // Get estimated payload size of our backlog
144
154
  const textEncoder = new TextEncoder()
@@ -205,6 +215,7 @@ export class Aggregate extends AggregateBase {
205
215
  }
206
216
  } catch (error) {
207
217
  this.ee.abort()
218
+ this.blocked = true
208
219
  warn(17, error)
209
220
  return
210
221
  }