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.
- package/.cursor/rules/guidelines.mdc +154 -0
- package/.github/workflows/ci.yml +32 -12
- package/.lgtm.yml +7 -7
- package/.prettierignore +18 -0
- package/.vscode/settings.json +39 -0
- package/CHANGELOG.md +121 -35
- package/CLAUDE.md +201 -0
- package/Gruntfile.js +101 -48
- package/Makefile +3 -3
- package/README.md +2 -4
- package/SECURITY.md +5 -0
- package/babel.config.json +9 -0
- package/bower.json +1 -3
- package/codex.md +148 -0
- package/defaults.js +17 -5
- package/dist/plugins/jquery.min.js +1 -1
- package/dist/rollbar.js +18748 -5375
- package/dist/rollbar.js.map +1 -1
- package/dist/rollbar.min.js +2 -1
- package/dist/rollbar.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.min.js.map +1 -1
- package/dist/rollbar.named-amd.js +19368 -6000
- package/dist/rollbar.named-amd.js.map +1 -1
- package/dist/rollbar.named-amd.min.js +3 -1
- package/dist/rollbar.named-amd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.named-amd.min.js.map +1 -1
- package/dist/rollbar.noconflict.umd.js +18749 -5380
- package/dist/rollbar.noconflict.umd.js.map +1 -1
- package/dist/rollbar.noconflict.umd.min.js +3 -1
- package/dist/rollbar.noconflict.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.noconflict.umd.min.js.map +1 -1
- package/dist/rollbar.snippet.js +1 -1
- package/dist/rollbar.umd.js +19367 -6000
- package/dist/rollbar.umd.js.map +1 -1
- package/dist/rollbar.umd.min.js +3 -1
- package/dist/rollbar.umd.min.js.LICENSE.txt +1 -0
- package/dist/rollbar.umd.min.js.map +1 -1
- package/docs/extension-exceptions.md +35 -30
- package/docs/migration_v0_to_v1.md +41 -38
- package/eslint.config.mjs +33 -0
- package/get_versions.js +33 -0
- package/index.d.ts +270 -231
- package/karma.conf.js +18 -27
- package/package.json +21 -21
- package/prettier.config.js +7 -0
- package/src/api.js +78 -14
- package/src/apiUtility.js +14 -11
- package/src/browser/core.js +138 -72
- package/src/browser/defaults/scrubFields.js +3 -3
- package/src/browser/detection.js +7 -8
- package/src/browser/domUtility.js +18 -8
- package/src/browser/globalSetup.js +12 -6
- package/src/browser/logger.js +1 -1
- package/src/browser/plugins/jquery.js +35 -35
- package/src/browser/predicates.js +1 -1
- package/src/browser/replay/defaults.js +71 -0
- package/src/browser/replay/recorder.js +193 -0
- package/src/browser/replay/replayMap.js +195 -0
- package/src/browser/rollbar.js +12 -8
- package/src/browser/rollbarWrapper.js +8 -5
- package/src/browser/shim.js +43 -19
- package/src/browser/snippet_callback.js +6 -4
- package/src/browser/telemetry.js +573 -361
- package/src/browser/transforms.js +46 -27
- package/src/browser/transport/fetch.js +26 -14
- package/src/browser/transport/xhr.js +41 -14
- package/src/browser/transport.js +93 -33
- package/src/browser/url.js +16 -8
- package/src/browser/wrapGlobals.js +27 -8
- package/src/defaults.js +3 -3
- package/src/errorParser.js +14 -11
- package/src/merge.js +32 -23
- package/src/notifier.js +16 -13
- package/src/predicates.js +43 -23
- package/src/queue.js +133 -40
- package/src/rateLimiter.js +59 -18
- package/src/react-native/logger.js +1 -1
- package/src/react-native/rollbar.js +59 -55
- package/src/react-native/transforms.js +13 -9
- package/src/react-native/transport.js +44 -34
- package/src/rollbar.js +72 -21
- package/src/scrub.js +0 -1
- package/src/server/locals.js +69 -39
- package/src/server/logger.js +4 -4
- package/src/server/parser.js +72 -47
- package/src/server/rollbar.js +135 -56
- package/src/server/sourceMap/stackTrace.js +33 -18
- package/src/server/telemetry/urlHelpers.js +9 -11
- package/src/server/telemetry.js +68 -45
- package/src/server/transforms.js +37 -21
- package/src/server/transport.js +62 -32
- package/src/telemetry.js +162 -33
- package/src/tracing/context.js +24 -0
- package/src/tracing/contextManager.js +37 -0
- package/src/tracing/defaults.js +7 -0
- package/src/tracing/exporter.js +188 -0
- package/src/tracing/hrtime.js +98 -0
- package/src/tracing/id.js +24 -0
- package/src/tracing/session.js +55 -0
- package/src/tracing/span.js +92 -0
- package/src/tracing/spanProcessor.js +15 -0
- package/src/tracing/tracer.js +46 -0
- package/src/tracing/tracing.js +89 -0
- package/src/transforms.js +33 -21
- package/src/truncation.js +8 -5
- package/src/utility/headers.js +43 -43
- package/src/utility/replace.js +9 -0
- package/src/utility/traverse.js +1 -1
- package/src/utility.js +123 -52
- package/test/api.test.js +88 -41
- package/test/apiUtility.test.js +48 -50
- package/test/browser.core.test.js +142 -141
- package/test/browser.domUtility.test.js +53 -36
- package/test/browser.predicates.test.js +14 -14
- package/test/browser.replay.recorder.test.js +416 -0
- package/test/browser.rollbar.test.js +655 -515
- package/test/browser.telemetry.test.js +46 -39
- package/test/browser.transforms.test.js +164 -139
- package/test/browser.transport.test.js +59 -50
- package/test/browser.url.test.js +13 -12
- package/test/fixtures/locals.fixtures.js +245 -126
- package/test/fixtures/replay/index.js +20 -0
- package/test/fixtures/replay/payloads.fixtures.js +229 -0
- package/test/fixtures/replay/rrwebEvents.fixtures.js +251 -0
- package/test/fixtures/replay/rrwebSyntheticEvents.fixtures.js +328 -0
- package/test/notifier.test.js +91 -79
- package/test/predicates.test.js +261 -215
- package/test/queue.test.js +231 -215
- package/test/rateLimiter.test.js +51 -43
- package/test/react-native.rollbar.test.js +150 -116
- package/test/react-native.transforms.test.js +23 -25
- package/test/react-native.transport.test.js +26 -14
- package/test/replay/index.js +2 -0
- package/test/replay/integration/api.spans.test.js +136 -0
- package/test/replay/integration/e2e.test.js +228 -0
- package/test/replay/integration/index.js +9 -0
- package/test/replay/integration/queue.replayMap.test.js +332 -0
- package/test/replay/integration/replayMap.test.js +163 -0
- package/test/replay/integration/sessionRecording.test.js +390 -0
- package/test/replay/unit/api.postSpans.test.js +150 -0
- package/test/replay/unit/index.js +7 -0
- package/test/replay/unit/queue.replayMap.test.js +225 -0
- package/test/replay/unit/replayMap.test.js +348 -0
- package/test/replay/util/index.js +5 -0
- package/test/replay/util/mockRecordFn.js +80 -0
- package/test/server.lambda.mocha.test.mjs +172 -0
- package/test/server.locals.constructor.mocha.test.mjs +80 -0
- package/test/server.locals.error-handling.mocha.test.mjs +387 -0
- package/test/server.locals.merge.mocha.test.mjs +267 -0
- package/test/server.locals.test-utils.mjs +114 -0
- package/test/server.parser.mocha.test.mjs +87 -0
- package/test/server.predicates.mocha.test.mjs +63 -0
- package/test/server.rollbar.constructor.mocha.test.mjs +199 -0
- package/test/server.rollbar.handlers.mocha.test.mjs +253 -0
- package/test/server.rollbar.logging.mocha.test.mjs +326 -0
- package/test/server.rollbar.misc.mocha.test.mjs +44 -0
- package/test/server.rollbar.test-utils.mjs +57 -0
- package/test/server.telemetry.mocha.test.mjs +377 -0
- package/test/server.transforms.data.mocha.test.mjs +163 -0
- package/test/server.transforms.error.mocha.test.mjs +199 -0
- package/test/server.transforms.request.mocha.test.mjs +208 -0
- package/test/server.transforms.scrub.mocha.test.mjs +140 -0
- package/test/server.transforms.sourcemaps.mocha.test.mjs +122 -0
- package/test/server.transforms.test-utils.mjs +62 -0
- package/test/server.transport.mocha.test.mjs +269 -0
- package/test/telemetry.test.js +178 -38
- package/test/tracing/contextManager.test.js +28 -0
- package/test/tracing/exporter.toPayload.test.js +400 -0
- package/test/tracing/id.test.js +24 -0
- package/test/tracing/span.test.js +183 -0
- package/test/tracing/spanProcessor.test.js +73 -0
- package/test/tracing/tracing.test.js +105 -0
- package/test/transforms.test.js +70 -68
- package/test/truncation.test.js +57 -55
- package/test/utility.test.js +310 -228
- package/webpack.config.js +36 -70
- package/.eslintignore +0 -7
- package/.gitmodules +0 -3
- package/test/server.lambda.test.js +0 -177
- package/test/server.locals.test.js +0 -841
- package/test/server.parser.test.js +0 -72
- package/test/server.predicates.test.js +0 -89
- package/test/server.rollbar.test.js +0 -676
- package/test/server.telemetry.test.js +0 -318
- package/test/server.transforms.test.js +0 -1099
- package/test/server.transport.test.js +0 -201
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
/* globals it */
|
|
4
4
|
/* globals sinon */
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
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 =
|
|
39
|
-
t.handleItemWithError(item, options, function(e, i) {
|
|
40
|
-
expect(item.stackInfo.exception).to.eql({
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
7
|
-
|
|
6
|
+
import truncation from '../src/truncation.js';
|
|
7
|
+
import Transport from '../src/react-native/transport.js';
|
|
8
8
|
var t = new Transport(truncation);
|
|
9
|
-
|
|
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(
|
|
37
|
-
|
|
38
|
-
|
|
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,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
|
+
});
|