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,33 @@
1
+ import babelParser from '@babel/eslint-parser';
2
+ import { defineConfig } from 'eslint/config';
3
+
4
+ export default defineConfig([
5
+ {
6
+ files: ['**/*.js', '**/*.mjs'],
7
+ languageOptions: {
8
+ ecmaVersion: 2021,
9
+ sourceType: 'module',
10
+ parser: babelParser,
11
+ parserOptions: {
12
+ ecmaVersion: 2021,
13
+ },
14
+ },
15
+ rules: {
16
+ 'comma-dangle': 'off',
17
+ 'strict': 0,
18
+ 'no-underscore-dangle': 0,
19
+ 'no-useless-escape': 0,
20
+ 'complexity': [2, { 'max': 35 }],
21
+ 'no-use-before-define': [0, { 'functions': false }],
22
+ 'no-prototype-builtins': 0,
23
+
24
+ // Off until issues are fixed
25
+ 'camelcase': 'off', //[2, {'properties': 'never'}],
26
+ 'no-unused-vars': 'off', //[2, { 'argsIgnorePattern': '^_' }],
27
+ 'quotes': 'off', //[2, 'single', 'avoid-escape'],
28
+ }
29
+ },
30
+ {
31
+ ignores: ['dist', 'examples', 'node_modules', 'vendor'],
32
+ }
33
+ ]);
package/karma.conf.js CHANGED
@@ -1,8 +1,8 @@
1
- var path = require('path');
2
- var webpack = require('webpack');
3
- var defaults = require('./defaults');
1
+ const path = require('path');
2
+ const webpack = require('webpack');
3
+ const defaults = require('./defaults');
4
4
 
5
- var defaultsPlugin = new webpack.DefinePlugin(defaults);
5
+ const defaultsPlugin = new webpack.DefinePlugin(defaults);
6
6
 
7
7
  module.exports = function (config) {
8
8
  config.set({
@@ -66,17 +66,8 @@ module.exports = function (config) {
66
66
  module: {
67
67
  rules: [
68
68
  {
69
- enforce: 'pre',
70
69
  test: /\.js$/,
71
- loader: 'eslint-loader',
72
- exclude: [/node_modules/, /vendor/, /lib/, /dist/],
73
- options: {
74
- configFile: path.resolve(__dirname, '.eslintrc'),
75
- },
76
- },
77
- {
78
- test: /\.js$/,
79
- loader: 'strict-loader',
70
+ loader: 'babel-loader',
80
71
  exclude: [/node_modules/, /vendor/, /lib/, /dist/, /test/],
81
72
  },
82
73
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rollbar",
3
- "version": "2.26.4",
3
+ "version": "3.0.0-alpha.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "http://github.com/rollbar/rollbar.js"
@@ -18,6 +18,7 @@
18
18
  "browser": "dist/rollbar.umd.min.js",
19
19
  "types": "./index.d.ts",
20
20
  "dependencies": {
21
+ "@rrweb/record": "^2.0.0-alpha.18",
21
22
  "async": "~3.2.3",
22
23
  "console-polyfill": "0.3.0",
23
24
  "error-stack-parser": "^2.0.4",
@@ -27,20 +28,21 @@
27
28
  "source-map": "^0.5.7"
28
29
  },
29
30
  "devDependencies": {
30
- "@babel/core": "^7.22.11",
31
+ "@babel/core": "^7.26.10",
32
+ "@babel/eslint-parser": "^7.27.0",
33
+ "@babel/preset-env": "^7.26.9",
31
34
  "babel-eslint": "^10.0.3",
32
- "babel-loader": "^8.0.4",
35
+ "babel-loader": "^9.2.1",
33
36
  "bluebird": "^3.3.5",
34
37
  "chai": "^4.2.0",
35
38
  "chalk": "^1.1.1",
36
39
  "coverage-istanbul-loader": "^3.0.5",
37
- "eslint": "^6.8.0",
38
- "eslint-loader": "^3.0.3",
39
- "express": "^4.18.2",
40
+ "eslint": "^9.24.0",
41
+ "express": "^4.21.2",
40
42
  "glob": "^5.0.14",
41
43
  "grunt": "^1.1.0",
42
44
  "grunt-bumpup": "^0.6.3",
43
- "grunt-cli": "^1.3.2",
45
+ "grunt-cli": "^1.5.0",
44
46
  "grunt-contrib-concat": "^2.1.0",
45
47
  "grunt-contrib-connect": "^2.1.0",
46
48
  "grunt-contrib-copy": "^1.0.0",
@@ -50,12 +52,11 @@
50
52
  "grunt-karma": "^4.0.2",
51
53
  "grunt-parallel": "^0.5.1",
52
54
  "grunt-text-replace": "^0.4.0",
53
- "grunt-vows": "^0.4.2",
54
55
  "grunt-webpack": "^5.0.0",
55
56
  "jade": "~0.27.7",
56
57
  "jasmine-core": "^2.3.4",
57
58
  "jquery-mockjax": "^2.5.0",
58
- "karma": "^6.4.2",
59
+ "karma": "^6.4.4",
59
60
  "karma-chai": "^0.1.0",
60
61
  "karma-chrome-launcher": "^2.2.0",
61
62
  "karma-expect": "^1.1.0",
@@ -69,30 +70,28 @@
69
70
  "karma-sinon": "^1.0.4",
70
71
  "karma-sourcemap-loader": "^0.3.5",
71
72
  "karma-webpack": "^5.0.0",
72
- "mocha": "^10.2.0",
73
+ "mocha": "^11.1.0",
73
74
  "natives": "^1.1.6",
74
75
  "nock": "^11.9.1",
75
76
  "node-libs-browser": "^0.5.2",
76
77
  "prettier": "^3.2.5",
77
- "requirejs": "^2.1.20",
78
+ "requirejs": "^2.3.7",
78
79
  "script-loader": "0.6.1",
79
80
  "sinon": "^8.1.1",
80
81
  "stackframe": "^0.2.2",
81
- "strict-loader": "^1.2.0",
82
82
  "time-grunt": "^1.0.0",
83
- "vows": "^0.8.3",
84
- "webpack": "^5.88.2"
85
- },
86
- "optionalDependencies": {
87
- "decache": "^3.0.5"
83
+ "webpack": "^5.98.0"
88
84
  },
89
85
  "scripts": {
90
86
  "build": "./node_modules/.bin/grunt",
91
87
  "test": "./node_modules/.bin/grunt test",
92
88
  "test-browser": "./node_modules/.bin/grunt test-browser",
93
- "test-server": "./node_modules/.bin/grunt test-server",
94
- "test_ci": "./node_modules/.bin/grunt test",
95
- "lint": "./node_modules/.bin/eslint . --ext .js"
89
+ "test-server": "mocha 'test/server.*.mocha.test.mjs' --reporter spec",
90
+ "test-replay": "./node_modules/.bin/grunt test-replay",
91
+ "test-replay-unit": "./node_modules/.bin/grunt test-replay-unit",
92
+ "test-replay-integration": "./node_modules/.bin/grunt test-replay-integration",
93
+ "test-ci": "./node_modules/.bin/grunt test && npm run test-server",
94
+ "lint": "./node_modules/.bin/eslint ."
96
95
  },
97
96
  "cdn": {
98
97
  "host": "cdn.rollbar.com"
package/src/api.js CHANGED
@@ -10,6 +10,15 @@ var defaultOptions = {
10
10
  port: 443,
11
11
  };
12
12
 
13
+ var OTLPDefaultOptions = {
14
+ hostname: 'api.rollbar.com',
15
+ path: '/api/1/session/',
16
+ search: null,
17
+ version: '1',
18
+ protocol: 'https:',
19
+ port: 443,
20
+ };
21
+
13
22
  /**
14
23
  * Api is an object that encapsulates methods of communicating with
15
24
  * the Rollbar API. It is a standard interface with some parts implemented
@@ -29,16 +38,35 @@ var defaultOptions = {
29
38
  * protocol (optional): https
30
39
  * }
31
40
  */
32
- function Api(options, transport, urllib, truncation, jsonBackup) {
41
+ function Api(options, transport, urllib, truncation) {
33
42
  this.options = options;
34
43
  this.transport = transport;
35
44
  this.url = urllib;
36
45
  this.truncation = truncation;
37
- this.jsonBackup = jsonBackup;
38
46
  this.accessToken = options.accessToken;
39
47
  this.transportOptions = _getTransport(options, urllib);
48
+ this.OTLPTransportOptions = _getOTLPTransport(options, urllib);
40
49
  }
41
50
 
51
+ /**
52
+ * Wraps transport.post in a Promise to support async/await
53
+ *
54
+ * @param {Object} options - Options for the API request
55
+ * @param {string} options.accessToken - The access token for authentication
56
+ * @param {Object} options.transportOptions - Options for the transport
57
+ * @param {Object} options.payload - The data payload to send
58
+ * @returns {Promise} A promise that resolves with the response or rejects with an error
59
+ * @private
60
+ */
61
+ Api.prototype._postPromise = function({ accessToken, transportOptions, payload }) {
62
+ const self = this;
63
+ return new Promise((resolve, reject) => {
64
+ self.transport.post(accessToken, transportOptions, payload, (err, resp) =>
65
+ err ? reject(err) : resolve(resp)
66
+ );
67
+ });
68
+ };
69
+
42
70
  /**
43
71
  *
44
72
  * @param data
@@ -49,7 +77,7 @@ Api.prototype.postItem = function (data, callback) {
49
77
  this.transportOptions,
50
78
  'POST',
51
79
  );
52
- var payload = helpers.buildPayload(this.accessToken, data, this.jsonBackup);
80
+ var payload = helpers.buildPayload(data);
53
81
  var self = this;
54
82
 
55
83
  // ensure the network request is scheduled after the current tick.
@@ -58,13 +86,32 @@ Api.prototype.postItem = function (data, callback) {
58
86
  }, 0);
59
87
  };
60
88
 
89
+ /**
90
+ * Posts spans to the Rollbar API using the session endpoint
91
+ *
92
+ * @param {Array} payload - The spans to send
93
+ * @returns {Promise<Object>} A promise that resolves with the API response
94
+ */
95
+ Api.prototype.postSpans = async function (payload) {
96
+ const transportOptions = helpers.transportOptions(
97
+ this.OTLPTransportOptions,
98
+ 'POST',
99
+ );
100
+
101
+ return await this._postPromise({
102
+ accessToken: this.accessToken,
103
+ transportOptions,
104
+ payload
105
+ });
106
+ };
107
+
61
108
  /**
62
109
  *
63
110
  * @param data
64
111
  * @param callback
65
112
  */
66
113
  Api.prototype.buildJsonPayload = function (data, callback) {
67
- var payload = helpers.buildPayload(this.accessToken, data, this.jsonBackup);
114
+ var payload = helpers.buildPayload(data);
68
115
 
69
116
  var stringifyResult;
70
117
  if (this.truncation) {
@@ -105,6 +152,7 @@ Api.prototype.configure = function (options) {
105
152
  var oldOptions = this.oldOptions;
106
153
  this.options = _.merge(oldOptions, options);
107
154
  this.transportOptions = _getTransport(this.options, this.url);
155
+ this.OTLPTransportOptions = _getOTLPTransport(this.options, this.url);
108
156
  if (this.options.accessToken !== undefined) {
109
157
  this.accessToken = this.options.accessToken;
110
158
  }
@@ -115,4 +163,9 @@ function _getTransport(options, url) {
115
163
  return helpers.getTransportFromOptions(options, defaultOptions, url);
116
164
  }
117
165
 
166
+ function _getOTLPTransport(options, url) {
167
+ options = {...options, endpoint: options.tracing?.endpoint};
168
+ return helpers.getTransportFromOptions(options, OTLPDefaultOptions, url);
169
+ }
170
+
118
171
  module.exports = Api;
package/src/apiUtility.js CHANGED
@@ -1,8 +1,8 @@
1
1
  var _ = require('./utility');
2
2
 
3
- function buildPayload(accessToken, data, jsonBackup) {
3
+ function buildPayload(data) {
4
4
  if (!_.isType(data.context, 'string')) {
5
- var contextResult = _.stringify(data.context, jsonBackup);
5
+ var contextResult = _.stringify(data.context);
6
6
  if (contextResult.error) {
7
7
  data.context = "Error: could not serialize 'context'";
8
8
  } else {
@@ -13,7 +13,6 @@ function buildPayload(accessToken, data, jsonBackup) {
13
13
  }
14
14
  }
15
15
  return {
16
- access_token: accessToken,
17
16
  data: data,
18
17
  };
19
18
  }
@@ -12,24 +12,48 @@ var sharedTransforms = require('../transforms');
12
12
  var predicates = require('./predicates');
13
13
  var sharedPredicates = require('../predicates');
14
14
  var errorParser = require('../errorParser');
15
+ const recorderDefaults = require('./replay/defaults').default;
16
+ const tracingDefaults = require('../tracing/defaults').default;
17
+ const ReplayMap = require('./replay/replayMap').default;
15
18
 
16
19
  function Rollbar(options, client) {
17
20
  this.options = _.handleOptions(defaultOptions, options, null, logger);
18
21
  this.options._configuredOptions = options;
19
- var Telemeter = this.components.telemeter;
20
- var Instrumenter = this.components.instrumenter;
21
- var polyfillJSON = this.components.polyfillJSON;
22
+ const Telemeter = this.components.telemeter;
23
+ const Instrumenter = this.components.instrumenter;
24
+ const polyfillJSON = this.components.polyfillJSON;
22
25
  this.wrapGlobals = this.components.wrapGlobals;
23
26
  this.scrub = this.components.scrub;
24
- var truncation = this.components.truncation;
27
+ const truncation = this.components.truncation;
28
+ const Tracing = this.components.tracing;
29
+ const Recorder = this.components.recorder;
30
+
31
+ const transport = new Transport(truncation);
32
+ const api = new API(this.options, transport, urllib, truncation);
33
+ if (Tracing) {
34
+ this.tracing = new Tracing(_gWindow(), this.options);
35
+ this.tracing.initSession();
36
+ }
37
+
38
+ if (Recorder && _.isBrowser()) {
39
+ const recorderOptions = this.options.recorder;
40
+ this.recorder = new Recorder(recorderOptions);
41
+ this.replayMap = new ReplayMap({
42
+ recorder: this.recorder,
43
+ api: api,
44
+ tracing: this.tracing
45
+ });
46
+
47
+ if (recorderOptions.enabled && recorderOptions.autoStart) {
48
+ this.recorder.start();
49
+ }
50
+ }
25
51
 
26
- var transport = new Transport(truncation);
27
- var api = new API(this.options, transport, urllib, truncation);
28
52
  if (Telemeter) {
29
- this.telemeter = new Telemeter(this.options);
53
+ this.telemeter = new Telemeter(this.options, this.tracing);
30
54
  }
31
55
  this.client =
32
- client || new Client(this.options, api, logger, this.telemeter, 'browser');
56
+ client || new Client(this.options, api, logger, this.telemeter, this.tracing, this.replayMap, 'browser');
33
57
  var gWindow = _gWindow();
34
58
  var gDocument = typeof document != 'undefined' && document;
35
59
  this.isChrome = gWindow.chrome && gWindow.chrome.runtime; // check .runtime to avoid Edge browsers
@@ -94,12 +118,15 @@ Rollbar.prototype.configure = function (options, payloadData) {
94
118
  if (payloadData) {
95
119
  payload = { payload: payloadData };
96
120
  }
121
+
97
122
  this.options = _.handleOptions(oldOptions, options, payload, logger);
98
123
  this.options._configuredOptions = _.handleOptions(
99
124
  oldOptions._configuredOptions,
100
125
  options,
101
126
  payload,
102
127
  );
128
+
129
+ this.recorder?.configure(this.options);
103
130
  this.client.configure(this.options, payloadData);
104
131
  this.instrumenter && this.instrumenter.configure(this.options);
105
132
  this.setupUnhandledCapture();
@@ -344,7 +371,6 @@ Rollbar.prototype.handleAnonymousErrors = function () {
344
371
 
345
372
  var r = this;
346
373
  function prepareStackTrace(error, _stack) {
347
- // eslint-disable-line no-unused-vars
348
374
  if (r.options.inspectAnonymousErrors) {
349
375
  if (r.anonymousErrorsPending) {
350
376
  // This is the only known way to detect that onerror saw an anonymous error.
@@ -600,6 +626,8 @@ var defaultOptions = {
600
626
  inspectAnonymousErrors: true,
601
627
  ignoreDuplicateErrors: true,
602
628
  wrapGlobalEventHandlers: false,
629
+ recorder: recorderDefaults,
630
+ tracing: tracingDefaults,
603
631
  };
604
632
 
605
633
  module.exports = Rollbar;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Default options for the rrweb recorder
3
+ * See https://github.com/rrweb-io/rrweb/blob/master/guide.md#options for details
4
+ */
5
+ export default {
6
+ enabled: false, // Whether recording is enabled
7
+ autoStart: true, // Start recording automatically when Rollbar initializes
8
+ maxSeconds: 300, // Maximum recording duration in seconds
9
+
10
+ triggerOptions: {
11
+ // Trigger replay on specific items (occurrences)
12
+ item: {
13
+ levels: ['error', 'critical'], // Trigger on item level
14
+ },
15
+ },
16
+
17
+ debug: {
18
+ logErrors: true, // Whether to log errors emitted by rrweb.
19
+ logEmits: false, // Whether to log emitted events
20
+ },
21
+
22
+ // Recording options
23
+ inlineStylesheet: true, // Whether to inline stylesheets to improve replay accuracy
24
+ inlineImages: false, // Whether to record the image content
25
+ collectFonts: true, // Whether to collect fonts in the website
26
+
27
+ // Privacy options
28
+ // Fine-grained control over which input types to mask
29
+ // By default only password inputs are masked if maskInputs is true
30
+ maskInputOptions: {
31
+ password: true,
32
+ email: false,
33
+ tel: false,
34
+ text: false,
35
+ color: false,
36
+ date: false,
37
+ 'datetime-local': false,
38
+ month: false,
39
+ number: false,
40
+ range: false,
41
+ search: false,
42
+ time: false,
43
+ url: false,
44
+ week: false,
45
+ },
46
+
47
+ // Remove unnecessary parts of the DOM
48
+ // By default all removable elements are removed
49
+ slimDOMOptions: {
50
+ script: true, // Remove script elements
51
+ comment: true, // Remove comments
52
+ headFavicon: true, // Remove favicons in the head
53
+ headWhitespace: true, // Remove whitespace in head
54
+ headMetaDescKeywords: true, // Remove meta description and keywords
55
+ headMetaSocial: true, // Remove social media meta tags
56
+ headMetaRobots: true, // Remove robots meta directives
57
+ headMetaHttpEquiv: true, // Remove http-equiv meta directives
58
+ headMetaAuthorship: true, // Remove authorship meta directives
59
+ headMetaVerification: true, // Remove verification meta directives
60
+ },
61
+
62
+ // Custom callbacks for advanced use cases
63
+ // These are undefined by default and can be set programmatically
64
+ // maskInputFn: undefined, // Custom function to mask input values
65
+ // maskTextFn: undefined, // Custom function to mask text content
66
+ // errorHandler: undefined, // Custom error handler for recording errors
67
+
68
+ // Plugin system
69
+ // plugins: [] // List of plugins to use (must be set programmatically)
70
+ };
@@ -0,0 +1,194 @@
1
+ import { record as rrwebRecordFn } from '@rrweb/record';
2
+ import { EventType } from '@rrweb/types';
3
+
4
+ import hrtime from '../../tracing/hrtime.js';
5
+ import logger from '../logger.js';
6
+
7
+ export default class Recorder {
8
+ #options;
9
+ #rrwebOptions;
10
+ #stopFn = null;
11
+ #recordFn;
12
+ #events = {
13
+ previous: [],
14
+ current: [],
15
+ };
16
+
17
+ /**
18
+ * Creates a new Recorder instance for capturing DOM events
19
+ *
20
+ * @param {Object} options - Configuration options for the recorder
21
+ * @param {Function} [recordFn=rrwebRecordFn] - The recording function to use
22
+ */
23
+ constructor(options, recordFn = rrwebRecordFn) {
24
+ if (!recordFn) {
25
+ throw new TypeError("Expected 'recordFn' to be provided");
26
+ }
27
+
28
+ this.options = options;
29
+ this.#recordFn = recordFn;
30
+ }
31
+
32
+ get isRecording() {
33
+ return this.#stopFn !== null;
34
+ }
35
+
36
+ get options() {
37
+ return this.#options;
38
+ }
39
+
40
+ set options(newOptions) {
41
+ this.configure(newOptions);
42
+ }
43
+
44
+ configure(newOptions) {
45
+ const {
46
+ // Rollbar options
47
+ enabled,
48
+ autoStart,
49
+ maxSeconds,
50
+ triggerOptions,
51
+ debug,
52
+
53
+ // disallowed rrweb options
54
+ emit,
55
+ checkoutEveryNms,
56
+
57
+ // rrweb options
58
+ ...rrwebOptions
59
+ } = newOptions;
60
+ this.#options = { enabled, autoStart, maxSeconds, triggerOptions, debug };
61
+ this.#rrwebOptions = rrwebOptions;
62
+
63
+ if (this.isRecording && newOptions.enabled === false) {
64
+ this.stop();
65
+ }
66
+ }
67
+
68
+ checkoutEveryNms() {
69
+ // Recording may be up to two checkout intervals, therefore the checkout
70
+ // interval is set to half of the maxSeconds.
71
+ return (this.options.maxSeconds || 10) * 1000 / 2;
72
+ }
73
+
74
+ /**
75
+ * Converts recorded events into a formatted payload ready for transport.
76
+ *
77
+ * This method takes the recorder's stored events, creates a new span with the
78
+ * provided tracing context, attaches all events with their timestamps as span
79
+ * events, and then returns a payload ready for transport to the server.
80
+ *
81
+ * @param {Object} tracing - The tracing system instance to create spans
82
+ * @param {string} replayId - Unique identifier to associate with this replay recording
83
+ * @returns {Object|null} A formatted payload containing spans data in OTLP format, or null if no events exist
84
+ */
85
+ dump(tracing, replayId, occurrenceUuid) {
86
+ const events = this.#events.previous.concat(this.#events.current);
87
+
88
+ if (events.length < 2) {
89
+ logger.error('Replay recording cannot have less than 2 events');
90
+ return null;
91
+ }
92
+
93
+ const recordingSpan = tracing.startSpan('rrweb-replay-recording', {});
94
+
95
+ recordingSpan.setAttribute('rollbar.replay.id', replayId);
96
+
97
+ if (occurrenceUuid) {
98
+ recordingSpan.setAttribute('rollbar.occurrence.uuid', occurrenceUuid);
99
+ }
100
+
101
+ const earliestEvent = events.reduce((earliestEvent, event) =>
102
+ event.timestamp < earliestEvent.timestamp ? event : earliestEvent,
103
+ );
104
+
105
+ recordingSpan.span.startTime = hrtime.fromMillis(earliestEvent.timestamp);
106
+
107
+ for (const event of events) {
108
+ recordingSpan.addEvent(
109
+ 'rrweb-replay-events',
110
+ {
111
+ eventType: event.type,
112
+ json: JSON.stringify(event.data),
113
+ 'rollbar.replay.id': replayId,
114
+ },
115
+ hrtime.fromMillis(event.timestamp),
116
+ );
117
+ }
118
+
119
+ recordingSpan.end();
120
+
121
+ return tracing.exporter.toPayload();
122
+ }
123
+
124
+ start() {
125
+ if (this.isRecording || this.options.enabled === false) {
126
+ return;
127
+ }
128
+
129
+ this.clear();
130
+
131
+ this.#stopFn = this.#recordFn({
132
+ emit: (event, isCheckout) => {
133
+ if (this.options.debug?.logEmits) {
134
+ this._logEvent(event, isCheckout);
135
+ }
136
+
137
+ if (isCheckout && event.type === EventType.Meta) {
138
+ this.#events.previous = this.#events.current;
139
+ this.#events.current = [];
140
+ }
141
+
142
+ this.#events.current.push(event);
143
+ },
144
+ checkoutEveryNms: this.checkoutEveryNms(),
145
+ errorHandler: (error) => {
146
+ if (this.options.debug?.logErrors) {
147
+ logger.error('Error during replay recording', error);
148
+ }
149
+ return true; // swallow the error instead of throwing it to the window
150
+ },
151
+ ...this.#rrwebOptions,
152
+ });
153
+
154
+ return this;
155
+ }
156
+
157
+ stop() {
158
+ if (!this.isRecording) {
159
+ return;
160
+ }
161
+
162
+ this.#stopFn();
163
+ this.#stopFn = null;
164
+
165
+ return this;
166
+ }
167
+
168
+ clear() {
169
+ this.#events = {
170
+ previous: [],
171
+ current: [],
172
+ };
173
+ }
174
+
175
+ _logEvent(event, isCheckout) {
176
+ logger.log(
177
+ `Recorder: ${isCheckout ? 'checkout' : ''} event\n`,
178
+ ((e) => {
179
+ const seen = new WeakSet();
180
+ return JSON.stringify(
181
+ e,
182
+ (_, v) => {
183
+ if (typeof v === 'object' && v !== null) {
184
+ if (seen.has(v)) return '[Circular]';
185
+ seen.add(v);
186
+ }
187
+ return v;
188
+ },
189
+ 2,
190
+ );
191
+ })(event),
192
+ );
193
+ }
194
+ }