rollbar 2.26.3 → 3.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/.cursor/rules/guidelines.mdc +154 -0
  2. package/.github/workflows/ci.yml +32 -12
  3. package/.lgtm.yml +7 -7
  4. package/.prettierignore +18 -0
  5. package/.vscode/settings.json +39 -0
  6. package/CHANGELOG.md +121 -35
  7. package/CLAUDE.md +201 -0
  8. package/Gruntfile.js +101 -48
  9. package/Makefile +3 -3
  10. package/README.md +2 -4
  11. package/SECURITY.md +5 -0
  12. package/babel.config.json +9 -0
  13. package/bower.json +1 -3
  14. package/codex.md +148 -0
  15. package/defaults.js +17 -5
  16. package/dist/plugins/jquery.min.js +1 -1
  17. package/dist/rollbar.js +18748 -5375
  18. package/dist/rollbar.js.map +1 -1
  19. package/dist/rollbar.min.js +2 -1
  20. package/dist/rollbar.min.js.LICENSE.txt +1 -0
  21. package/dist/rollbar.min.js.map +1 -1
  22. package/dist/rollbar.named-amd.js +19368 -6000
  23. package/dist/rollbar.named-amd.js.map +1 -1
  24. package/dist/rollbar.named-amd.min.js +3 -1
  25. package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
  26. package/dist/rollbar.named-amd.min.js.map +1 -1
  27. package/dist/rollbar.noconflict.umd.js +18749 -5380
  28. package/dist/rollbar.noconflict.umd.js.map +1 -1
  29. package/dist/rollbar.noconflict.umd.min.js +3 -1
  30. package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
  31. package/dist/rollbar.noconflict.umd.min.js.map +1 -1
  32. package/dist/rollbar.snippet.js +1 -1
  33. package/dist/rollbar.umd.js +19367 -6000
  34. package/dist/rollbar.umd.js.map +1 -1
  35. package/dist/rollbar.umd.min.js +3 -1
  36. package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
  37. package/dist/rollbar.umd.min.js.map +1 -1
  38. package/docs/extension-exceptions.md +35 -30
  39. package/docs/migration_v0_to_v1.md +41 -38
  40. package/eslint.config.mjs +33 -0
  41. package/get_versions.js +33 -0
  42. package/index.d.ts +270 -231
  43. package/karma.conf.js +18 -27
  44. package/package.json +21 -21
  45. package/prettier.config.js +7 -0
  46. package/src/api.js +78 -14
  47. package/src/apiUtility.js +14 -11
  48. package/src/browser/core.js +138 -72
  49. package/src/browser/defaults/scrubFields.js +3 -3
  50. package/src/browser/detection.js +7 -8
  51. package/src/browser/domUtility.js +18 -8
  52. package/src/browser/globalSetup.js +12 -6
  53. package/src/browser/logger.js +1 -1
  54. package/src/browser/plugins/jquery.js +35 -35
  55. package/src/browser/predicates.js +1 -1
  56. package/src/browser/replay/defaults.js +71 -0
  57. package/src/browser/replay/recorder.js +193 -0
  58. package/src/browser/replay/replayMap.js +195 -0
  59. package/src/browser/rollbar.js +12 -8
  60. package/src/browser/rollbarWrapper.js +8 -5
  61. package/src/browser/shim.js +43 -19
  62. package/src/browser/snippet_callback.js +6 -4
  63. package/src/browser/telemetry.js +573 -361
  64. package/src/browser/transforms.js +46 -27
  65. package/src/browser/transport/fetch.js +26 -14
  66. package/src/browser/transport/xhr.js +41 -14
  67. package/src/browser/transport.js +93 -33
  68. package/src/browser/url.js +16 -8
  69. package/src/browser/wrapGlobals.js +27 -8
  70. package/src/defaults.js +3 -3
  71. package/src/errorParser.js +14 -11
  72. package/src/merge.js +32 -23
  73. package/src/notifier.js +16 -13
  74. package/src/predicates.js +43 -23
  75. package/src/queue.js +133 -40
  76. package/src/rateLimiter.js +59 -18
  77. package/src/react-native/logger.js +1 -1
  78. package/src/react-native/rollbar.js +59 -55
  79. package/src/react-native/transforms.js +13 -9
  80. package/src/react-native/transport.js +44 -34
  81. package/src/rollbar.js +72 -21
  82. package/src/scrub.js +0 -1
  83. package/src/server/locals.js +69 -39
  84. package/src/server/logger.js +4 -4
  85. package/src/server/parser.js +72 -47
  86. package/src/server/rollbar.js +135 -56
  87. package/src/server/sourceMap/stackTrace.js +33 -18
  88. package/src/server/telemetry/urlHelpers.js +9 -11
  89. package/src/server/telemetry.js +68 -45
  90. package/src/server/transforms.js +37 -21
  91. package/src/server/transport.js +62 -32
  92. package/src/telemetry.js +162 -33
  93. package/src/tracing/context.js +24 -0
  94. package/src/tracing/contextManager.js +37 -0
  95. package/src/tracing/defaults.js +7 -0
  96. package/src/tracing/exporter.js +188 -0
  97. package/src/tracing/hrtime.js +98 -0
  98. package/src/tracing/id.js +24 -0
  99. package/src/tracing/session.js +55 -0
  100. package/src/tracing/span.js +92 -0
  101. package/src/tracing/spanProcessor.js +15 -0
  102. package/src/tracing/tracer.js +46 -0
  103. package/src/tracing/tracing.js +89 -0
  104. package/src/transforms.js +33 -21
  105. package/src/truncation.js +8 -5
  106. package/src/utility/headers.js +43 -43
  107. package/src/utility/replace.js +9 -0
  108. package/src/utility/traverse.js +1 -1
  109. package/src/utility.js +123 -52
  110. package/test/api.test.js +88 -41
  111. package/test/apiUtility.test.js +48 -50
  112. package/test/browser.core.test.js +142 -141
  113. package/test/browser.domUtility.test.js +53 -36
  114. package/test/browser.predicates.test.js +14 -14
  115. package/test/browser.replay.recorder.test.js +416 -0
  116. package/test/browser.rollbar.test.js +655 -515
  117. package/test/browser.telemetry.test.js +46 -39
  118. package/test/browser.transforms.test.js +164 -139
  119. package/test/browser.transport.test.js +59 -50
  120. package/test/browser.url.test.js +13 -12
  121. package/test/fixtures/locals.fixtures.js +245 -126
  122. package/test/fixtures/replay/index.js +20 -0
  123. package/test/fixtures/replay/payloads.fixtures.js +229 -0
  124. package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
  125. package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
  126. package/test/notifier.test.js +91 -79
  127. package/test/predicates.test.js +261 -215
  128. package/test/queue.test.js +231 -215
  129. package/test/rateLimiter.test.js +51 -43
  130. package/test/react-native.rollbar.test.js +150 -116
  131. package/test/react-native.transforms.test.js +23 -25
  132. package/test/react-native.transport.test.js +26 -14
  133. package/test/replay/index.js +2 -0
  134. package/test/replay/integration/api.spans.test.js +136 -0
  135. package/test/replay/integration/e2e.test.js +228 -0
  136. package/test/replay/integration/index.js +9 -0
  137. package/test/replay/integration/queue.replayMap.test.js +332 -0
  138. package/test/replay/integration/replayMap.test.js +163 -0
  139. package/test/replay/integration/sessionRecording.test.js +390 -0
  140. package/test/replay/unit/api.postSpans.test.js +150 -0
  141. package/test/replay/unit/index.js +7 -0
  142. package/test/replay/unit/queue.replayMap.test.js +225 -0
  143. package/test/replay/unit/replayMap.test.js +348 -0
  144. package/test/replay/util/index.js +5 -0
  145. package/test/replay/util/mockRecordFn.js +80 -0
  146. package/test/server.lambda.mocha.test.mjs +172 -0
  147. package/test/server.locals.constructor.mocha.test.mjs +80 -0
  148. package/test/server.locals.error-handling.mocha.test.mjs +387 -0
  149. package/test/server.locals.merge.mocha.test.mjs +267 -0
  150. package/test/server.locals.test-utils.mjs +114 -0
  151. package/test/server.parser.mocha.test.mjs +87 -0
  152. package/test/server.predicates.mocha.test.mjs +63 -0
  153. package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
  154. package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
  155. package/test/server.rollbar.logging.mocha.test.mjs +326 -0
  156. package/test/server.rollbar.misc.mocha.test.mjs +44 -0
  157. package/test/server.rollbar.test-utils.mjs +57 -0
  158. package/test/server.telemetry.mocha.test.mjs +377 -0
  159. package/test/server.transforms.data.mocha.test.mjs +163 -0
  160. package/test/server.transforms.error.mocha.test.mjs +199 -0
  161. package/test/server.transforms.request.mocha.test.mjs +208 -0
  162. package/test/server.transforms.scrub.mocha.test.mjs +140 -0
  163. package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
  164. package/test/server.transforms.test-utils.mjs +62 -0
  165. package/test/server.transport.mocha.test.mjs +269 -0
  166. package/test/telemetry.test.js +178 -38
  167. package/test/tracing/contextManager.test.js +28 -0
  168. package/test/tracing/exporter.toPayload.test.js +400 -0
  169. package/test/tracing/id.test.js +24 -0
  170. package/test/tracing/span.test.js +183 -0
  171. package/test/tracing/spanProcessor.test.js +73 -0
  172. package/test/tracing/tracing.test.js +105 -0
  173. package/test/transforms.test.js +70 -68
  174. package/test/truncation.test.js +57 -55
  175. package/test/utility.test.js +310 -228
  176. package/webpack.config.js +36 -70
  177. package/.eslintignore +0 -7
  178. package/.gitmodules +0 -3
  179. package/test/server.lambda.test.js +0 -177
  180. package/test/server.locals.test.js +0 -841
  181. package/test/server.parser.test.js +0 -72
  182. package/test/server.predicates.test.js +0 -89
  183. package/test/server.rollbar.test.js +0 -676
  184. package/test/server.telemetry.test.js +0 -318
  185. package/test/server.transforms.test.js +0 -1099
  186. package/test/server.transport.test.js +0 -201
@@ -3,20 +3,20 @@
3
3
  /* globals it */
4
4
  /* globals sinon */
5
5
 
6
- var Rollbar = require('../src/react-native/rollbar');
7
- var t = require('../src/react-native/transforms');
6
+ import Rollbar from '../src/react-native/rollbar.js';
7
+ import t from '../src/react-native/transforms.js';
8
8
 
9
9
  function TestClientGen() {
10
- var TestClient = function() {
10
+ var TestClient = function () {
11
11
  this.notifier = {
12
- addTransform: function() {
12
+ addTransform: function () {
13
13
  return this.notifier;
14
- }.bind(this)
14
+ }.bind(this),
15
15
  };
16
16
  this.queue = {
17
- addPredicate: function() {
17
+ addPredicate: function () {
18
18
  return this.queue;
19
- }.bind(this)
19
+ }.bind(this),
20
20
  };
21
21
  };
22
22
  return TestClient;
@@ -24,47 +24,45 @@ function TestClientGen() {
24
24
 
25
25
  function itemFromArgs(args) {
26
26
  var client = new (TestClientGen())();
27
- var rollbar = new Rollbar({autoInstrument: false}, client);
27
+ var rollbar = new Rollbar({ autoInstrument: false }, client);
28
28
  var item = rollbar._createItem(args);
29
29
  item.level = 'debug';
30
30
  return item;
31
31
  }
32
32
 
33
- describe('handleItemWithError', function() {
34
- it('should create stackInfo', function(done) {
33
+ describe('handleItemWithError', function () {
34
+ it('should create stackInfo', function (done) {
35
35
  var err = new Error('test');
36
36
  var args = ['a message', err];
37
37
  var item = itemFromArgs(args);
38
- var options = (new Rollbar({})).options;
39
- t.handleItemWithError(item, options, function(e, i) {
40
- expect(item.stackInfo.exception).to.eql({class: 'Error', message: 'test'});
38
+ var options = new Rollbar({}).options;
39
+ t.handleItemWithError(item, options, function (e, i) {
40
+ expect(item.stackInfo.exception).to.eql({
41
+ class: 'Error',
42
+ message: 'test',
43
+ });
41
44
  done(e);
42
45
  });
43
46
  });
44
47
  });
45
- describe('_matchFilename', function() {
48
+ describe('_matchFilename', function () {
46
49
  var filenames = {
47
50
  before: [
48
51
  '/var/mobile/Containers/Data/Application/1122ABCD-FF02-4942-A0D7-632E691D342F/.app/main.jsbundle',
49
52
  '/var/mobile/Containers/Data/Application/1122ABCD-FF02-4942-A0D7-632E691D342F/Library/Application Support/CodePush/2071980d74d1fef682fdab1d1cab345f33f498e3b51f68585c1b0b5469334df7/codepush_ios/main.jsbundle',
50
53
  '/data/user/0/com.example/files/CodePush/2071980d74d1fef682fdab1d1cab345f33f498e3b51f68585c1b0b5469334df7/codepush_android/index.android.bundle',
51
- 'index.android.bundle'
52
- ],
53
- after: [
54
- 'main.jsbundle',
55
- 'main.jsbundle',
56
54
  'index.android.bundle',
57
- null
58
- ]
59
- }
55
+ ],
56
+ after: ['main.jsbundle', 'main.jsbundle', 'index.android.bundle', null],
57
+ };
60
58
 
61
- it('should rewrite filenames', function(done) {
62
- var options = (new Rollbar({})).options;
59
+ it('should rewrite filenames', function (done) {
60
+ var options = new Rollbar({}).options;
63
61
  console.log(options);
64
62
 
65
63
  var length = filenames.before.length;
66
64
 
67
- for(var i = 0; i < length; i++){
65
+ for (var i = 0; i < length; i++) {
68
66
  var filename = t._matchFilename(filenames.before[i], options);
69
67
  expect(filename).to.eql(filenames.after[i]);
70
68
  }
@@ -3,22 +3,22 @@
3
3
  /* globals it */
4
4
  /* globals sinon */
5
5
 
6
- var truncation = require('../src/truncation');
7
- var Transport = require('../src/react-native/transport');
6
+ import truncation from '../src/truncation.js';
7
+ import Transport from '../src/react-native/transport.js';
8
8
  var t = new Transport(truncation);
9
- var utility = require('../src/utility');
9
+ import utility from '../src/utility.js';
10
10
  utility.setupJSON();
11
11
 
12
- describe('post', function() {
12
+ describe('post', function () {
13
13
  var accessToken = 'abc123';
14
14
  var options = {
15
15
  hostname: 'api.rollbar.com',
16
16
  protocol: 'https',
17
- path: '/api/1/item/'
17
+ path: '/api/1/item/',
18
18
  };
19
19
  var payload = {
20
20
  access_token: accessToken,
21
- data: {a: 1}
21
+ data: { a: 1 },
22
22
  };
23
23
  var uuid = 'd4c7acef55bf4c9ea95e4fe9428a8287';
24
24
 
@@ -33,16 +33,28 @@ describe('post', function() {
33
33
  });
34
34
 
35
35
  function stubResponse(code, err, message) {
36
- window.fetch.returns(Promise.resolve(new Response(
37
- JSON.stringify({ err: err, message: message, result: { uuid: uuid }}),
38
- { status: code, statusText: message, headers: { 'Content-Type': 'application/json' }}
39
- )));
36
+ window.fetch.returns(
37
+ Promise.resolve(
38
+ new Response(
39
+ JSON.stringify({
40
+ err: err,
41
+ message: message,
42
+ result: { uuid: uuid },
43
+ }),
44
+ {
45
+ status: code,
46
+ statusText: message,
47
+ headers: { 'Content-Type': 'application/json' },
48
+ },
49
+ ),
50
+ ),
51
+ );
40
52
  }
41
53
 
42
- it('should callback with the right value on success', function(done) {
54
+ it('should callback with the right value on success', function (done) {
43
55
  stubResponse(200, 0, 'OK');
44
56
 
45
- var callback = function(err, data) {
57
+ var callback = function (err, data) {
46
58
  expect(err).to.eql(null);
47
59
  expect(data).to.be.ok();
48
60
  expect(data.uuid).to.eql(uuid);
@@ -51,10 +63,10 @@ describe('post', function() {
51
63
  t.post(accessToken, options, payload, callback);
52
64
  });
53
65
 
54
- it('should callback with the server error if 403', function(done) {
66
+ it('should callback with the server error if 403', function (done) {
55
67
  stubResponse(403, '403', 'bad request');
56
68
 
57
- var callback = function(err, resp) {
69
+ var callback = function (err, resp) {
58
70
  expect(resp).to.not.be.ok();
59
71
  expect(err.message).to.eql('Api error: bad request');
60
72
  done();
@@ -0,0 +1,2 @@
1
+ export * from './integration/index.js';
2
+ export * from './unit/index.js';
@@ -0,0 +1,136 @@
1
+ /**
2
+ * API integration tests for span transport
3
+ */
4
+
5
+ /* globals describe */
6
+ /* globals it */
7
+ /* globals beforeEach */
8
+ /* globals afterEach */
9
+
10
+ import { expect } from 'chai';
11
+ import sinon from 'sinon';
12
+
13
+ import Api from '../../../src/api.js';
14
+ import {
15
+ standardPayload,
16
+ createPayloadWithReplayId,
17
+ } from '../../fixtures/replay/payloads.fixtures.js';
18
+
19
+ describe('API Span Transport', function () {
20
+ let api;
21
+ let transport;
22
+ let accessToken;
23
+
24
+ beforeEach(function () {
25
+ accessToken = 'test-token-12345';
26
+
27
+ transport = {
28
+ post: sinon
29
+ .stub()
30
+ .callsFake((accessToken, transportOptions, payload, callback) => {
31
+ setTimeout(() => {
32
+ callback(null, { err: 0, result: { id: '12345' } });
33
+ }, 10);
34
+ }),
35
+ postJsonPayload: sinon.stub(),
36
+ };
37
+
38
+ const urlMock = { parse: sinon.stub().returns({}) };
39
+ const truncationMock = {
40
+ truncate: sinon.stub().returns({ error: null, value: '{}' }),
41
+ };
42
+
43
+ api = new Api({ accessToken }, transport, urlMock, truncationMock);
44
+ });
45
+
46
+ afterEach(function () {
47
+ sinon.restore();
48
+ });
49
+
50
+ it('should use the session endpoint for spans', async function () {
51
+ const spans = standardPayload;
52
+
53
+ await api.postSpans(spans);
54
+
55
+ expect(transport.post.calledOnce).to.be.true;
56
+
57
+ const transportOptions = transport.post.firstCall.args[1];
58
+ expect(transportOptions.path).to.include('/api/1/session/');
59
+ });
60
+
61
+ it('should format spans payload correctly', async function () {
62
+ const testReplayId = 'test-replay-api-12345';
63
+ const spans = createPayloadWithReplayId(testReplayId);
64
+
65
+ await api.postSpans(spans);
66
+
67
+ const payload = transport.post.firstCall.args[2];
68
+
69
+ expect(payload).to.have.property('resourceSpans');
70
+ expect(payload).to.deep.equal(spans);
71
+ });
72
+
73
+ it('should handle transport errors', async function () {
74
+ const testError = new Error('Transport failure');
75
+ transport.post.callsFake(
76
+ (accessToken, transportOptions, payload, callback) => {
77
+ setTimeout(() => {
78
+ callback(testError);
79
+ }, 10);
80
+ },
81
+ );
82
+
83
+ const spans = [{ id: 'error-span', name: 'error-recording' }];
84
+
85
+ try {
86
+ await api.postSpans(spans);
87
+ expect.fail('Expected error not thrown');
88
+ } catch (error) {
89
+ expect(error).to.equal(testError);
90
+ }
91
+ });
92
+
93
+ it('should handle API response errors', async function () {
94
+ transport.post.callsFake(
95
+ (accessToken, transportOptions, payload, callback) => {
96
+ setTimeout(() => {
97
+ callback(null, { err: 1, message: 'API Error' });
98
+ }, 10);
99
+ },
100
+ );
101
+
102
+ const spans = [{ id: 'error-span', name: 'error-recording' }];
103
+
104
+ const response = await api.postSpans(spans);
105
+
106
+ expect(response).to.have.property('err', 1);
107
+ expect(response).to.have.property('message', 'API Error');
108
+ });
109
+
110
+ it('should merge options when reconfigured', function () {
111
+ const originalOptions = api.options;
112
+
113
+ api.configure({
114
+ endpoint: 'https://custom.rollbar.com/api/',
115
+ });
116
+
117
+ expect(api.options).to.not.equal(originalOptions);
118
+ expect(api.options).to.have.property(
119
+ 'endpoint',
120
+ 'https://custom.rollbar.com/api/',
121
+ );
122
+ expect(api.accessToken).to.equal(accessToken);
123
+ });
124
+
125
+ it('should update sessionTransportOptions when reconfigured', function () {
126
+ const originalTransportOptions = api.OTLPTransportOptions;
127
+
128
+ api.configure({
129
+ tracing: {
130
+ endpoint: 'https://custom.rollbar.com/api/',
131
+ },
132
+ });
133
+
134
+ expect(api.OTLPTransportOptions).to.not.equal(originalTransportOptions);
135
+ });
136
+ });
@@ -0,0 +1,228 @@
1
+ /**
2
+ * End-to-end integration test for the complete session replay feature
3
+ */
4
+
5
+ /* globals describe */
6
+ /* globals it */
7
+ /* globals beforeEach */
8
+ /* globals afterEach */
9
+
10
+ import { expect } from 'chai';
11
+ import sinon from 'sinon';
12
+
13
+ import Tracing from '../../../src/tracing/tracing.js';
14
+ import Recorder from '../../../src/browser/replay/recorder.js';
15
+ import ReplayMap from '../../../src/browser/replay/replayMap.js';
16
+ import recorderDefaults from '../../../src/browser/replay/defaults.js';
17
+ import Api from '../../../src/api.js';
18
+ import Queue from '../../../src/queue.js';
19
+ import { mockRecordFn } from '../util';
20
+ import * as payloads from '../../fixtures/replay/payloads.fixtures.js';
21
+
22
+ const options = {
23
+ enabled: true,
24
+ resource: {
25
+ 'service.name': 'test_service',
26
+ },
27
+ notifier: {
28
+ name: 'rollbar.js',
29
+ version: '0.1.0',
30
+ },
31
+ recorder: {
32
+ ...recorderDefaults,
33
+ enabled: true,
34
+ autoStart: true,
35
+ emitEveryNms: 50,
36
+ },
37
+ payload: {
38
+ environment: 'testenv',
39
+ },
40
+ };
41
+
42
+ describe('Session Replay E2E', function () {
43
+ let tracing;
44
+ let recorder;
45
+ let api;
46
+ let transport;
47
+ let replayMap;
48
+ let queue;
49
+ let rateLimiter;
50
+
51
+ beforeEach(function () {
52
+ transport = {
53
+ post: sinon
54
+ .stub()
55
+ .callsFake((accessToken, transportOptions, payload, callback) => {
56
+ setTimeout(() => {
57
+ callback(
58
+ null,
59
+ { err: 0, result: { id: '12345' } },
60
+ { 'Rollbar-Replay-Enabled': 'true' }
61
+ );
62
+ }, 10);
63
+ }),
64
+ postJsonPayload: sinon.stub(),
65
+ };
66
+ const urlMock = { parse: sinon.stub().returns({}) };
67
+ const truncationMock = {
68
+ truncate: sinon.stub().returns({ error: null, value: '{}' }),
69
+ };
70
+ const logger = { error: sinon.spy(), log: sinon.spy() };
71
+
72
+ tracing = new Tracing(window, options);
73
+ tracing.initSession();
74
+ api = new Api(
75
+ { accessToken: 'test-token-12345' },
76
+ transport,
77
+ urlMock,
78
+ truncationMock,
79
+ );
80
+
81
+ recorder = new Recorder(options.recorder, mockRecordFn);
82
+
83
+ rateLimiter = { shouldSend: () => ({ shouldSend: true }) };
84
+
85
+ replayMap = new ReplayMap({
86
+ recorder,
87
+ api,
88
+ tracing,
89
+ });
90
+
91
+ queue = new Queue(rateLimiter, api, logger, { transmit: true }, replayMap);
92
+ });
93
+
94
+ afterEach(function () {
95
+ if (recorder && recorder.isRecording) {
96
+ recorder.stop();
97
+ }
98
+ sinon.restore();
99
+ });
100
+
101
+ it('should handle complete session replay flow from error to API call', function (done) {
102
+ recorder.start();
103
+
104
+ setTimeout(() => {
105
+ const recorderDumpSpy = sinon.spy(recorder, 'dump');
106
+ const replayMapAddSpy = sinon.spy(replayMap, 'add');
107
+ const replayMapSendSpy = sinon.spy(replayMap, 'send');
108
+ const apiPostItemSpy = sinon.spy(api, 'postItem');
109
+ const apiPostSpansSpy = sinon.spy(api, 'postSpans');
110
+
111
+ const errorItem = {
112
+ body: {
113
+ trace: {
114
+ exception: {
115
+ message: 'E2E test error',
116
+ },
117
+ },
118
+ },
119
+ attributes: [
120
+ { key: 'replay_id', value: '1234567812345678' },
121
+ ],
122
+ };
123
+
124
+ queue.addItem(errorItem, function (err, resp) {
125
+ expect(errorItem).to.have.property('replayId');
126
+ const expectedReplayId = errorItem.replayId;
127
+ expect(expectedReplayId).to.match(/^[0-9a-fA-F]{16}$/);
128
+
129
+ expect(replayMapAddSpy.calledOnce).to.be.true;
130
+ expect(apiPostItemSpy.calledOnce).to.be.true;
131
+
132
+ setTimeout(() => {
133
+ expect(recorderDumpSpy.called).to.be.true;
134
+ expect(recorderDumpSpy.calledWith(tracing, errorItem.replayId)).to.be
135
+ .true;
136
+ expect(replayMapSendSpy.calledWith(errorItem.replayId)).to.be.true;
137
+ expect(apiPostSpansSpy.calledOnce).to.be.true;
138
+
139
+ const payload = apiPostSpansSpy.firstCall.args[0];
140
+ expect(payload).to.be.an('object');
141
+ expect(payload).to.have.property('resourceSpans');
142
+ expect(payload.resourceSpans).to.be.an('array');
143
+ expect(payload.resourceSpans).to.have.length.greaterThan(0);
144
+
145
+ const resourceSpan = payload.resourceSpans[0];
146
+ expect(resourceSpan).to.have.property('resource');
147
+
148
+ const resource = payload.resourceSpans[0].resource;
149
+ expect(resource).to.deep.equal(
150
+ payloads.standardPayload.resourceSpans[0].resource,
151
+ );
152
+
153
+ const span = resourceSpan.scopeSpans[0].spans[0];
154
+ expect(span).to.have.property('name', 'rrweb-replay-recording');
155
+ expect(span).to.have.property('events');
156
+ expect(span.events).to.be.an('array');
157
+ expect(span).to.have.property('attributes').that.is.an('array');
158
+ expect(span.attributes).to.have.lengthOf(2);
159
+
160
+ expect(span.attributes).to.deep.include({
161
+ key: 'rollbar.replay.id',
162
+ value: { stringValue: expectedReplayId },
163
+ });
164
+
165
+ const sessionIdAttr = span.attributes.find(
166
+ (attr) => attr.key === 'session.id',
167
+ );
168
+ expect(sessionIdAttr).to.exist;
169
+ expect(sessionIdAttr.value.stringValue).to.match(/^[0-9a-fA-F]{32}$/);
170
+
171
+ const transportArgs = transport.post.lastCall.args;
172
+ expect(transportArgs[1].path).to.include('/api/1/session/');
173
+
174
+ done();
175
+ }, 200);
176
+ });
177
+ }, 50);
178
+ });
179
+
180
+ it('should integrate with real components in failure scenario', function (done) {
181
+ transport.post.callsFake(
182
+ (accessToken, transportOptions, payload, callback) => {
183
+ if (transportOptions.path.includes('/api/1/item/')) {
184
+ setTimeout(() => {
185
+ callback(null, { err: 1, message: 'API Error' });
186
+ }, 10);
187
+ } else {
188
+ setTimeout(() => {
189
+ callback(null, { err: 0, result: { id: '12345' } });
190
+ }, 10);
191
+ }
192
+ },
193
+ );
194
+
195
+ const replayMapAddSpy = sinon.spy(replayMap, 'add');
196
+ const replayMapDiscardSpy = sinon.spy(replayMap, 'discard');
197
+ const apiPostSpansSpy = sinon.spy(api, 'postSpans');
198
+
199
+ recorder.start();
200
+
201
+ const errorItem = {
202
+ body: {
203
+ trace: {
204
+ exception: {
205
+ message: 'Error that will fail to send',
206
+ },
207
+ },
208
+ },
209
+ attributes: [
210
+ { key: 'replay_id', value: '1234567812345678' },
211
+ ],
212
+ };
213
+
214
+ queue.addItem(errorItem, function (err, resp) {
215
+ expect(errorItem).to.have.property('replayId');
216
+ expect(replayMapAddSpy.calledOnce).to.be.true;
217
+ expect(resp).to.have.property('err', 1);
218
+
219
+ setTimeout(() => {
220
+ expect(replayMapDiscardSpy.calledWith(errorItem.replayId)).to.be.true;
221
+ expect(apiPostSpansSpy.called).to.be.false;
222
+ expect(replayMap.getSpans(errorItem.replayId)).to.be.null;
223
+
224
+ done();
225
+ }, 100);
226
+ });
227
+ });
228
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Session Replay Integration Tests
3
+ */
4
+
5
+ export * from './sessionRecording.test.js';
6
+ export * from './api.spans.test.js';
7
+ export * from './replayMap.test.js';
8
+ export * from './queue.replayMap.test.js';
9
+ export * from './e2e.test.js';