rollbar 2.26.4 → 3.0.0-alpha.2

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/.claude/settings.local.json +3 -0
  2. package/.cursor/rules/guidelines.mdc +154 -0
  3. package/.github/workflows/ci.yml +4 -6
  4. package/CLAUDE.local.md +297 -0
  5. package/CLAUDE.md +201 -0
  6. package/CLAUDE.testrunner.md +470 -0
  7. package/Gruntfile.js +59 -16
  8. package/Makefile +3 -3
  9. package/SECURITY.md +5 -0
  10. package/babel.config.json +9 -0
  11. package/codex.md +148 -0
  12. package/dist/plugins/jquery.min.js +1 -1
  13. package/dist/rollbar.js +19332 -6596
  14. package/dist/rollbar.js.map +1 -1
  15. package/dist/rollbar.min.js +2 -1
  16. package/dist/rollbar.min.js.LICENSE.txt +1 -0
  17. package/dist/rollbar.min.js.map +1 -1
  18. package/dist/rollbar.named-amd.js +19332 -6596
  19. package/dist/rollbar.named-amd.js.map +1 -1
  20. package/dist/rollbar.named-amd.min.js +2 -1
  21. package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
  22. package/dist/rollbar.named-amd.min.js.map +1 -1
  23. package/dist/rollbar.noconflict.umd.js +19319 -6581
  24. package/dist/rollbar.noconflict.umd.js.map +1 -1
  25. package/dist/rollbar.noconflict.umd.min.js +2 -1
  26. package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
  27. package/dist/rollbar.noconflict.umd.min.js.map +1 -1
  28. package/dist/rollbar.snippet.js +1 -1
  29. package/dist/rollbar.umd.js +19333 -6597
  30. package/dist/rollbar.umd.js.map +1 -1
  31. package/dist/rollbar.umd.min.js +2 -1
  32. package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
  33. package/dist/rollbar.umd.min.js.map +1 -1
  34. package/eslint.config.mjs +33 -0
  35. package/karma.conf.js +5 -14
  36. package/package.json +19 -20
  37. package/src/api.js +57 -4
  38. package/src/apiUtility.js +2 -3
  39. package/src/browser/core.js +37 -9
  40. package/src/browser/replay/defaults.js +70 -0
  41. package/src/browser/replay/recorder.js +194 -0
  42. package/src/browser/replay/replayMap.js +195 -0
  43. package/src/browser/rollbar.js +11 -7
  44. package/src/browser/telemetry.js +3 -3
  45. package/src/browser/transport/fetch.js +17 -4
  46. package/src/browser/transport/xhr.js +17 -1
  47. package/src/browser/transport.js +11 -8
  48. package/src/defaults.js +1 -1
  49. package/src/queue.js +65 -4
  50. package/src/react-native/rollbar.js +1 -1
  51. package/src/rollbar.js +52 -10
  52. package/src/server/rollbar.js +3 -2
  53. package/src/telemetry.js +76 -11
  54. package/src/tracing/context.js +24 -0
  55. package/src/tracing/contextManager.js +37 -0
  56. package/src/tracing/defaults.js +7 -0
  57. package/src/tracing/exporter.js +188 -0
  58. package/src/tracing/hrtime.js +98 -0
  59. package/src/tracing/id.js +24 -0
  60. package/src/tracing/session.js +55 -0
  61. package/src/tracing/span.js +92 -0
  62. package/src/tracing/spanProcessor.js +15 -0
  63. package/src/tracing/tracer.js +46 -0
  64. package/src/tracing/tracing.js +89 -0
  65. package/src/utility.js +34 -0
  66. package/test/api.test.js +57 -12
  67. package/test/apiUtility.test.js +5 -6
  68. package/test/browser.core.test.js +1 -10
  69. package/test/browser.domUtility.test.js +1 -1
  70. package/test/browser.predicates.test.js +1 -1
  71. package/test/browser.replay.recorder.test.js +430 -0
  72. package/test/browser.rollbar.test.js +58 -12
  73. package/test/browser.telemetry.test.js +1 -1
  74. package/test/browser.transforms.test.js +20 -13
  75. package/test/browser.transport.test.js +5 -4
  76. package/test/browser.url.test.js +1 -1
  77. package/test/fixtures/replay/index.js +20 -0
  78. package/test/fixtures/replay/payloads.fixtures.js +229 -0
  79. package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
  80. package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
  81. package/test/notifier.test.js +1 -1
  82. package/test/predicates.test.js +1 -1
  83. package/test/queue.test.js +1 -1
  84. package/test/rateLimiter.test.js +1 -1
  85. package/test/react-native.rollbar.test.js +1 -1
  86. package/test/react-native.transforms.test.js +2 -2
  87. package/test/react-native.transport.test.js +3 -3
  88. package/test/replay/index.js +2 -0
  89. package/test/replay/integration/api.spans.test.js +136 -0
  90. package/test/replay/integration/e2e.test.js +228 -0
  91. package/test/replay/integration/index.js +9 -0
  92. package/test/replay/integration/queue.replayMap.test.js +332 -0
  93. package/test/replay/integration/replayMap.test.js +163 -0
  94. package/test/replay/integration/sessionRecording.test.js +390 -0
  95. package/test/replay/unit/api.postSpans.test.js +150 -0
  96. package/test/replay/unit/index.js +7 -0
  97. package/test/replay/unit/queue.replayMap.test.js +225 -0
  98. package/test/replay/unit/replayMap.test.js +348 -0
  99. package/test/replay/util/index.js +5 -0
  100. package/test/replay/util/mockRecordFn.js +80 -0
  101. package/test/server.lambda.mocha.test.mjs +172 -0
  102. package/test/server.locals.constructor.mocha.test.mjs +80 -0
  103. package/test/server.locals.error-handling.mocha.test.mjs +387 -0
  104. package/test/server.locals.merge.mocha.test.mjs +267 -0
  105. package/test/server.locals.test-utils.mjs +114 -0
  106. package/test/server.parser.mocha.test.mjs +87 -0
  107. package/test/server.predicates.mocha.test.mjs +63 -0
  108. package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
  109. package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
  110. package/test/server.rollbar.logging.mocha.test.mjs +326 -0
  111. package/test/server.rollbar.misc.mocha.test.mjs +44 -0
  112. package/test/server.rollbar.test-utils.mjs +57 -0
  113. package/test/server.telemetry.mocha.test.mjs +377 -0
  114. package/test/server.transforms.data.mocha.test.mjs +163 -0
  115. package/test/server.transforms.error.mocha.test.mjs +199 -0
  116. package/test/server.transforms.request.mocha.test.mjs +208 -0
  117. package/test/server.transforms.scrub.mocha.test.mjs +140 -0
  118. package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
  119. package/test/server.transforms.test-utils.mjs +62 -0
  120. package/test/server.transport.mocha.test.mjs +269 -0
  121. package/test/telemetry.test.js +132 -1
  122. package/test/tracing/contextManager.test.js +28 -0
  123. package/test/tracing/exporter.toPayload.test.js +400 -0
  124. package/test/tracing/id.test.js +24 -0
  125. package/test/tracing/span.test.js +183 -0
  126. package/test/tracing/spanProcessor.test.js +73 -0
  127. package/test/tracing/tracing.test.js +105 -0
  128. package/test/transforms.test.js +2 -2
  129. package/test/truncation.test.js +2 -2
  130. package/test/utility.test.js +44 -6
  131. package/webpack.config.js +6 -44
  132. package/.eslintignore +0 -7
  133. package/test/server.lambda.test.js +0 -194
  134. package/test/server.locals.test.js +0 -1068
  135. package/test/server.parser.test.js +0 -78
  136. package/test/server.predicates.test.js +0 -91
  137. package/test/server.rollbar.test.js +0 -728
  138. package/test/server.telemetry.test.js +0 -443
  139. package/test/server.transforms.test.js +0 -1193
  140. package/test/server.transport.test.js +0 -269
@@ -0,0 +1,195 @@
1
+ import id from '../../tracing/id.js';
2
+ import logger from '../logger.js';
3
+
4
+ /**
5
+ * ReplayMap - Manages the mapping between error occurrences and their associated
6
+ * session recordings. This class handles the coordination between when recordings
7
+ * are dumped and when they are eventually sent to the backend.
8
+ */
9
+ export default class ReplayMap {
10
+ #map;
11
+ #recorder;
12
+ #api;
13
+ #tracing;
14
+
15
+ /**
16
+ * Creates a new ReplayMap instance
17
+ *
18
+ * @param {Object} props - Configuration props
19
+ * @param {Object} props.recorder - The recorder instance that dumps replay data into spans
20
+ * @param {Object} props.api - The API instance used to send replay payloads to the backend
21
+ * @param {Object} props.tracing - The tracing instance used to create spans and manage context
22
+ */
23
+ constructor({ recorder, api, tracing }) {
24
+ if (!recorder) {
25
+ throw new TypeError("Expected 'recorder' to be provided");
26
+ }
27
+
28
+ if (!api) {
29
+ throw new TypeError("Expected 'api' to be provided");
30
+ }
31
+
32
+ if (!tracing) {
33
+ throw new TypeError("Expected 'tracing' to be provided");
34
+ }
35
+
36
+ this.#map = new Map();
37
+ this.#recorder = recorder;
38
+ this.#api = api;
39
+ this.#tracing = tracing;
40
+ }
41
+
42
+ /**
43
+ * Processes a replay by converting recorder events into a transport-ready payload.
44
+ *
45
+ * Calls recorder.dump() to capture events as spans, formats them into a proper payload,
46
+ * and stores the result in the map using replayId as the key.
47
+ *
48
+ * @param {string} replayId - The unique ID for this replay
49
+ * @returns {Promise<string>} A promise resolving to the processed replayId
50
+ * @private
51
+ */
52
+ async _processReplay(replayId, occurrenceUuid) {
53
+ try {
54
+ const payload = this.#recorder.dump(
55
+ this.#tracing,
56
+ replayId,
57
+ occurrenceUuid,
58
+ );
59
+
60
+ this.#map.set(replayId, payload);
61
+ } catch (transformError) {
62
+ logger.error('Error transforming spans:', transformError);
63
+
64
+ this.#map.set(replayId, null); // TODO(matux): Error span?
65
+ }
66
+
67
+ return replayId;
68
+ }
69
+
70
+ /**
71
+ * Adds a replay to the map and returns a uniquely generated replay ID.
72
+ *
73
+ * This method immediately returns the replayId and asynchronously processes
74
+ * the replay data in the background. The processing involves converting
75
+ * recorder events into a payload format and storing it in the map.
76
+ *
77
+ * @returns {string} A unique identifier for this replay
78
+ */
79
+ add(replayId, occurrenceUuid) {
80
+ replayId = replayId || id.gen(8);
81
+
82
+ this._processReplay(replayId, occurrenceUuid).catch((error) => {
83
+ logger.error('Failed to process replay:', error);
84
+ });
85
+
86
+ return replayId;
87
+ }
88
+
89
+ /**
90
+ * Sends the replay payload associated with the given replayId to the backend
91
+ * and removes it from the map.
92
+ *
93
+ * Retrieves the payload from the map, checks if it's valid, then sends it
94
+ * to the API endpoint for processing. The payload can be either a spans array
95
+ * or a formatted OTLP payload object.
96
+ *
97
+ * @param {string} replayId - The ID of the replay to send
98
+ * @returns {Promise<boolean>} A promise that resolves to true if the payload was found and sent, false otherwise
99
+ */
100
+ async send(replayId) {
101
+ if (!replayId) {
102
+ logger.error('ReplayMap.send: No replayId provided');
103
+ return false;
104
+ }
105
+
106
+ if (!this.#map.has(replayId)) {
107
+ logger.error(`ReplayMap.send: No replay found for replayId: ${replayId}`);
108
+ return false;
109
+ }
110
+
111
+ const payload = this.#map.get(replayId);
112
+ this.#map.delete(replayId);
113
+
114
+ // Check if payload is empty (could be raw spans array or OTLP payload)
115
+ const isEmpty =
116
+ !payload ||
117
+ (Array.isArray(payload) && payload.length === 0) ||
118
+ (payload.resourceSpans && payload.resourceSpans.length === 0);
119
+
120
+ if (isEmpty) {
121
+ logger.error(
122
+ `ReplayMap.send: No payload found for replayId: ${replayId}`,
123
+ );
124
+ return false;
125
+ }
126
+
127
+ try {
128
+ await this.#api.postSpans(payload);
129
+ return true;
130
+ } catch (error) {
131
+ logger.error('Error sending replay:', error);
132
+ return false;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Discards the replay associated with the given replay ID by removing
138
+ * it from the map without sending it.
139
+ *
140
+ * @param {string} replayId - The ID of the replay to discard
141
+ * @returns {boolean} True if a replay was found and discarded, false otherwise
142
+ */
143
+ discard(replayId) {
144
+ if (!replayId) {
145
+ logger.error('ReplayMap.discard: No replayId provided');
146
+ return false;
147
+ }
148
+
149
+ if (!this.#map.has(replayId)) {
150
+ logger.error(
151
+ `ReplayMap.discard: No replay found for replayId: ${replayId}`,
152
+ );
153
+ return false;
154
+ }
155
+
156
+ this.#map.delete(replayId);
157
+ return true;
158
+ }
159
+
160
+ /**
161
+ * Gets spans for the given replay ID
162
+ *
163
+ * @param {string} replayId - The ID to retrieve spans for
164
+ * @returns {Array|null} The spans array or null if not found
165
+ */
166
+ getSpans(replayId) {
167
+ return this.#map.get(replayId) ?? null;
168
+ }
169
+
170
+ /**
171
+ * Sets spans for a given replay ID
172
+ *
173
+ * @param {string} replayId - The ID to set spans for
174
+ * @param {Array} spans - The spans to set
175
+ */
176
+ setSpans(replayId, spans) {
177
+ this.#map.set(replayId, spans);
178
+ }
179
+
180
+ /**
181
+ * Returns the size of the map (number of stored replays)
182
+ *
183
+ * @returns {number} The number of replays currently stored
184
+ */
185
+ get size() {
186
+ return this.#map.size;
187
+ }
188
+
189
+ /**
190
+ * Clears all stored replays without sending them
191
+ */
192
+ clear() {
193
+ this.#map.clear();
194
+ }
195
+ }
@@ -1,10 +1,12 @@
1
- var Rollbar = require('./core');
2
- var telemeter = require('../telemetry');
3
- var instrumenter = require('./telemetry');
4
- var polyfillJSON = require('../utility/polyfillJSON');
5
- var wrapGlobals = require('./wrapGlobals');
6
- var scrub = require('../scrub');
7
- var truncation = require('../truncation');
1
+ const Rollbar = require('./core');
2
+ const telemeter = require('../telemetry');
3
+ const instrumenter = require('./telemetry');
4
+ const polyfillJSON = require('../utility/polyfillJSON');
5
+ const wrapGlobals = require('./wrapGlobals');
6
+ const scrub = require('../scrub');
7
+ const truncation = require('../truncation');
8
+ const Tracing = require('../tracing/tracing');
9
+ const Recorder = require('./replay/recorder');
8
10
 
9
11
  Rollbar.setComponents({
10
12
  telemeter: telemeter,
@@ -13,6 +15,8 @@ Rollbar.setComponents({
13
15
  wrapGlobals: wrapGlobals,
14
16
  scrub: scrub,
15
17
  truncation: truncation,
18
+ tracing: Tracing.default,
19
+ recorder: Recorder.default,
16
20
  });
17
21
 
18
22
  module.exports = Rollbar;
@@ -613,7 +613,7 @@ Instrumenter.prototype.instrumentConsole = function () {
613
613
  c[method] = function () {
614
614
  var args = Array.prototype.slice.call(arguments);
615
615
  var message = _.formatArgsAsString(args);
616
- self.telemeter.captureLog(message, level);
616
+ self.telemeter.captureLog(message, level, null, _.now());
617
617
  if (orig) {
618
618
  Function.prototype.apply.call(orig, origConsole, args);
619
619
  }
@@ -822,7 +822,7 @@ Instrumenter.prototype.handleUrlChange = function (from, to) {
822
822
  ) {
823
823
  from = parsedFrom.path + (parsedFrom.hash || '');
824
824
  }
825
- this.telemeter.captureNavigation(from, to);
825
+ this.telemeter.captureNavigation(from, to, null, _.now());
826
826
  };
827
827
 
828
828
  Instrumenter.prototype.deinstrumentConnectivity = function () {
@@ -922,7 +922,7 @@ Instrumenter.prototype.handleCspEvent = function (cspEvent) {
922
922
 
923
923
  message += 'originalPolicy: ' + cspEvent.originalPolicy;
924
924
 
925
- this.telemeter.captureLog(message, 'error');
925
+ this.telemeter.captureLog(message, 'error', null, _.now());
926
926
  this.handleCspError(message);
927
927
  };
928
928
 
@@ -23,10 +23,23 @@ function makeFetchRequest(accessToken, url, method, data, callback, timeout) {
23
23
  })
24
24
  .then(function (response) {
25
25
  if (timeoutId) clearTimeout(timeoutId);
26
- return response.json();
27
- })
28
- .then(function (data) {
29
- callback(null, data);
26
+ const respHeaders = response.headers;
27
+
28
+ const isItemRoute = url.endsWith('/api/1/item/');
29
+ const headers = isItemRoute
30
+ ? {
31
+ 'Rollbar-Replay-Enabled': respHeaders.get('Rollbar-Replay-Enabled'),
32
+ 'Rollbar-Replay-RateLimit-Remaining': respHeaders.get(
33
+ 'Rollbar-Replay-RateLimit-Remaining',
34
+ ),
35
+ 'Rollbar-Replay-RateLimit-Reset': respHeaders.get(
36
+ 'Rollbar-Replay-RateLimit-Reset',
37
+ ),
38
+ }
39
+ : {};
40
+
41
+ const json = response.json();
42
+ callback(null, json, headers);
30
43
  })
31
44
  .catch(function (error) {
32
45
  logger.error(error.message);
@@ -31,7 +31,23 @@ function makeXhrRequest(
31
31
 
32
32
  var parseResponse = _.jsonParse(request.responseText);
33
33
  if (_isSuccess(request)) {
34
- callback(parseResponse.error, parseResponse.value);
34
+ const isItemRoute = url.endsWith('/api/1/item/');
35
+
36
+ const headers = isItemRoute
37
+ ? {
38
+ 'Rollbar-Replay-Enabled': request.getResponseHeader(
39
+ 'Rollbar-Replay-Enabled',
40
+ ),
41
+ 'Rollbar-Replay-RateLimit-Remaining':
42
+ request.getResponseHeader(
43
+ 'Rollbar-Replay-RateLimit-Remaining',
44
+ ),
45
+ 'Rollbar-Replay-RateLimit-Reset': request.getResponseHeader(
46
+ 'Rollbar-Replay-RateLimit-Reset',
47
+ ),
48
+ }
49
+ : {};
50
+ callback(parseResponse.error, parseResponse.value, headers);
35
51
  return;
36
52
  } else if (_isNormalFailure(request)) {
37
53
  if (request.status === 403) {
@@ -66,7 +66,8 @@ Transport.prototype.post = function (
66
66
  }
67
67
 
68
68
  var stringifyResult;
69
- if (this.truncation) {
69
+ // Check payload.body to ensure only items are truncated.
70
+ if (this.truncation && payload.body) {
70
71
  stringifyResult = this.truncation.truncate(payload);
71
72
  } else {
72
73
  stringifyResult = _.stringify(payload);
@@ -115,19 +116,21 @@ Transport.prototype.postJsonPayload = function (
115
116
  );
116
117
  };
117
118
 
118
- // Wraps _makeRequest and if Angular 2+ Zone.js is detected, changes scope
119
- // so Angular change detection isn't triggered on each API call.
120
- // This is the equivalent of runOutsideAngular().
121
- //
119
+ // Wraps `_makeRequest` if zone.js is being used, ensuring that Rollbar
120
+ // API calls are not intercepted by any child forked zones.
121
+ // This is equivalent to `NgZone.runOutsideAngular` in Angular.
122
122
  Transport.prototype._makeZoneRequest = function () {
123
123
  var gWindow =
124
124
  (typeof window != 'undefined' && window) ||
125
125
  (typeof self != 'undefined' && self);
126
- var currentZone = gWindow && gWindow.Zone && gWindow.Zone.current;
126
+ // Whenever zone.js is loaded and `Zone` is exposed globally, access
127
+ // the root zone to ensure that requests are always made within it.
128
+ // This approach is framework-agnostic, regardless of which
129
+ // framework zone.js is used with.
130
+ var rootZone = gWindow && gWindow.Zone && gWindow.Zone.root;
127
131
  var args = Array.prototype.slice.call(arguments);
128
132
 
129
- if (currentZone && currentZone._name === 'angular') {
130
- var rootZone = currentZone._parent;
133
+ if (rootZone) {
131
134
  var self = this;
132
135
  rootZone.run(function () {
133
136
  self._makeRequest.apply(undefined, args);
package/src/defaults.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
- version: '2.26.4',
2
+ version: '3.0.0-alpha.2',
3
3
  endpoint: 'api.rollbar.com/api/1/item/',
4
4
  logLevel: 'debug',
5
5
  reportLevel: 'debug',
package/src/queue.js CHANGED
@@ -12,12 +12,14 @@ var _ = require('./utility');
12
12
  * api.postItem(payload, function(err, response))
13
13
  * @param logger - An object used to log verbose messages if desired
14
14
  * @param options - see Queue.prototype.configure
15
+ * @param replayMap - Optional ReplayMap for coordinating session replay with error occurrences
15
16
  */
16
- function Queue(rateLimiter, api, logger, options) {
17
+ function Queue(rateLimiter, api, logger, options, replayMap) {
17
18
  this.rateLimiter = rateLimiter;
18
19
  this.api = api;
19
20
  this.logger = logger;
20
21
  this.options = options;
22
+ this.replayMap = replayMap;
21
23
  this.predicates = [];
22
24
  this.pendingItems = [];
23
25
  this.pendingRequests = [];
@@ -99,12 +101,25 @@ Queue.prototype.addItem = function (
99
101
  callback(new Error('Transmit disabled'));
100
102
  return;
101
103
  }
104
+
105
+ if (this.replayMap && item.body) {
106
+ const replayId = _.getItemAttribute(item, 'replay_id');
107
+ if (replayId) {
108
+ item.replayId = this.replayMap.add(replayId, item.uuid);
109
+ }
110
+ }
111
+
102
112
  this.pendingRequests.push(item);
103
113
  try {
104
114
  this._makeApiRequest(
105
115
  item,
106
- function (err, resp) {
116
+ function (err, resp, headers) {
107
117
  this._dequeuePendingRequest(item);
118
+
119
+ if (!err && resp && item.replayId) {
120
+ this._handleReplayResponse(item.replayId, resp, headers);
121
+ }
122
+
108
123
  callback(err, resp);
109
124
  }.bind(this),
110
125
  );
@@ -169,11 +184,11 @@ Queue.prototype._makeApiRequest = function (item, callback) {
169
184
  if (rateLimitResponse.shouldSend) {
170
185
  this.api.postItem(
171
186
  item,
172
- function (err, resp) {
187
+ function (err, resp, headers) {
173
188
  if (err) {
174
189
  this._maybeRetry(err, item, callback);
175
190
  } else {
176
- callback(err, resp);
191
+ callback(err, resp, headers);
177
192
  }
178
193
  }.bind(this),
179
194
  );
@@ -297,4 +312,50 @@ Queue.prototype._maybeCallWait = function () {
297
312
  return false;
298
313
  };
299
314
 
315
+ /**
316
+ * Handles the API response for an item with a replay ID.
317
+ * Based on the success or failure status of the response,
318
+ * it either sends or discards the associated session replay.
319
+ *
320
+ * @param {string} replayId - The ID of the replay to handle
321
+ * @param {Object} response - The API response
322
+ * @returns {Promise<boolean>} A promise that resolves to true if replay was sent successfully,
323
+ * false if replay was discarded or an error occurred
324
+ * @private
325
+ */
326
+ Queue.prototype._handleReplayResponse = async function (replayId, response, headers) {
327
+ if (!this.replayMap) {
328
+ console.warn('Queue._handleReplayResponse: ReplayMap not available');
329
+ return false;
330
+ }
331
+
332
+ if (!replayId) {
333
+ console.warn('Queue._handleReplayResponse: No replayId provided');
334
+ return false;
335
+ }
336
+
337
+ try {
338
+ if (this._shouldSendReplay(response, headers)) {
339
+ return await this.replayMap.send(replayId);
340
+ } else {
341
+ this.replayMap.discard(replayId);
342
+ return false;
343
+ }
344
+ } catch (error) {
345
+ console.error('Error handling replay response:', error);
346
+ return false;
347
+ }
348
+ };
349
+
350
+ Queue.prototype._shouldSendReplay = function (response, headers) {
351
+ if (response?.err !== 0 ||
352
+ !headers ||
353
+ headers['Rollbar-Replay-Enabled'] !== 'true' ||
354
+ headers['Rollbar-Replay-RateLimit-Remaining'] === '0') {
355
+ return false;
356
+ }
357
+
358
+ return true;
359
+ };
360
+
300
361
  module.exports = Queue;
@@ -30,7 +30,7 @@ function Rollbar(options, client) {
30
30
  var api = new API(this.options, transport, urllib, truncation);
31
31
  var telemeter = new Telemeter(this.options);
32
32
  this.client =
33
- client || new Client(this.options, api, logger, telemeter, 'react-native');
33
+ client || new Client(this.options, api, logger, telemeter, null, null, 'react-native');
34
34
  addTransformsToNotifier(this.client.notifier);
35
35
  addPredicatesToQueue(this.client.queue);
36
36
  _.setupJSON(polyfillJSON);
package/src/rollbar.js CHANGED
@@ -1,7 +1,7 @@
1
- var RateLimiter = require('./rateLimiter');
2
- var Queue = require('./queue');
3
- var Notifier = require('./notifier');
4
- var _ = require('./utility');
1
+ const RateLimiter = require('./rateLimiter');
2
+ const Queue = require('./queue');
3
+ const Notifier = require('./notifier');
4
+ const _ = require('./utility');
5
5
 
6
6
  /*
7
7
  * Rollbar - the interface to Rollbar
@@ -10,14 +10,17 @@ var _ = require('./utility');
10
10
  * @param api
11
11
  * @param logger
12
12
  */
13
- function Rollbar(options, api, logger, telemeter, platform) {
13
+ function Rollbar(options, api, logger, telemeter, tracing, replayMap, platform) {
14
14
  this.options = _.merge(options);
15
15
  this.logger = logger;
16
16
  Rollbar.rateLimiter.configureGlobal(this.options);
17
17
  Rollbar.rateLimiter.setPlatformOptions(platform, this.options);
18
18
  this.api = api;
19
- this.queue = new Queue(Rollbar.rateLimiter, api, logger, this.options);
19
+ this.queue = new Queue(Rollbar.rateLimiter, api, logger, this.options, replayMap);
20
20
 
21
+ this.tracing = tracing;
22
+
23
+ // Legacy OpenTracing support
21
24
  // This must happen before the Notifier is created
22
25
  var tracer = this.options.tracer || null;
23
26
  if (validateTracer(tracer)) {
@@ -57,6 +60,7 @@ Rollbar.prototype.configure = function (options, payloadData) {
57
60
 
58
61
  this.options = _.merge(oldOptions, options, payload);
59
62
 
63
+ // Legacy OpenTracing support
60
64
  // This must happen before the Notifier is configured
61
65
  var tracer = this.options.tracer || null;
62
66
  if (validateTracer(tracer)) {
@@ -150,11 +154,25 @@ Rollbar.prototype._log = function (defaultLevel, item) {
150
154
  return;
151
155
  }
152
156
  try {
153
- this._addTracingInfo(item);
154
157
  item.level = item.level || defaultLevel;
155
- this.telemeter && this.telemeter._captureRollbarItem(item);
156
- item.telemetryEvents =
157
- (this.telemeter && this.telemeter.copyEvents()) || [];
158
+
159
+ const replayId = this._replayIdIfTriggered(item);
160
+ this._addTracingAttributes(item, replayId);
161
+
162
+ // Legacy OpenTracing support
163
+ this._addTracingInfo(item);
164
+
165
+ const telemeter = this.telemeter;
166
+ if (telemeter) {
167
+ telemeter._captureRollbarItem(item);
168
+ item.telemetryEvents = telemeter.copyEvents() || [];
169
+
170
+ if (telemeter.telemetrySpan) {
171
+ telemeter.telemetrySpan.end({'rollbar.replay.id': replayId});
172
+ telemeter.telemetrySpan = telemeter.tracing.startSpan('rollbar-telemetry', {});
173
+ }
174
+ }
175
+
158
176
  this.notifier.log(item, callback);
159
177
  } catch (e) {
160
178
  if (callback) {
@@ -164,6 +182,30 @@ Rollbar.prototype._log = function (defaultLevel, item) {
164
182
  }
165
183
  };
166
184
 
185
+ Rollbar.prototype._addTracingAttributes = function (item, replayId) {
186
+ const span = this.tracing?.getSpan();
187
+
188
+ const attributes = [
189
+ {key: 'replay_id', value: replayId},
190
+ {key: 'session_id', value: this.tracing?.sessionId},
191
+ {key: 'span_id', value: span?.spanId},
192
+ {key: 'trace_id', value: span?.traceId},
193
+ ];
194
+ _.addItemAttributes(item.data, attributes);
195
+
196
+ span?.addEvent(
197
+ 'rollbar.occurrence',
198
+ [{key: 'rollbar.occurrence.uuid', value: item.uuid}],
199
+ );
200
+ };
201
+
202
+ Rollbar.prototype._replayIdIfTriggered = function (item) {
203
+ const levels = this.options.recorder?.triggerOptions?.item?.levels || [];
204
+ if (levels.includes(item.level)) {
205
+ return this.tracing?.idGen(8);
206
+ }
207
+ }
208
+
167
209
  Rollbar.prototype._defaultLogLevel = function () {
168
210
  return this.options.logLevel || 'debug';
169
211
  };
@@ -41,7 +41,7 @@ function Rollbar(options, client) {
41
41
  var api = new API(this.options, transport, urllib, truncation, jsonBackup);
42
42
  var telemeter = new Telemeter(this.options);
43
43
  this.client =
44
- client || new Client(this.options, api, logger, telemeter, 'server');
44
+ client || new Client(this.options, api, logger, telemeter, null, null, 'server');
45
45
  this.instrumenter = new Instrumenter(
46
46
  this.options,
47
47
  this.client.telemeter,
@@ -250,8 +250,9 @@ Rollbar.error = function () {
250
250
  Rollbar.prototype._uncaughtError = function () {
251
251
  var item = this._createItem(arguments);
252
252
  item._isUncaught = true;
253
+ item.level = this.options.uncaughtErrorLevel;
253
254
  var uuid = item.uuid;
254
- this.client.error(item);
255
+ this.client.log(item);
255
256
  return { uuid: uuid };
256
257
  };
257
258