@newrelic/browser-agent 1.256.1 → 1.258.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.
Files changed (140) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/cjs/common/config/state/configurable.js +8 -5
  3. package/dist/cjs/common/config/state/init.js +0 -2
  4. package/dist/cjs/common/config/state/runtime.js +10 -8
  5. package/dist/cjs/common/constants/env.cdn.js +1 -1
  6. package/dist/cjs/common/constants/env.npm.js +1 -1
  7. package/dist/cjs/common/constants/runtime.js +8 -2
  8. package/dist/cjs/common/harvest/harvest.js +7 -5
  9. package/dist/cjs/common/session/constants.js +1 -0
  10. package/dist/cjs/common/session/session-entity.js +3 -0
  11. package/dist/cjs/common/timing/time-keeper.js +45 -9
  12. package/dist/cjs/common/vitals/time-to-first-byte.js +1 -1
  13. package/dist/cjs/common/vitals/vital-metric.js +1 -1
  14. package/dist/cjs/features/ajax/aggregate/chunk.js +50 -0
  15. package/dist/cjs/features/ajax/aggregate/index.js +131 -191
  16. package/dist/cjs/features/ajax/instrument/index.js +0 -3
  17. package/dist/cjs/features/jserrors/aggregate/index.js +26 -13
  18. package/dist/cjs/features/page_view_event/aggregate/index.js +3 -3
  19. package/dist/cjs/features/session_replay/aggregate/index.js +12 -5
  20. package/dist/cjs/features/session_replay/constants.js +2 -1
  21. package/dist/cjs/features/session_replay/instrument/index.js +15 -5
  22. package/dist/cjs/features/session_replay/shared/recorder.js +6 -3
  23. package/dist/cjs/features/session_replay/shared/utils.js +9 -8
  24. package/dist/cjs/features/session_trace/aggregate/index.js +3 -5
  25. package/dist/cjs/features/soft_navigations/aggregate/index.js +2 -2
  26. package/dist/cjs/features/spa/instrument/index.js +0 -2
  27. package/dist/cjs/features/utils/agent-session.js +1 -5
  28. package/dist/cjs/features/utils/instrument-base.js +11 -14
  29. package/dist/cjs/features/utils/nr1-debugger.js +27 -0
  30. package/dist/cjs/loaders/agent.js +4 -0
  31. package/dist/cjs/loaders/api/apiAsync.js +5 -4
  32. package/dist/esm/common/config/state/configurable.js +8 -5
  33. package/dist/esm/common/config/state/init.js +0 -2
  34. package/dist/esm/common/config/state/runtime.js +11 -9
  35. package/dist/esm/common/constants/env.cdn.js +1 -1
  36. package/dist/esm/common/constants/env.npm.js +1 -1
  37. package/dist/esm/common/constants/runtime.js +7 -1
  38. package/dist/esm/common/harvest/harvest.js +7 -5
  39. package/dist/esm/common/session/constants.js +1 -0
  40. package/dist/esm/common/session/session-entity.js +3 -0
  41. package/dist/esm/common/timing/time-keeper.js +46 -9
  42. package/dist/esm/common/vitals/time-to-first-byte.js +2 -2
  43. package/dist/esm/common/vitals/vital-metric.js +1 -1
  44. package/dist/esm/features/ajax/aggregate/chunk.js +43 -0
  45. package/dist/esm/features/ajax/aggregate/index.js +130 -191
  46. package/dist/esm/features/ajax/instrument/index.js +1 -4
  47. package/dist/esm/features/jserrors/aggregate/index.js +26 -13
  48. package/dist/esm/features/page_view_event/aggregate/index.js +4 -4
  49. package/dist/esm/features/session_replay/aggregate/index.js +12 -5
  50. package/dist/esm/features/session_replay/constants.js +2 -1
  51. package/dist/esm/features/session_replay/instrument/index.js +16 -6
  52. package/dist/esm/features/session_replay/shared/recorder.js +6 -3
  53. package/dist/esm/features/session_replay/shared/utils.js +9 -7
  54. package/dist/esm/features/session_trace/aggregate/index.js +3 -5
  55. package/dist/esm/features/soft_navigations/aggregate/index.js +2 -2
  56. package/dist/esm/features/spa/instrument/index.js +0 -2
  57. package/dist/esm/features/utils/agent-session.js +1 -5
  58. package/dist/esm/features/utils/instrument-base.js +11 -14
  59. package/dist/esm/features/utils/nr1-debugger.js +21 -0
  60. package/dist/esm/loaders/agent.js +4 -0
  61. package/dist/esm/loaders/api/apiAsync.js +5 -4
  62. package/dist/types/common/config/state/configurable.d.ts.map +1 -1
  63. package/dist/types/common/config/state/init.d.ts.map +1 -1
  64. package/dist/types/common/config/state/runtime.d.ts.map +1 -1
  65. package/dist/types/common/constants/runtime.d.ts +6 -1
  66. package/dist/types/common/constants/runtime.d.ts.map +1 -1
  67. package/dist/types/common/harvest/harvest.d.ts +1 -1
  68. package/dist/types/common/harvest/harvest.d.ts.map +1 -1
  69. package/dist/types/common/session/constants.d.ts +1 -0
  70. package/dist/types/common/session/session-entity.d.ts.map +1 -1
  71. package/dist/types/common/timing/time-keeper.d.ts +1 -1
  72. package/dist/types/common/timing/time-keeper.d.ts.map +1 -1
  73. package/dist/types/features/ajax/aggregate/chunk.d.ts +8 -0
  74. package/dist/types/features/ajax/aggregate/chunk.d.ts.map +1 -0
  75. package/dist/types/features/ajax/aggregate/index.d.ts +8 -6
  76. package/dist/types/features/ajax/aggregate/index.d.ts.map +1 -1
  77. package/dist/types/features/ajax/instrument/index.d.ts +2 -2
  78. package/dist/types/features/ajax/instrument/index.d.ts.map +1 -1
  79. package/dist/types/features/jserrors/aggregate/index.d.ts +0 -1
  80. package/dist/types/features/jserrors/aggregate/index.d.ts.map +1 -1
  81. package/dist/types/features/session_replay/aggregate/index.d.ts +0 -1
  82. package/dist/types/features/session_replay/aggregate/index.d.ts.map +1 -1
  83. package/dist/types/features/session_replay/constants.d.ts +1 -0
  84. package/dist/types/features/session_replay/constants.d.ts.map +1 -1
  85. package/dist/types/features/session_replay/instrument/index.d.ts +1 -0
  86. package/dist/types/features/session_replay/instrument/index.d.ts.map +1 -1
  87. package/dist/types/features/session_replay/shared/recorder.d.ts +1 -0
  88. package/dist/types/features/session_replay/shared/recorder.d.ts.map +1 -1
  89. package/dist/types/features/session_replay/shared/utils.d.ts +5 -5
  90. package/dist/types/features/session_replay/shared/utils.d.ts.map +1 -1
  91. package/dist/types/features/session_trace/aggregate/index.d.ts +8 -8
  92. package/dist/types/features/session_trace/aggregate/index.d.ts.map +1 -1
  93. package/dist/types/features/spa/instrument/index.d.ts.map +1 -1
  94. package/dist/types/features/utils/agent-session.d.ts.map +1 -1
  95. package/dist/types/features/utils/instrument-base.d.ts.map +1 -1
  96. package/dist/types/features/utils/nr1-debugger.d.ts +2 -0
  97. package/dist/types/features/utils/nr1-debugger.d.ts.map +1 -0
  98. package/dist/types/loaders/agent.d.ts +5 -1
  99. package/dist/types/loaders/agent.d.ts.map +1 -1
  100. package/dist/types/loaders/api/apiAsync.d.ts.map +1 -1
  101. package/package.json +1 -1
  102. package/src/common/config/state/configurable.js +9 -8
  103. package/src/common/config/state/init.js +0 -1
  104. package/src/common/config/state/runtime.js +12 -9
  105. package/src/common/constants/__mocks__/runtime.js +2 -0
  106. package/src/common/constants/runtime.js +6 -1
  107. package/src/common/drain/__mocks__/drain.js +2 -0
  108. package/src/common/harvest/harvest.js +8 -6
  109. package/src/common/session/constants.js +1 -0
  110. package/src/common/session/session-entity.js +2 -0
  111. package/src/common/timing/time-keeper.js +44 -10
  112. package/src/common/vitals/time-to-first-byte.js +2 -2
  113. package/src/common/vitals/vital-metric.js +1 -1
  114. package/src/common/window/__mocks__/load.js +3 -0
  115. package/src/features/ajax/aggregate/chunk.js +51 -0
  116. package/src/features/ajax/aggregate/index.js +128 -200
  117. package/src/features/ajax/instrument/index.js +1 -4
  118. package/src/features/jserrors/aggregate/index.js +28 -11
  119. package/src/features/page_view_event/aggregate/index.js +4 -4
  120. package/src/features/session_replay/aggregate/index.js +19 -7
  121. package/src/features/session_replay/constants.js +2 -1
  122. package/src/features/session_replay/instrument/index.js +16 -6
  123. package/src/features/session_replay/shared/__mocks__/utils.js +2 -0
  124. package/src/features/session_replay/shared/recorder.js +7 -4
  125. package/src/features/session_replay/shared/utils.js +9 -7
  126. package/src/features/session_trace/aggregate/index.js +3 -6
  127. package/src/features/soft_navigations/aggregate/index.js +2 -2
  128. package/src/features/spa/instrument/index.js +0 -3
  129. package/src/features/utils/__mocks__/agent-session.js +1 -0
  130. package/src/features/utils/__mocks__/feature-base.js +11 -0
  131. package/src/features/utils/agent-session.js +1 -7
  132. package/src/features/utils/instrument-base.js +11 -14
  133. package/src/features/utils/nr1-debugger.js +22 -0
  134. package/src/loaders/agent.js +4 -0
  135. package/src/loaders/api/apiAsync.js +5 -4
  136. package/dist/cjs/common/storage/first-party-cookies.js +0 -36
  137. package/dist/esm/common/storage/first-party-cookies.js +0 -29
  138. package/dist/types/common/storage/first-party-cookies.d.ts +0 -8
  139. package/dist/types/common/storage/first-party-cookies.d.ts.map +0 -1
  140. package/src/common/storage/first-party-cookies.js +0 -32
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.Aggregate = void 0;
7
7
  var _registerHandler = require("../../../common/event-emitter/register-handler");
8
8
  var _stringify = require("../../../common/util/stringify");
9
- var _belSerializer = require("../../../common/serialize/bel-serializer");
10
9
  var _handle = require("../../../common/event-emitter/handle");
11
10
  var _config = require("../../../common/config/config");
12
11
  var _harvestScheduler = require("../../../common/harvest/harvest-scheduler");
@@ -17,6 +16,8 @@ var _constants2 = require("../../metrics/constants");
17
16
  var _aggregateBase = require("../../utils/aggregate-base");
18
17
  var _gql = require("./gql");
19
18
  var _nreum = require("../../../common/window/nreum");
19
+ var _chunk = _interopRequireDefault(require("./chunk"));
20
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
20
21
  /*
21
22
  * Copyright 2020 New Relic Corporation. All rights reserved.
22
23
  * SPDX-License-Identifier: Apache-2.0
@@ -24,224 +25,163 @@ var _nreum = require("../../../common/window/nreum");
24
25
 
25
26
  class Aggregate extends _aggregateBase.AggregateBase {
26
27
  static featureName = _constants.FEATURE_NAME;
28
+ #agentInfo;
29
+ #agentRuntime;
30
+ #agentInit;
27
31
  constructor(agentIdentifier, aggregator) {
28
32
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME);
29
- const agentInit = (0, _config.getConfiguration)(agentIdentifier);
30
- (0, _registerHandler.registerHandler)('xhr', storeXhr, this.featureName, this.ee);
31
- this.waitForFlags([]).then(() => {
32
- const scheduler = new _harvestScheduler.HarvestScheduler('events', {
33
- onFinished: onEventsHarvestFinished,
34
- getPayload: prepareHarvest
35
- }, this);
36
- scheduler.startTimer(harvestTimeSeconds);
37
- this.drain();
38
- });
39
- const agentRuntime = (0, _config.getRuntime)(agentIdentifier);
40
- const denyList = agentRuntime.denyList;
41
- (0, _denyList.setDenyList)(denyList);
42
- let ajaxEvents = [];
43
- let spaAjaxEvents = {};
44
- let sentAjaxEvents = [];
45
- const ee = this.ee;
46
- const harvestTimeSeconds = agentInit.ajax.harvestTimeSeconds || 10;
47
- const MAX_PAYLOAD_SIZE = agentInit.ajax.maxPayloadSize || 1000000;
48
-
49
- // Exposes these methods to browser test files -- future TO DO: can be removed once these fns are extracted from the constructor into class func
50
- this.storeXhr = storeXhr;
51
- this.prepareHarvest = prepareHarvest;
52
- this.getStoredEvents = function () {
53
- return {
54
- ajaxEvents,
55
- spaAjaxEvents
56
- };
57
- };
33
+ this.#agentInfo = (0, _config.getInfo)(agentIdentifier);
34
+ this.#agentRuntime = (0, _config.getRuntime)(agentIdentifier);
35
+ this.#agentInit = (0, _config.getConfiguration)(agentIdentifier);
36
+ const harvestTimeSeconds = this.#agentInit.ajax.harvestTimeSeconds || 10;
37
+ this.MAX_PAYLOAD_SIZE = this.#agentInit.ajax.maxPayloadSize || 1000000;
38
+ (0, _denyList.setDenyList)(this.#agentRuntime.denyList);
39
+ this.ajaxEvents = [];
40
+ this.spaAjaxEvents = {};
41
+ this.sentAjaxEvents = [];
42
+ const classThis = this;
58
43
 
59
44
  // --- v Used by old spa feature
60
- ee.on('interactionDone', (interaction, wasSaved) => {
61
- if (!spaAjaxEvents[interaction.id]) return;
45
+ this.ee.on('interactionDone', (interaction, wasSaved) => {
46
+ if (!this.spaAjaxEvents[interaction.id]) return;
62
47
  if (!wasSaved) {
63
48
  // if the ixn was saved, then its ajax reqs are part of the payload whereas if it was discarded, it should still be harvested in the ajax feature itself
64
- spaAjaxEvents[interaction.id].forEach(function (item) {
65
- ajaxEvents.push(item);
66
- });
49
+ this.spaAjaxEvents[interaction.id].forEach(item => this.ajaxEvents.push(item));
67
50
  }
68
- delete spaAjaxEvents[interaction.id];
51
+ delete this.spaAjaxEvents[interaction.id];
69
52
  });
70
53
  // --- ^
71
54
  // --- v Used by new soft nav
72
- (0, _registerHandler.registerHandler)('returnAjax', event => ajaxEvents.push(event), this.featureName, this.ee);
55
+ (0, _registerHandler.registerHandler)('returnAjax', event => this.ajaxEvents.push(event), this.featureName, this.ee);
73
56
  // --- ^
57
+ (0, _registerHandler.registerHandler)('xhr', function () {
58
+ // the EE-drain system not only switches "this" but also passes a new EventContext with info. Should consider platform refactor to another system which passes a mutable context around separately and predictably to avoid problems like this.
59
+ classThis.storeXhr(...arguments, this); // this switches the context back to the class instance while passing the NR context as an argument -- see "ctx" in storeXhr
60
+ }, this.featureName, this.ee);
61
+ this.waitForFlags([]).then(() => {
62
+ const scheduler = new _harvestScheduler.HarvestScheduler('events', {
63
+ onFinished: this.onEventsHarvestFinished.bind(this),
64
+ getPayload: this.prepareHarvest.bind(this)
65
+ }, this);
66
+ scheduler.startTimer(harvestTimeSeconds);
67
+ this.drain();
68
+ });
69
+ }
70
+ storeXhr(params, metrics, startTime, endTime, type, ctx) {
71
+ metrics.time = startTime;
74
72
 
75
- const beacon = (0, _config.getInfo)(agentIdentifier).errorBeacon;
76
- const proxyBeacon = agentInit.proxy.beacon;
77
- function storeXhr(params, metrics, startTime, endTime, type) {
78
- metrics.time = startTime;
73
+ // send to session traces
74
+ let hash;
75
+ if (params.cat) {
76
+ hash = (0, _stringify.stringify)([params.status, params.cat]);
77
+ } else {
78
+ hash = (0, _stringify.stringify)([params.status, params.host, params.pathname]);
79
+ }
80
+ const shouldCollect = (0, _denyList.shouldCollectEvent)(params);
81
+ const shouldOmitAjaxMetrics = this.#agentInit.feature_flags?.includes('ajax_metrics_deny_list');
79
82
 
80
- // send to session traces
81
- var hash;
82
- if (params.cat) {
83
- hash = (0, _stringify.stringify)([params.status, params.cat]);
83
+ // store for timeslice metric (harvested by jserrors feature)
84
+ if (shouldCollect || !shouldOmitAjaxMetrics) {
85
+ this.aggregator.store('xhr', hash, params, metrics);
86
+ }
87
+ if (!shouldCollect) {
88
+ if (params.hostname === this.#agentInfo.errorBeacon || this.#agentInit.proxy?.beacon && params.hostname === this.#agentInit.proxy.beacon) {
89
+ // This doesn't make a distinction if the same-domain request is going to a different port or path...
90
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/Agent'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
91
+ if (shouldOmitAjaxMetrics) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Metrics/Excluded/Agent'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
84
92
  } else {
85
- hash = (0, _stringify.stringify)([params.status, params.host, params.pathname]);
93
+ (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/App'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
94
+ if (shouldOmitAjaxMetrics) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Metrics/Excluded/App'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
86
95
  }
87
- const shouldCollect = (0, _denyList.shouldCollectEvent)(params);
88
- const ajaxMetricDenyListEnabled = agentInit.feature_flags?.includes('ajax_metrics_deny_list');
96
+ return; // do not send this ajax as an event
97
+ }
98
+ (0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, this.ee); // have trace feature harvest AjaxNode
89
99
 
90
- // store as metric
91
- if (shouldCollect || !ajaxMetricDenyListEnabled) {
92
- aggregator.store('xhr', hash, params, metrics);
93
- }
94
- if (!shouldCollect) {
95
- if (params.hostname === beacon || proxyBeacon && params.hostname === proxyBeacon) {
96
- // This doesn't make a distinction if the same-domain request is going to a different port or path...
97
- (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/Agent'], undefined, _features.FEATURE_NAMES.metrics, ee);
98
- if (ajaxMetricDenyListEnabled) {
99
- (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Metrics/Excluded/Agent'], undefined, _features.FEATURE_NAMES.metrics, ee);
100
- }
101
- } else {
102
- (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/Excluded/App'], undefined, _features.FEATURE_NAMES.metrics, ee);
103
- if (ajaxMetricDenyListEnabled) {
104
- (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Metrics/Excluded/App'], undefined, _features.FEATURE_NAMES.metrics, ee);
105
- }
106
- }
107
- return;
108
- }
109
- (0, _handle.handle)('bstXhrAgg', ['xhr', hash, params, metrics], undefined, _features.FEATURE_NAMES.sessionTrace, ee);
110
- var xhrContext = this;
111
- var event = {
112
- method: params.method,
113
- status: params.status,
114
- domain: params.host,
115
- path: params.pathname,
116
- requestSize: metrics.txSize,
117
- responseSize: metrics.rxSize,
118
- type,
119
- startTime,
120
- endTime,
121
- callbackDuration: metrics.cbTime
122
- };
123
- if (xhrContext.dt) {
124
- event.spanId = xhrContext.dt.spanId;
125
- event.traceId = xhrContext.dt.traceId;
126
- event.spanTimestamp = agentRuntime.timeKeeper.correctAbsoluteTimestamp(xhrContext.dt.timestamp);
127
- }
100
+ const event = {
101
+ method: params.method,
102
+ status: params.status,
103
+ domain: params.host,
104
+ path: params.pathname,
105
+ requestSize: metrics.txSize,
106
+ responseSize: metrics.rxSize,
107
+ type,
108
+ startTime,
109
+ endTime,
110
+ callbackDuration: metrics.cbTime
111
+ };
112
+ if (ctx.dt) {
113
+ event.spanId = ctx.dt.spanId;
114
+ event.traceId = ctx.dt.traceId;
115
+ event.spanTimestamp = this.#agentRuntime.timeKeeper.correctAbsoluteTimestamp(ctx.dt.timestamp);
116
+ }
128
117
 
129
- // parsed from the AJAX body, looking for operationName param & parsing query for operationType
130
- event.gql = params.gql = (0, _gql.parseGQL)({
131
- body: this.body,
132
- query: this?.parsedOrigin?.search
133
- });
134
- if (event.gql) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', (0, _stringify.stringify)(event.gql).length], undefined, _features.FEATURE_NAMES.metrics, ee);
135
- const softNavInUse = Boolean((0, _nreum.getNREUMInitializedAgent)(agentIdentifier)?.features?.[_features.FEATURE_NAMES.softNav]);
136
- if (softNavInUse) {
137
- // For newer soft nav (when running), pass the event to it for evaluation -- either part of an interaction or is given back
138
- (0, _handle.handle)('ajax', [event], undefined, _features.FEATURE_NAMES.softNav, ee);
139
- } else if (this.spaNode) {
140
- // For old spa (when running), if the ajax happened inside an interaction, hold it until the interaction finishes
141
- const interactionId = this.spaNode.interaction.id;
142
- spaAjaxEvents[interactionId] = spaAjaxEvents[interactionId] || [];
143
- spaAjaxEvents[interactionId].push(event);
144
- } else {
145
- ajaxEvents.push(event);
146
- }
118
+ // parsed from the AJAX body, looking for operationName param & parsing query for operationType
119
+ event.gql = params.gql = (0, _gql.parseGQL)({
120
+ body: ctx.body,
121
+ query: ctx.parsedOrigin?.search
122
+ });
123
+ if (event.gql) (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Ajax/Events/GraphQL/Bytes-Added', (0, _stringify.stringify)(event.gql).length], undefined, _features.FEATURE_NAMES.metrics, this.ee);
124
+ const softNavInUse = Boolean((0, _nreum.getNREUMInitializedAgent)(this.agentIdentifier)?.features?.[_features.FEATURE_NAMES.softNav]);
125
+ if (softNavInUse) {
126
+ // For newer soft nav (when running), pass the event to it for evaluation -- either part of an interaction or is given back
127
+ (0, _handle.handle)('ajax', [event], undefined, _features.FEATURE_NAMES.softNav, this.ee);
128
+ } else if (ctx.spaNode) {
129
+ // For old spa (when running), if the ajax happened inside an interaction, hold it until the interaction finishes
130
+ const interactionId = ctx.spaNode.interaction.id;
131
+ this.spaAjaxEvents[interactionId] = this.spaAjaxEvents[interactionId] || [];
132
+ this.spaAjaxEvents[interactionId].push(event);
133
+ } else {
134
+ this.ajaxEvents.push(event);
147
135
  }
148
- function prepareHarvest(options) {
149
- options = options || {};
150
- if (ajaxEvents.length === 0) {
151
- return null;
152
- }
153
- var payload = getPayload(ajaxEvents, options.maxPayloadSize || MAX_PAYLOAD_SIZE);
154
- var payloadObjs = [];
155
- for (var i = 0; i < payload.length; i++) {
156
- payloadObjs.push({
157
- body: {
158
- e: payload[i]
159
- }
160
- });
161
- }
162
- if (options.retry) {
163
- sentAjaxEvents = ajaxEvents.slice();
136
+ }
137
+ prepareHarvest(options) {
138
+ options = options || {};
139
+ if (this.ajaxEvents.length === 0) return null;
140
+ const payload = this.#getPayload(this.ajaxEvents);
141
+ const payloadObjs = [];
142
+ for (let i = 0; i < payload.length; i++) payloadObjs.push({
143
+ body: {
144
+ e: payload[i]
164
145
  }
165
- ajaxEvents = [];
166
- return payloadObjs;
146
+ });
147
+ if (options.retry) this.sentAjaxEvents = this.ajaxEvents;
148
+ this.ajaxEvents = [];
149
+ return payloadObjs;
150
+ }
151
+ onEventsHarvestFinished(result) {
152
+ if (result.retry && this.sentAjaxEvents.length > 0) {
153
+ this.ajaxEvents.unshift(...this.sentAjaxEvents);
154
+ this.sentAjaxEvents = [];
167
155
  }
168
- function getPayload(events, maxPayloadSize, chunks) {
169
- chunks = chunks || 1;
170
- var payload = [];
171
- var chunkSize = events.length / chunks;
172
- var eventChunks = splitChunks(events, chunkSize);
173
- var tooBig = false;
174
- for (var i = 0; i < eventChunks.length; i++) {
175
- var currentChunk = eventChunks[i];
176
- if (currentChunk.tooBig(maxPayloadSize)) {
177
- if (currentChunk.events.length !== 1) {
178
- /* if it is too big BUT it isnt length 1, we can split it down again,
179
- else we just want to NOT push it into payload
180
- because if it's length 1 and still too big for the maxPayloadSize
181
- it cant get any smaller and we dont want to recurse forever */
182
- tooBig = true;
183
- break;
184
- }
185
- } else {
186
- payload.push(currentChunk.payload);
156
+ }
157
+ #getPayload(events, numberOfChunks) {
158
+ numberOfChunks = numberOfChunks || 1;
159
+ const payload = [];
160
+ const chunkSize = events.length / numberOfChunks;
161
+ const eventChunks = splitChunks.call(this, events, chunkSize);
162
+ let tooBig = false;
163
+ for (let i = 0; i < eventChunks.length; i++) {
164
+ const currentChunk = eventChunks[i];
165
+ if (currentChunk.tooBig) {
166
+ if (currentChunk.events.length > 1) {
167
+ tooBig = true;
168
+ break; // if the payload is too big BUT is made of more than 1 event, we can split it down again
187
169
  }
188
- }
189
- // check if the current payload string is too big, if so then run getPayload again with more buckets
190
- return tooBig ? getPayload(events, maxPayloadSize, ++chunks) : payload;
191
- }
192
- function onEventsHarvestFinished(result) {
193
- if (result.retry && sentAjaxEvents.length > 0) {
194
- ajaxEvents.unshift(...sentAjaxEvents);
195
- sentAjaxEvents = [];
170
+ // Otherwise, if it consists of one sole event, we do not send it (discarded) since we cannot break it apart any further.
171
+ } else {
172
+ payload.push(currentChunk.payload);
196
173
  }
197
174
  }
175
+ // Check if the current payload string is too big, if so then run getPayload again with more buckets.
176
+ return tooBig ? this.#getPayload(events, ++numberOfChunks) : payload;
198
177
  function splitChunks(arr, chunkSize) {
199
178
  chunkSize = chunkSize || arr.length;
200
- var chunks = [];
201
- for (var i = 0, len = arr.length; i < len; i += chunkSize) {
202
- chunks.push(new Chunk(arr.slice(i, i + chunkSize)));
179
+ const chunks = [];
180
+ for (let i = 0, len = arr.length; i < len; i += chunkSize) {
181
+ chunks.push(new _chunk.default(arr.slice(i, i + chunkSize), this));
203
182
  }
204
183
  return chunks;
205
184
  }
206
- function Chunk(events) {
207
- this.addString = (0, _belSerializer.getAddStringContext)(agentIdentifier); // pass agentIdentifier here
208
- this.events = events;
209
- this.payload = 'bel.7;';
210
- for (var i = 0; i < events.length; i++) {
211
- var event = events[i];
212
- var fields = [(0, _belSerializer.numeric)(event.startTime), (0, _belSerializer.numeric)(event.endTime - event.startTime), (0, _belSerializer.numeric)(0),
213
- // callbackEnd
214
- (0, _belSerializer.numeric)(0),
215
- // no callbackDuration for non-SPA events
216
- this.addString(event.method), (0, _belSerializer.numeric)(event.status), this.addString(event.domain), this.addString(event.path), (0, _belSerializer.numeric)(event.requestSize), (0, _belSerializer.numeric)(event.responseSize), event.type === 'fetch' ? 1 : '', this.addString(0),
217
- // nodeId
218
- (0, _belSerializer.nullable)(event.spanId, this.addString, true) +
219
- // guid
220
- (0, _belSerializer.nullable)(event.traceId, this.addString, true) +
221
- // traceId
222
- (0, _belSerializer.nullable)(event.spanTimestamp, _belSerializer.numeric, false) // timestamp
223
- ];
224
- var insert = '2,';
225
-
226
- // add custom attributes
227
- // gql decorators are added as custom attributes to alleviate need for new BEL schema
228
- var attrParts = (0, _belSerializer.addCustomAttributes)({
229
- ...((0, _config.getInfo)(agentIdentifier).jsAttributes || {}),
230
- ...(event.gql || {})
231
- }, this.addString);
232
- fields.unshift((0, _belSerializer.numeric)(attrParts.length));
233
- insert += fields.join(',');
234
- if (attrParts && attrParts.length > 0) {
235
- insert += ';' + attrParts.join(';');
236
- }
237
- if (i + 1 < events.length) insert += ';';
238
- this.payload += insert;
239
- }
240
- this.tooBig = function (maxPayloadSize) {
241
- maxPayloadSize = maxPayloadSize || MAX_PAYLOAD_SIZE;
242
- return this.payload.length * 2 > maxPayloadSize;
243
- };
244
- }
245
185
  }
246
186
  }
247
187
  exports.Aggregate = Aggregate;
@@ -33,9 +33,6 @@ class Instrument extends _instrumentBase.InstrumentBase {
33
33
  constructor(agentIdentifier, aggregator) {
34
34
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
35
35
  super(agentIdentifier, aggregator, _constants.FEATURE_NAME, auto);
36
-
37
- // Very unlikely, but in case the existing XMLHttpRequest.prototype object on the page couldn't be wrapped.
38
- if (!(0, _config.getRuntime)(agentIdentifier).xhrWrappable) return;
39
36
  this.dt = new _distributedTracing.DT(agentIdentifier);
40
37
  this.handler = (type, args, ctx, group) => (0, _handle.handle)(type, args, ctx, group, this.ee);
41
38
 
@@ -42,13 +42,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
42
42
  this.bufferedErrorsUnderSpa = {};
43
43
  this.currentBody = undefined;
44
44
  this.errorOnPage = false;
45
- this.replayAborted = false;
46
45
 
47
46
  // this will need to change to match whatever ee we use in the instrument
48
47
  this.ee.on('interactionDone', (interaction, wasSaved) => this.onInteractionDone(interaction, wasSaved));
49
- this.ee.on('REPLAY_ABORTED', () => {
50
- this.replayAborted = true;
51
- });
52
48
  (0, _registerHandler.registerHandler)('err', function () {
53
49
  return _this.storeError(...arguments);
54
50
  }, this.featureName, this.ee);
@@ -94,11 +90,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
94
90
  payload.qs.ri = releaseIds;
95
91
  }
96
92
  if (body && body.err && body.err.length) {
97
- if (this.replayAborted) {
98
- body.err.forEach(e => {
99
- delete e.params?.hasReplay;
100
- });
101
- }
93
+ this.#runCrossFeatureChecks(body.err);
102
94
  if (!this.errorOnPage) {
103
95
  payload.qs.pve = '1';
104
96
  this.errorOnPage = true;
@@ -172,14 +164,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
172
164
  // Notice if filterOutput isn't false|undefined OR our specified object, this func would've returned already (so it's unnecessary to req-check group).
173
165
  // Do not modify the name ('errorGroup') of params without DEM approval!
174
166
  if (filterOutput?.group) params.errorGroup = filterOutput.group;
175
-
167
+ if (hasReplay) params.hasReplay = hasReplay;
176
168
  /**
177
169
  * The bucketHash is different from the params.stackHash because the params.stackHash is based on the canonicalized
178
170
  * stack trace and is used downstream in NR1 to attempt to group the same errors across different browsers. However,
179
171
  * the canonical stack trace excludes items like the column number increasing the hit-rate of different errors potentially
180
172
  * bucketing and ultimately resulting in the loss of data in NR1.
181
173
  */
182
- var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString));
174
+ var bucketHash = (0, _stringHashCode.stringHashCode)("".concat(stackInfo.name, "_").concat(stackInfo.message, "_").concat(stackInfo.stackString, "_").concat(params.hasReplay ? 1 : 0));
183
175
  if (!this.stackReported[bucketHash]) {
184
176
  this.stackReported[bucketHash] = true;
185
177
  params.stack_trace = (0, _formatStackTrace.truncateSize)(stackInfo.stackString);
@@ -198,9 +190,8 @@ class Aggregate extends _aggregateBase.AggregateBase {
198
190
  params.pageview = 1;
199
191
  this.pageviewReported[bucketHash] = true;
200
192
  }
201
- if (hasReplay && !this.replayAborted) params.hasReplay = hasReplay;
202
193
  params.firstOccurrenceTimestamp = this.observedAt[bucketHash];
203
- params.timestamp = this.observedAt[bucketHash];
194
+ params.timestamp = agentRuntime.timeKeeper.convertRelativeTimestamp(time);
204
195
  var type = internal ? 'ierr' : 'err';
205
196
  var newMetrics = {
206
197
  time
@@ -293,5 +284,27 @@ class Aggregate extends _aggregateBase.AggregateBase {
293
284
  );
294
285
  delete this.bufferedErrorsUnderSpa[interactionId]; // wipe the list of jserrors so they aren't duplicated by another call to the same id
295
286
  }
287
+
288
+ /**
289
+ * Dispatches a cross-feature communication event to allow other
290
+ * features to provide flags and data that can be used to mutation
291
+ * to the payload and to allow features to know about a feature
292
+ * harvest happening.
293
+ * @param {any[]} errors Array of errors from the payload body
294
+ */
295
+ #runCrossFeatureChecks(errors) {
296
+ const errorHashes = errors.map(error => error.params.stackHash);
297
+ const crossFeatureData = {
298
+ errorHashes
299
+ };
300
+ this.ee.emit("cfc.".concat(this.featureName), [crossFeatureData]);
301
+ let hasReplayFlag = errors.find(err => err.params.hasReplay);
302
+ if (hasReplayFlag && !crossFeatureData.hasReplay) {
303
+ // Some errors have `hasReplay` and a replay is not being recorded
304
+ errors.forEach(error => {
305
+ delete error.params.hasReplay;
306
+ });
307
+ }
308
+ }
296
309
  }
297
310
  exports.Aggregate = Aggregate;
@@ -103,14 +103,14 @@ class Aggregate extends _aggregateBase.AggregateBase {
103
103
  // Navigation Timing level 2 API that replaced PerformanceTiming & PerformanceNavigation
104
104
  const navTimingEntry = _runtime.globalScope?.performance?.getEntriesByType('navigation')?.[0];
105
105
  const perf = {
106
- timing: (0, _navTiming.addPT)(agentRuntime.offset, navTimingEntry, {}),
106
+ timing: (0, _navTiming.addPT)(_runtime.originTime, navTimingEntry, {}),
107
107
  navigation: (0, _navTiming.addPN)(navTimingEntry, {})
108
108
  };
109
109
  queryParameters.perf = (0, _stringify.stringify)(perf);
110
110
  } else if (typeof PerformanceTiming !== 'undefined') {
111
111
  // Safari pre-15 did not support level 2 timing
112
112
  const perf = {
113
- timing: (0, _navTiming.addPT)(agentRuntime.offset, _runtime.globalScope.performance.timing, {}, true),
113
+ timing: (0, _navTiming.addPT)(_runtime.originTime, _runtime.globalScope.performance.timing, {}, true),
114
114
  navigation: (0, _navTiming.addPN)(_runtime.globalScope.performance.navigation, {})
115
115
  };
116
116
  queryParameters.perf = (0, _stringify.stringify)(perf);
@@ -142,7 +142,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
142
142
  return;
143
143
  }
144
144
  try {
145
- const timeKeeper = new _timeKeeper.TimeKeeper();
145
+ const timeKeeper = new _timeKeeper.TimeKeeper(this.agentIdentifier);
146
146
  timeKeeper.processRumRequest(xhr, rumStartTime, rumEndTime);
147
147
  if (!timeKeeper.ready) throw new Error('TimeKeeper not ready');
148
148
  agentRuntime.timeKeeper = timeKeeper;
@@ -57,9 +57,11 @@ class Aggregate extends _aggregateBase.AggregateBase {
57
57
  /** set at BCS response, stored in runtime */
58
58
  this.timeKeeper = undefined;
59
59
  this.recorder = args?.recorder;
60
- this.preloaded = !!this.recorder;
61
60
  this.errorNoticed = args?.errorNoticed || false;
62
61
  (0, _handle.handle)(_constants2.SUPPORTABILITY_METRIC_CHANNEL, ['Config/SessionReplay/Enabled'], undefined, _features.FEATURE_NAMES.metrics, this.ee);
62
+ this.ee.on("cfc.".concat(_features.FEATURE_NAMES.jserrors), crossFeatureData => {
63
+ crossFeatureData.hasReplay = !!(this.scheduler?.started && this.recorder && this.mode === _constants3.MODE.FULL && !this.blocked && this.entitled);
64
+ });
63
65
 
64
66
  // The SessionEntity class can emit a message indicating the session was cleared and reset (expiry, inactivity). This feature must abort and never resume if that occurs.
65
67
  this.ee.on(_constants3.SESSION_EVENTS.RESET, () => {
@@ -106,6 +108,9 @@ class Aggregate extends _aggregateBase.AggregateBase {
106
108
  (0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.PAUSE, () => {
107
109
  this.forceStop(this.mode !== _constants3.MODE.ERROR);
108
110
  }, this.featureName, this.ee);
111
+ (0, _registerHandler.registerHandler)(_constants.SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, e => {
112
+ this.handleError(e);
113
+ }, this.featureName, this.ee);
109
114
  const {
110
115
  error_sampling_rate,
111
116
  sampling_rate,
@@ -154,15 +159,17 @@ class Aggregate extends _aggregateBase.AggregateBase {
154
159
  }
155
160
  }
156
161
  switchToFull() {
162
+ if (!this.entitled || this.blocked) return;
157
163
  this.mode = _constants3.MODE.FULL;
158
164
  // if the error was noticed AFTER the recorder was already imported....
159
165
  if (this.recorder && this.initialized) {
160
- this.recorder.stopRecording();
161
- this.recorder.startRecording();
166
+ if (!this.recorder.recording) this.recorder.startRecording();
162
167
  this.scheduler.startTimer(this.harvestTimeSeconds);
163
168
  this.syncWithSessionManager({
164
169
  sessionReplayMode: this.mode
165
170
  });
171
+ } else {
172
+ this.initializeRecording(false, true, true);
166
173
  }
167
174
  }
168
175
 
@@ -223,7 +230,6 @@ class Aggregate extends _aggregateBase.AggregateBase {
223
230
 
224
231
  // If an error was noticed before the mode could be set (like in the early lifecycle of the page), immediately set to FULL mode
225
232
  if (this.mode === _constants3.MODE.ERROR && this.errorNoticed) this.mode = _constants3.MODE.FULL;
226
- if (!this.preloaded) this.ee.on('err', e => this.handleError(e));
227
233
 
228
234
  // FULL mode records AND reports from the beginning, while ERROR mode only records (but does not report).
229
235
  // ERROR mode will do this until an error is thrown, and then switch into FULL mode.
@@ -317,7 +323,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
317
323
  return [payload];
318
324
  }
319
325
  getCorrectedTimestamp(node) {
320
- if (!node.timestamp) return;
326
+ if (!node?.timestamp) return;
321
327
  if (node.__newrelic) return node.timestamp;
322
328
  return this.timeKeeper.correctAbsoluteTimestamp(node.timestamp);
323
329
  }
@@ -367,6 +373,7 @@ class Aggregate extends _aggregateBase.AggregateBase {
367
373
  ...(agentMetadata.entityGuid && {
368
374
  entityGuid: agentMetadata.entityGuid
369
375
  }),
376
+ harvestId: [agentRuntime.session?.state.value, agentRuntime.ptid, agentRuntime.harvestCount].filter(x => x).join('_'),
370
377
  'replay.firstTimestamp': firstTimestamp,
371
378
  'replay.lastTimestamp': lastTimestamp,
372
379
  'replay.nodes': events.length,
@@ -10,7 +10,8 @@ const FEATURE_NAME = exports.FEATURE_NAME = _features.FEATURE_NAMES.sessionRepla
10
10
  const SR_EVENT_EMITTER_TYPES = exports.SR_EVENT_EMITTER_TYPES = {
11
11
  RECORD: 'recordReplay',
12
12
  PAUSE: 'pauseReplay',
13
- REPLAY_RUNNING: 'replayRunning'
13
+ REPLAY_RUNNING: 'replayRunning',
14
+ ERROR_DURING_REPLAY: 'errorDuringReplay'
14
15
  };
15
16
  const AVG_COMPRESSION = exports.AVG_COMPRESSION = 0.12;
16
17
  const RRWEB_EVENT_TYPES = exports.RRWEB_EVENT_TYPES = {
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.Instrument = void 0;
7
+ var _handle = require("../../../common/event-emitter/handle");
7
8
  var _constants = require("../../../common/session/constants");
8
9
  var _instrumentBase = require("../../utils/instrument-base");
9
10
  var _constants2 = require("../constants");
@@ -25,19 +26,28 @@ class Instrument extends _instrumentBase.InstrumentBase {
25
26
  let auto = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
26
27
  super(agentIdentifier, aggregator, _constants2.FEATURE_NAME, auto);
27
28
  let session;
29
+ this.replayRunning = false;
28
30
  try {
29
31
  session = JSON.parse(localStorage.getItem("".concat(_constants.PREFIX, "_").concat(_constants.DEFAULT_KEY)));
30
32
  } catch (err) {}
31
33
  if (this.#canPreloadRecorder(session)) {
32
- /** If this is preloaded, set up a buffer, if not, later when sampling we will set up a .on for live events */
33
- this.ee.on('err', e => {
34
- this.errorNoticed = true;
35
- if (this.featAggregate) this.featAggregate.handleError();
36
- });
37
34
  this.#startRecording(session?.sessionReplayMode);
38
35
  } else {
39
36
  this.importAggregator();
40
37
  }
38
+
39
+ /** If the recorder is running, we can pass error events on to the agg to help it switch to full mode later */
40
+ this.ee.on('err', e => {
41
+ if (this.replayRunning) {
42
+ this.errorNoticed = true;
43
+ (0, _handle.handle)(_constants2.SR_EVENT_EMITTER_TYPES.ERROR_DURING_REPLAY, [e], undefined, this.featureName, this.ee);
44
+ }
45
+ });
46
+
47
+ /** Emitted by the recorder when it starts capturing data, used to determine if we should pass errors on to the agg */
48
+ this.ee.on(_constants2.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, isRunning => {
49
+ this.replayRunning = isRunning;
50
+ });
41
51
  }
42
52
 
43
53
  // At this point wherein session state exists already but we haven't init SessionEntity aka verify timers.
@@ -103,11 +103,10 @@ class Recorder {
103
103
  collectFonts: collect_fonts,
104
104
  checkoutEveryNms: _constants.CHECKOUT_MS[this.parent.mode]
105
105
  });
106
- this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode]);
107
106
  this.stopRecording = () => {
108
107
  this.recording = false;
109
108
  this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [false, this.parent.mode]);
110
- stop();
109
+ stop?.();
111
110
  };
112
111
  }
113
112
 
@@ -150,6 +149,10 @@ class Recorder {
150
149
  if (!event) return;
151
150
  if (!this.parent.scheduler && this.#preloaded.length) this.currentBufferTarget = this.#preloaded[this.#preloaded.length - 1];else this.currentBufferTarget = this.#events;
152
151
  if (this.parent.blocked) return;
152
+ if (!this.notified) {
153
+ this.parent.ee.emit(_constants.SR_EVENT_EMITTER_TYPES.REPLAY_RUNNING, [true, this.parent.mode]);
154
+ this.notified = true;
155
+ }
153
156
  if (this.parent.timeKeeper?.ready && !event.__newrelic) {
154
157
  event.__newrelic = (0, _utils.buildNRMetaNode)(event.timestamp, this.parent.timeKeeper);
155
158
  event.timestamp = this.parent.timeKeeper.correctAbsoluteTimestamp(event.timestamp);
@@ -180,7 +183,7 @@ class Recorder {
180
183
 
181
184
  // We are making an effort to try to keep payloads manageable for unloading. If they reach the unload limit before their interval,
182
185
  // it will send immediately. This often happens on the first snapshot, which can be significantly larger than the other payloads.
183
- if (payloadSize > _constants.IDEAL_PAYLOAD_SIZE && this.parent.mode !== _constants2.MODE.ERROR) {
186
+ if ((event.type === _constants.RRWEB_EVENT_TYPES.FullSnapshot && this.currentBufferTarget.hasMeta || payloadSize > _constants.IDEAL_PAYLOAD_SIZE) && this.parent.mode === _constants2.MODE.FULL) {
184
187
  // if we've made it to the ideal size of ~64kb before the interval timer, we should send early.
185
188
  if (this.parent.scheduler) {
186
189
  this.parent.scheduler.runHarvest();