@commercetools-frontend/sentry 0.0.0-CRAFT-1791-20251006162610

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) commercetools GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # @commercetools-frontend/sentry
2
+
3
+ <p align="center">
4
+ <a href="https://www.npmjs.com/package/@commercetools-frontend/sentry"><img src="https://badgen.net/npm/v/@commercetools-frontend/sentry" alt="Latest release (latest dist-tag)" /></a> <a href="https://www.npmjs.com/package/@commercetools-frontend/sentry"><img src="https://badgen.net/npm/v/@commercetools-frontend/sentry/next" alt="Latest release (next dist-tag)" /></a> <a href="https://bundlephobia.com/result?p=@commercetools-frontend/sentry"><img src="https://badgen.net/bundlephobia/minzip/@commercetools-frontend/sentry" alt="Minified + GZipped size" /></a> <a href="https://github.com/commercetools/merchant-center-application-kit/blob/main/LICENSE"><img src="https://badgen.net/github/license/commercetools/merchant-center-application-kit" alt="GitHub license" /></a>
5
+ </p>
6
+
7
+ Components to set up Sentry for a MC application.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ $ npm install --save @commercetools-frontend/sentry
13
+ ```
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29tbWVyY2V0b29scy1mcm9udGVuZC1zZW50cnkuY2pzLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuL2RlY2xhcmF0aW9ucy9zcmMvaW5kZXguZC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSJ9
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _JSON$stringify = require('@babel/runtime-corejs3/core-js-stable/json/stringify');
6
+ var _includesInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/includes');
7
+ var _Object$keys = require('@babel/runtime-corejs3/core-js-stable/object/keys');
8
+ var Sentry = require('@sentry/react');
9
+ var browser = require('@sentry/browser');
10
+ var history = require('@commercetools-frontend/browser-history');
11
+ var _Reflect$construct = require('@babel/runtime-corejs3/core-js-stable/reflect/construct');
12
+ var _classCallCheck = require('@babel/runtime-corejs3/helpers/classCallCheck');
13
+ var _createClass = require('@babel/runtime-corejs3/helpers/createClass');
14
+ var _possibleConstructorReturn = require('@babel/runtime-corejs3/helpers/possibleConstructorReturn');
15
+ var _getPrototypeOf = require('@babel/runtime-corejs3/helpers/getPrototypeOf');
16
+ var _inherits = require('@babel/runtime-corejs3/helpers/inherits');
17
+ var react = require('react');
18
+
19
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
20
+
21
+ function _interopNamespace(e) {
22
+ if (e && e.__esModule) return e;
23
+ var n = Object.create(null);
24
+ if (e) {
25
+ Object.keys(e).forEach(function (k) {
26
+ if (k !== 'default') {
27
+ var d = Object.getOwnPropertyDescriptor(e, k);
28
+ Object.defineProperty(n, k, d.get ? d : {
29
+ enumerable: true,
30
+ get: function () { return e[k]; }
31
+ });
32
+ }
33
+ });
34
+ }
35
+ n["default"] = e;
36
+ return Object.freeze(n);
37
+ }
38
+
39
+ var _JSON$stringify__default = /*#__PURE__*/_interopDefault(_JSON$stringify);
40
+ var _includesInstanceProperty__default = /*#__PURE__*/_interopDefault(_includesInstanceProperty);
41
+ var _Object$keys__default = /*#__PURE__*/_interopDefault(_Object$keys);
42
+ var Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
43
+ var history__default = /*#__PURE__*/_interopDefault(history);
44
+ var _Reflect$construct__default = /*#__PURE__*/_interopDefault(_Reflect$construct);
45
+
46
+ // NOTE: This string will be replaced on build time with the package version.
47
+ var version = "24.7.2";
48
+
49
+ const makeErrorToCapture = error => {
50
+ if (error instanceof Error) return error;
51
+ if (error instanceof ErrorEvent) return new Error(error.message);
52
+ return new Error(_JSON$stringify__default["default"](error.reason || 'Unhandled rejection without a reason'));
53
+ };
54
+ const sendErrorToSentry = error => {
55
+ if (typeof error === 'string') return Sentry__namespace.captureMessage(error);
56
+ const errorToCapture = makeErrorToCapture(error);
57
+ return Sentry__namespace.captureException(errorToCapture);
58
+ };
59
+ const replaceEventValues = (source, replacements) => {
60
+ const replaceEventValue = prop => {
61
+ source[prop] = replacements[prop];
62
+ };
63
+ for (const prop in source) {
64
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
65
+ var _context;
66
+ const hasPropReplacement = _includesInstanceProperty__default["default"](_context = _Object$keys__default["default"](replacements)).call(_context, prop);
67
+ switch (typeof source[prop]) {
68
+ case 'string':
69
+ if (hasPropReplacement) {
70
+ replaceEventValue(prop);
71
+ }
72
+ break;
73
+ case 'object':
74
+ if (hasPropReplacement) {
75
+ replaceEventValue(prop);
76
+ } else {
77
+ replaceEventValues(source[prop], replacements);
78
+ }
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ return source;
84
+ };
85
+ const redactUnsafeEventFields = event => {
86
+ return replaceEventValues(event, {
87
+ firstName: '[Redacted]',
88
+ lastName: '[Redacted]',
89
+ email: '[Redacted]'
90
+ });
91
+ };
92
+ const boot = () => {
93
+ if (window.app.trackingSentry && window.app.trackingSentry !== 'null') {
94
+ Sentry__namespace.init({
95
+ dsn: window.app.trackingSentry,
96
+ release: window.app.revision,
97
+ environment: `${window.app.env}-${window.app.location}`,
98
+ // In order to reduce the noise in sentry we only track errors that come
99
+ // from our code and ignore errors that come from other services
100
+ // https://blog.sentry.io/2017/03/27/tips-for-reducing-javascript-error-noise.html
101
+ allowUrls: [window.app.cdnUrl, window.app.frontendHost],
102
+ // suppress any resize observer errors, see
103
+ // https://github.com/vercel/next.js/discussions/51551
104
+ ignoreErrors: [/ResizeObserver loop (limit exceeded|completed with undelivered notifications)/i],
105
+ integrations: [browser.globalHandlersIntegration({
106
+ onunhandledrejection: false,
107
+ onerror: false
108
+ }), Sentry__namespace.reactRouterV5BrowserTracingIntegration({
109
+ history: history__default["default"]
110
+ })],
111
+ // Sending 5% of transactions. We can adjust that as we see a need to.
112
+ // Generally we need to find a balance between performance and data volume.
113
+ // If we need more flexible and dynamic way of gathering important samples,
114
+ // we can implement the `tracesSampler` function.
115
+ // https://docs.sentry.io/platforms/javascript/guides/react/configuration/sampling/#sampling-transaction-events
116
+ tracesSampleRate: 0.05,
117
+ beforeSend(event) {
118
+ return redactUnsafeEventFields(event);
119
+ }
120
+ });
121
+ const sentryScope = Sentry__namespace.getCurrentScope();
122
+ sentryScope.setTag('role', 'frontend');
123
+ sentryScope.setTag('applicationName', window.app.applicationName);
124
+ sentryScope.setTag('service', `merchant-center-frontend-${window.app.applicationName}`);
125
+ }
126
+ };
127
+ const isExtraAsObject = extrasOrExtra => typeof extrasOrExtra === 'object';
128
+ const reportErrorToSentry = (error, extraInfo, getIsEnabled) => {
129
+ const isEnabled = getIsEnabled ? getIsEnabled() : Boolean(window.app.trackingSentry);
130
+ if (typeof error === 'string' && !isEnabled) {
131
+ console.warn('[SENTRY]: You called "reportErrorToSentry" with a string argument. ' + '"Error" objects should be preferred so that a stack-trace can be made available in Sentry. ' + 'See: https://docs.sentry.io/clients/javascript/usage/#try-catch');
132
+ }
133
+ if (isEnabled) {
134
+ if (extraInfo?.extra) {
135
+ if (isExtraAsObject(extraInfo.extra)) {
136
+ // See https://docs.sentry.io/platforms/javascript/react/
137
+ Sentry__namespace.setExtras(extraInfo.extra);
138
+ } else {
139
+ Sentry__namespace.setExtra('extra', extraInfo.extra);
140
+ }
141
+ } else if (error instanceof ErrorEvent) {
142
+ Sentry__namespace.setExtras({
143
+ filename: error.filename,
144
+ lineno: error.lineno,
145
+ colno: error.colno,
146
+ error: error.error
147
+ });
148
+ }
149
+ // Generate a unique ID referring to the last generated Sentry error
150
+ const errorId = sendErrorToSentry(error);
151
+
152
+ // The error stack should be available in Sentry, so there is no
153
+ // need to print it in the console as well.
154
+ // We just notify that an error occurred and provide the error ID.
155
+ console.error(`[SENTRY]: An error occured (ID: ${errorId}).`);
156
+ return errorId;
157
+ }
158
+ console.error('[SENTRY]:', error);
159
+ return undefined;
160
+ };
161
+
162
+ function _callSuper$1(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct$1() ? _Reflect$construct__default["default"](o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
163
+ function _isNativeReflectConstruct$1() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct__default["default"](Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct$1 = function () { return !!t; })(); }
164
+ /**
165
+ * This component will let sentry know if any information about the user has
166
+ * changed.
167
+ */
168
+ let SentryUserTracker = /*#__PURE__*/function (_PureComponent) {
169
+ function SentryUserTracker() {
170
+ var _this;
171
+ _classCallCheck(this, SentryUserTracker);
172
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
173
+ args[_key] = arguments[_key];
174
+ }
175
+ _this = _callSuper$1(this, SentryUserTracker, [...args]);
176
+ _this.syncUser = () => {
177
+ if (_this.props.user && window.app.trackingSentry) {
178
+ // to avoid sending personal data to sentry we anonymize the email address
179
+ // by only sending the domain part or the email
180
+ const emailTld = _this.props.user.email.split('@')[1];
181
+ Sentry__namespace.setUser({
182
+ email: `xxx@${emailTld}`,
183
+ id: _this.props.user.id
184
+ });
185
+ }
186
+ };
187
+ return _this;
188
+ }
189
+ _inherits(SentryUserTracker, _PureComponent);
190
+ return _createClass(SentryUserTracker, [{
191
+ key: "componentDidMount",
192
+ value: function componentDidMount() {
193
+ // since the user and project could have been loaded from the apollo cache
194
+ // they could be preset already when mounting
195
+ this.syncUser();
196
+ }
197
+ }, {
198
+ key: "componentDidUpdate",
199
+ value: function componentDidUpdate() {
200
+ this.syncUser();
201
+ }
202
+ }, {
203
+ key: "render",
204
+ value: function render() {
205
+ return null;
206
+ }
207
+ }]);
208
+ }(react.PureComponent);
209
+ SentryUserTracker.displayName = 'SentryUserTracker';
210
+
211
+ function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? _Reflect$construct__default["default"](o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
212
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct__default["default"](Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); }
213
+ let SentryUserLogoutTracker = /*#__PURE__*/function (_Component) {
214
+ function SentryUserLogoutTracker() {
215
+ _classCallCheck(this, SentryUserLogoutTracker);
216
+ return _callSuper(this, SentryUserLogoutTracker, arguments);
217
+ }
218
+ _inherits(SentryUserLogoutTracker, _Component);
219
+ return _createClass(SentryUserLogoutTracker, [{
220
+ key: "componentDidMount",
221
+ value: function componentDidMount() {
222
+ // When the user is not logged in anymore (e.g. on logout) we still track
223
+ // errors but without the user data in context.
224
+ if (window.app.trackingSentry) {
225
+ Sentry__namespace.configureScope(scope => {
226
+ scope.clear();
227
+ });
228
+ }
229
+ }
230
+ }, {
231
+ key: "render",
232
+ value: function render() {
233
+ return null;
234
+ }
235
+ }]);
236
+ }(react.Component);
237
+ SentryUserLogoutTracker.displayName = 'SentryUserLogoutTracker';
238
+
239
+ exports.SentryUserLogoutTracker = SentryUserLogoutTracker;
240
+ exports.SentryUserTracker = SentryUserTracker;
241
+ exports.boot = boot;
242
+ exports.redactUnsafeEventFields = redactUnsafeEventFields;
243
+ exports.reportErrorToSentry = reportErrorToSentry;
244
+ exports.version = version;
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ if (process.env.NODE_ENV === "production") {
4
+ module.exports = require("./commercetools-frontend-sentry.cjs.prod.js");
5
+ } else {
6
+ module.exports = require("./commercetools-frontend-sentry.cjs.dev.js");
7
+ }
@@ -0,0 +1,244 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var _JSON$stringify = require('@babel/runtime-corejs3/core-js-stable/json/stringify');
6
+ var _includesInstanceProperty = require('@babel/runtime-corejs3/core-js-stable/instance/includes');
7
+ var _Object$keys = require('@babel/runtime-corejs3/core-js-stable/object/keys');
8
+ var Sentry = require('@sentry/react');
9
+ var browser = require('@sentry/browser');
10
+ var history = require('@commercetools-frontend/browser-history');
11
+ var _Reflect$construct = require('@babel/runtime-corejs3/core-js-stable/reflect/construct');
12
+ var _classCallCheck = require('@babel/runtime-corejs3/helpers/classCallCheck');
13
+ var _createClass = require('@babel/runtime-corejs3/helpers/createClass');
14
+ var _possibleConstructorReturn = require('@babel/runtime-corejs3/helpers/possibleConstructorReturn');
15
+ var _getPrototypeOf = require('@babel/runtime-corejs3/helpers/getPrototypeOf');
16
+ var _inherits = require('@babel/runtime-corejs3/helpers/inherits');
17
+ var react = require('react');
18
+
19
+ function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
20
+
21
+ function _interopNamespace(e) {
22
+ if (e && e.__esModule) return e;
23
+ var n = Object.create(null);
24
+ if (e) {
25
+ Object.keys(e).forEach(function (k) {
26
+ if (k !== 'default') {
27
+ var d = Object.getOwnPropertyDescriptor(e, k);
28
+ Object.defineProperty(n, k, d.get ? d : {
29
+ enumerable: true,
30
+ get: function () { return e[k]; }
31
+ });
32
+ }
33
+ });
34
+ }
35
+ n["default"] = e;
36
+ return Object.freeze(n);
37
+ }
38
+
39
+ var _JSON$stringify__default = /*#__PURE__*/_interopDefault(_JSON$stringify);
40
+ var _includesInstanceProperty__default = /*#__PURE__*/_interopDefault(_includesInstanceProperty);
41
+ var _Object$keys__default = /*#__PURE__*/_interopDefault(_Object$keys);
42
+ var Sentry__namespace = /*#__PURE__*/_interopNamespace(Sentry);
43
+ var history__default = /*#__PURE__*/_interopDefault(history);
44
+ var _Reflect$construct__default = /*#__PURE__*/_interopDefault(_Reflect$construct);
45
+
46
+ // NOTE: This string will be replaced on build time with the package version.
47
+ var version = "24.7.2";
48
+
49
+ const makeErrorToCapture = error => {
50
+ if (error instanceof Error) return error;
51
+ if (error instanceof ErrorEvent) return new Error(error.message);
52
+ return new Error(_JSON$stringify__default["default"](error.reason || 'Unhandled rejection without a reason'));
53
+ };
54
+ const sendErrorToSentry = error => {
55
+ if (typeof error === 'string') return Sentry__namespace.captureMessage(error);
56
+ const errorToCapture = makeErrorToCapture(error);
57
+ return Sentry__namespace.captureException(errorToCapture);
58
+ };
59
+ const replaceEventValues = (source, replacements) => {
60
+ const replaceEventValue = prop => {
61
+ source[prop] = replacements[prop];
62
+ };
63
+ for (const prop in source) {
64
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
65
+ var _context;
66
+ const hasPropReplacement = _includesInstanceProperty__default["default"](_context = _Object$keys__default["default"](replacements)).call(_context, prop);
67
+ switch (typeof source[prop]) {
68
+ case 'string':
69
+ if (hasPropReplacement) {
70
+ replaceEventValue(prop);
71
+ }
72
+ break;
73
+ case 'object':
74
+ if (hasPropReplacement) {
75
+ replaceEventValue(prop);
76
+ } else {
77
+ replaceEventValues(source[prop], replacements);
78
+ }
79
+ break;
80
+ }
81
+ }
82
+ }
83
+ return source;
84
+ };
85
+ const redactUnsafeEventFields = event => {
86
+ return replaceEventValues(event, {
87
+ firstName: '[Redacted]',
88
+ lastName: '[Redacted]',
89
+ email: '[Redacted]'
90
+ });
91
+ };
92
+ const boot = () => {
93
+ if (window.app.trackingSentry && window.app.trackingSentry !== 'null') {
94
+ Sentry__namespace.init({
95
+ dsn: window.app.trackingSentry,
96
+ release: window.app.revision,
97
+ environment: `${window.app.env}-${window.app.location}`,
98
+ // In order to reduce the noise in sentry we only track errors that come
99
+ // from our code and ignore errors that come from other services
100
+ // https://blog.sentry.io/2017/03/27/tips-for-reducing-javascript-error-noise.html
101
+ allowUrls: [window.app.cdnUrl, window.app.frontendHost],
102
+ // suppress any resize observer errors, see
103
+ // https://github.com/vercel/next.js/discussions/51551
104
+ ignoreErrors: [/ResizeObserver loop (limit exceeded|completed with undelivered notifications)/i],
105
+ integrations: [browser.globalHandlersIntegration({
106
+ onunhandledrejection: false,
107
+ onerror: false
108
+ }), Sentry__namespace.reactRouterV5BrowserTracingIntegration({
109
+ history: history__default["default"]
110
+ })],
111
+ // Sending 5% of transactions. We can adjust that as we see a need to.
112
+ // Generally we need to find a balance between performance and data volume.
113
+ // If we need more flexible and dynamic way of gathering important samples,
114
+ // we can implement the `tracesSampler` function.
115
+ // https://docs.sentry.io/platforms/javascript/guides/react/configuration/sampling/#sampling-transaction-events
116
+ tracesSampleRate: 0.05,
117
+ beforeSend(event) {
118
+ return redactUnsafeEventFields(event);
119
+ }
120
+ });
121
+ const sentryScope = Sentry__namespace.getCurrentScope();
122
+ sentryScope.setTag('role', 'frontend');
123
+ sentryScope.setTag('applicationName', window.app.applicationName);
124
+ sentryScope.setTag('service', `merchant-center-frontend-${window.app.applicationName}`);
125
+ }
126
+ };
127
+ const isExtraAsObject = extrasOrExtra => typeof extrasOrExtra === 'object';
128
+ const reportErrorToSentry = (error, extraInfo, getIsEnabled) => {
129
+ const isEnabled = getIsEnabled ? getIsEnabled() : Boolean(window.app.trackingSentry);
130
+ if (typeof error === 'string' && !isEnabled) {
131
+ console.warn('[SENTRY]: You called "reportErrorToSentry" with a string argument. ' + '"Error" objects should be preferred so that a stack-trace can be made available in Sentry. ' + 'See: https://docs.sentry.io/clients/javascript/usage/#try-catch');
132
+ }
133
+ if (isEnabled) {
134
+ if (extraInfo?.extra) {
135
+ if (isExtraAsObject(extraInfo.extra)) {
136
+ // See https://docs.sentry.io/platforms/javascript/react/
137
+ Sentry__namespace.setExtras(extraInfo.extra);
138
+ } else {
139
+ Sentry__namespace.setExtra('extra', extraInfo.extra);
140
+ }
141
+ } else if (error instanceof ErrorEvent) {
142
+ Sentry__namespace.setExtras({
143
+ filename: error.filename,
144
+ lineno: error.lineno,
145
+ colno: error.colno,
146
+ error: error.error
147
+ });
148
+ }
149
+ // Generate a unique ID referring to the last generated Sentry error
150
+ const errorId = sendErrorToSentry(error);
151
+
152
+ // The error stack should be available in Sentry, so there is no
153
+ // need to print it in the console as well.
154
+ // We just notify that an error occurred and provide the error ID.
155
+ console.error(`[SENTRY]: An error occured (ID: ${errorId}).`);
156
+ return errorId;
157
+ }
158
+ console.error('[SENTRY]:', error);
159
+ return undefined;
160
+ };
161
+
162
+ function _callSuper$1(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct$1() ? _Reflect$construct__default["default"](o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
163
+ function _isNativeReflectConstruct$1() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct__default["default"](Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct$1 = function () { return !!t; })(); }
164
+ /**
165
+ * This component will let sentry know if any information about the user has
166
+ * changed.
167
+ */
168
+ let SentryUserTracker = /*#__PURE__*/function (_PureComponent) {
169
+ function SentryUserTracker() {
170
+ var _this;
171
+ _classCallCheck(this, SentryUserTracker);
172
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
173
+ args[_key] = arguments[_key];
174
+ }
175
+ _this = _callSuper$1(this, SentryUserTracker, [...args]);
176
+ _this.syncUser = () => {
177
+ if (_this.props.user && window.app.trackingSentry) {
178
+ // to avoid sending personal data to sentry we anonymize the email address
179
+ // by only sending the domain part or the email
180
+ const emailTld = _this.props.user.email.split('@')[1];
181
+ Sentry__namespace.setUser({
182
+ email: `xxx@${emailTld}`,
183
+ id: _this.props.user.id
184
+ });
185
+ }
186
+ };
187
+ return _this;
188
+ }
189
+ _inherits(SentryUserTracker, _PureComponent);
190
+ return _createClass(SentryUserTracker, [{
191
+ key: "componentDidMount",
192
+ value: function componentDidMount() {
193
+ // since the user and project could have been loaded from the apollo cache
194
+ // they could be preset already when mounting
195
+ this.syncUser();
196
+ }
197
+ }, {
198
+ key: "componentDidUpdate",
199
+ value: function componentDidUpdate() {
200
+ this.syncUser();
201
+ }
202
+ }, {
203
+ key: "render",
204
+ value: function render() {
205
+ return null;
206
+ }
207
+ }]);
208
+ }(react.PureComponent);
209
+ SentryUserTracker.displayName = 'SentryUserTracker';
210
+
211
+ function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? _Reflect$construct__default["default"](o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
212
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct__default["default"](Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); }
213
+ let SentryUserLogoutTracker = /*#__PURE__*/function (_Component) {
214
+ function SentryUserLogoutTracker() {
215
+ _classCallCheck(this, SentryUserLogoutTracker);
216
+ return _callSuper(this, SentryUserLogoutTracker, arguments);
217
+ }
218
+ _inherits(SentryUserLogoutTracker, _Component);
219
+ return _createClass(SentryUserLogoutTracker, [{
220
+ key: "componentDidMount",
221
+ value: function componentDidMount() {
222
+ // When the user is not logged in anymore (e.g. on logout) we still track
223
+ // errors but without the user data in context.
224
+ if (window.app.trackingSentry) {
225
+ Sentry__namespace.configureScope(scope => {
226
+ scope.clear();
227
+ });
228
+ }
229
+ }
230
+ }, {
231
+ key: "render",
232
+ value: function render() {
233
+ return null;
234
+ }
235
+ }]);
236
+ }(react.Component);
237
+ SentryUserLogoutTracker.displayName = 'SentryUserLogoutTracker';
238
+
239
+ exports.SentryUserLogoutTracker = SentryUserLogoutTracker;
240
+ exports.SentryUserTracker = SentryUserTracker;
241
+ exports.boot = boot;
242
+ exports.redactUnsafeEventFields = redactUnsafeEventFields;
243
+ exports.reportErrorToSentry = reportErrorToSentry;
244
+ exports.version = version;
@@ -0,0 +1,208 @@
1
+ import _JSON$stringify from '@babel/runtime-corejs3/core-js-stable/json/stringify';
2
+ import _includesInstanceProperty from '@babel/runtime-corejs3/core-js-stable/instance/includes';
3
+ import _Object$keys from '@babel/runtime-corejs3/core-js-stable/object/keys';
4
+ import * as Sentry from '@sentry/react';
5
+ import { globalHandlersIntegration } from '@sentry/browser';
6
+ import history from '@commercetools-frontend/browser-history';
7
+ import _Reflect$construct from '@babel/runtime-corejs3/core-js-stable/reflect/construct';
8
+ import _classCallCheck from '@babel/runtime-corejs3/helpers/esm/classCallCheck';
9
+ import _createClass from '@babel/runtime-corejs3/helpers/esm/createClass';
10
+ import _possibleConstructorReturn from '@babel/runtime-corejs3/helpers/esm/possibleConstructorReturn';
11
+ import _getPrototypeOf from '@babel/runtime-corejs3/helpers/esm/getPrototypeOf';
12
+ import _inherits from '@babel/runtime-corejs3/helpers/esm/inherits';
13
+ import { PureComponent, Component } from 'react';
14
+
15
+ // NOTE: This string will be replaced on build time with the package version.
16
+ var version = "24.7.2";
17
+
18
+ const makeErrorToCapture = error => {
19
+ if (error instanceof Error) return error;
20
+ if (error instanceof ErrorEvent) return new Error(error.message);
21
+ return new Error(_JSON$stringify(error.reason || 'Unhandled rejection without a reason'));
22
+ };
23
+ const sendErrorToSentry = error => {
24
+ if (typeof error === 'string') return Sentry.captureMessage(error);
25
+ const errorToCapture = makeErrorToCapture(error);
26
+ return Sentry.captureException(errorToCapture);
27
+ };
28
+ const replaceEventValues = (source, replacements) => {
29
+ const replaceEventValue = prop => {
30
+ source[prop] = replacements[prop];
31
+ };
32
+ for (const prop in source) {
33
+ if (Object.prototype.hasOwnProperty.call(source, prop)) {
34
+ var _context;
35
+ const hasPropReplacement = _includesInstanceProperty(_context = _Object$keys(replacements)).call(_context, prop);
36
+ switch (typeof source[prop]) {
37
+ case 'string':
38
+ if (hasPropReplacement) {
39
+ replaceEventValue(prop);
40
+ }
41
+ break;
42
+ case 'object':
43
+ if (hasPropReplacement) {
44
+ replaceEventValue(prop);
45
+ } else {
46
+ replaceEventValues(source[prop], replacements);
47
+ }
48
+ break;
49
+ }
50
+ }
51
+ }
52
+ return source;
53
+ };
54
+ const redactUnsafeEventFields = event => {
55
+ return replaceEventValues(event, {
56
+ firstName: '[Redacted]',
57
+ lastName: '[Redacted]',
58
+ email: '[Redacted]'
59
+ });
60
+ };
61
+ const boot = () => {
62
+ if (window.app.trackingSentry && window.app.trackingSentry !== 'null') {
63
+ Sentry.init({
64
+ dsn: window.app.trackingSentry,
65
+ release: window.app.revision,
66
+ environment: `${window.app.env}-${window.app.location}`,
67
+ // In order to reduce the noise in sentry we only track errors that come
68
+ // from our code and ignore errors that come from other services
69
+ // https://blog.sentry.io/2017/03/27/tips-for-reducing-javascript-error-noise.html
70
+ allowUrls: [window.app.cdnUrl, window.app.frontendHost],
71
+ // suppress any resize observer errors, see
72
+ // https://github.com/vercel/next.js/discussions/51551
73
+ ignoreErrors: [/ResizeObserver loop (limit exceeded|completed with undelivered notifications)/i],
74
+ integrations: [globalHandlersIntegration({
75
+ onunhandledrejection: false,
76
+ onerror: false
77
+ }), Sentry.reactRouterV5BrowserTracingIntegration({
78
+ history
79
+ })],
80
+ // Sending 5% of transactions. We can adjust that as we see a need to.
81
+ // Generally we need to find a balance between performance and data volume.
82
+ // If we need more flexible and dynamic way of gathering important samples,
83
+ // we can implement the `tracesSampler` function.
84
+ // https://docs.sentry.io/platforms/javascript/guides/react/configuration/sampling/#sampling-transaction-events
85
+ tracesSampleRate: 0.05,
86
+ beforeSend(event) {
87
+ return redactUnsafeEventFields(event);
88
+ }
89
+ });
90
+ const sentryScope = Sentry.getCurrentScope();
91
+ sentryScope.setTag('role', 'frontend');
92
+ sentryScope.setTag('applicationName', window.app.applicationName);
93
+ sentryScope.setTag('service', `merchant-center-frontend-${window.app.applicationName}`);
94
+ }
95
+ };
96
+ const isExtraAsObject = extrasOrExtra => typeof extrasOrExtra === 'object';
97
+ const reportErrorToSentry = (error, extraInfo, getIsEnabled) => {
98
+ const isEnabled = getIsEnabled ? getIsEnabled() : Boolean(window.app.trackingSentry);
99
+ if (typeof error === 'string' && !isEnabled) {
100
+ console.warn('[SENTRY]: You called "reportErrorToSentry" with a string argument. ' + '"Error" objects should be preferred so that a stack-trace can be made available in Sentry. ' + 'See: https://docs.sentry.io/clients/javascript/usage/#try-catch');
101
+ }
102
+ if (isEnabled) {
103
+ if (extraInfo?.extra) {
104
+ if (isExtraAsObject(extraInfo.extra)) {
105
+ // See https://docs.sentry.io/platforms/javascript/react/
106
+ Sentry.setExtras(extraInfo.extra);
107
+ } else {
108
+ Sentry.setExtra('extra', extraInfo.extra);
109
+ }
110
+ } else if (error instanceof ErrorEvent) {
111
+ Sentry.setExtras({
112
+ filename: error.filename,
113
+ lineno: error.lineno,
114
+ colno: error.colno,
115
+ error: error.error
116
+ });
117
+ }
118
+ // Generate a unique ID referring to the last generated Sentry error
119
+ const errorId = sendErrorToSentry(error);
120
+
121
+ // The error stack should be available in Sentry, so there is no
122
+ // need to print it in the console as well.
123
+ // We just notify that an error occurred and provide the error ID.
124
+ console.error(`[SENTRY]: An error occured (ID: ${errorId}).`);
125
+ return errorId;
126
+ }
127
+ console.error('[SENTRY]:', error);
128
+ return undefined;
129
+ };
130
+
131
+ function _callSuper$1(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct$1() ? _Reflect$construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
132
+ function _isNativeReflectConstruct$1() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct$1 = function () { return !!t; })(); }
133
+ /**
134
+ * This component will let sentry know if any information about the user has
135
+ * changed.
136
+ */
137
+ let SentryUserTracker = /*#__PURE__*/function (_PureComponent) {
138
+ function SentryUserTracker() {
139
+ var _this;
140
+ _classCallCheck(this, SentryUserTracker);
141
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
142
+ args[_key] = arguments[_key];
143
+ }
144
+ _this = _callSuper$1(this, SentryUserTracker, [...args]);
145
+ _this.syncUser = () => {
146
+ if (_this.props.user && window.app.trackingSentry) {
147
+ // to avoid sending personal data to sentry we anonymize the email address
148
+ // by only sending the domain part or the email
149
+ const emailTld = _this.props.user.email.split('@')[1];
150
+ Sentry.setUser({
151
+ email: `xxx@${emailTld}`,
152
+ id: _this.props.user.id
153
+ });
154
+ }
155
+ };
156
+ return _this;
157
+ }
158
+ _inherits(SentryUserTracker, _PureComponent);
159
+ return _createClass(SentryUserTracker, [{
160
+ key: "componentDidMount",
161
+ value: function componentDidMount() {
162
+ // since the user and project could have been loaded from the apollo cache
163
+ // they could be preset already when mounting
164
+ this.syncUser();
165
+ }
166
+ }, {
167
+ key: "componentDidUpdate",
168
+ value: function componentDidUpdate() {
169
+ this.syncUser();
170
+ }
171
+ }, {
172
+ key: "render",
173
+ value: function render() {
174
+ return null;
175
+ }
176
+ }]);
177
+ }(PureComponent);
178
+ SentryUserTracker.displayName = 'SentryUserTracker';
179
+
180
+ function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? _Reflect$construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
181
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function () { return !!t; })(); }
182
+ let SentryUserLogoutTracker = /*#__PURE__*/function (_Component) {
183
+ function SentryUserLogoutTracker() {
184
+ _classCallCheck(this, SentryUserLogoutTracker);
185
+ return _callSuper(this, SentryUserLogoutTracker, arguments);
186
+ }
187
+ _inherits(SentryUserLogoutTracker, _Component);
188
+ return _createClass(SentryUserLogoutTracker, [{
189
+ key: "componentDidMount",
190
+ value: function componentDidMount() {
191
+ // When the user is not logged in anymore (e.g. on logout) we still track
192
+ // errors but without the user data in context.
193
+ if (window.app.trackingSentry) {
194
+ Sentry.configureScope(scope => {
195
+ scope.clear();
196
+ });
197
+ }
198
+ }
199
+ }, {
200
+ key: "render",
201
+ value: function render() {
202
+ return null;
203
+ }
204
+ }]);
205
+ }(Component);
206
+ SentryUserLogoutTracker.displayName = 'SentryUserLogoutTracker';
207
+
208
+ export { SentryUserLogoutTracker, SentryUserTracker, boot, redactUnsafeEventFields, reportErrorToSentry, version };
@@ -0,0 +1,4 @@
1
+ export { default as version } from "./version.js";
2
+ export * from "./sentry.js";
3
+ export { default as SentryUserTracker } from "./sentry-user-tracker/index.js";
4
+ export { default as SentryUserLogoutTracker } from "./sentry-user-logout-tracker/index.js";
@@ -0,0 +1 @@
1
+ export { default } from "./sentry-user-logout-tracker.js";
@@ -0,0 +1,7 @@
1
+ import { Component } from 'react';
2
+ declare class SentryUserLogoutTracker extends Component {
3
+ static displayName: string;
4
+ componentDidMount(): void;
5
+ render(): null;
6
+ }
7
+ export default SentryUserLogoutTracker;
@@ -0,0 +1 @@
1
+ export { default } from "./sentry-user-tracker.js";
@@ -0,0 +1,20 @@
1
+ import { PureComponent } from 'react';
2
+ type SentryUser = {
3
+ id: string;
4
+ email: string;
5
+ };
6
+ type Props = {
7
+ user?: SentryUser;
8
+ };
9
+ /**
10
+ * This component will let sentry know if any information about the user has
11
+ * changed.
12
+ */
13
+ declare class SentryUserTracker extends PureComponent<Props> {
14
+ static displayName: string;
15
+ componentDidMount(): void;
16
+ componentDidUpdate(): void;
17
+ syncUser: () => void;
18
+ render(): null;
19
+ }
20
+ export default SentryUserTracker;
@@ -0,0 +1,10 @@
1
+ import * as Sentry from '@sentry/react';
2
+ import type { Extra, Extras, Event } from '@sentry/types';
3
+ type ReportableEvent = ErrorEvent | PromiseRejectionEvent;
4
+ type Reportable = Error | ReportableEvent | string;
5
+ export declare const redactUnsafeEventFields: (event: Event) => Sentry.Event;
6
+ export declare const boot: () => void;
7
+ export declare const reportErrorToSentry: (error: Reportable, extraInfo?: {
8
+ extra?: Extra | Extras;
9
+ }, getIsEnabled?: () => boolean) => string | undefined;
10
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const _default: "__@APPLICATION_KIT_PACKAGE/VERSION_OF_RELEASE__";
2
+ export default _default;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@commercetools-frontend/sentry",
3
+ "version": "0.0.0-CRAFT-1791-20251006162610",
4
+ "description": "Components to set up Sentry for a MC application",
5
+ "bugs": "https://github.com/commercetools/merchant-center-application-kit/issues",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/commercetools/merchant-center-application-kit.git",
9
+ "directory": "packages/sentry"
10
+ },
11
+ "homepage": "https://docs.commercetools.com/merchant-center-customizations",
12
+ "keywords": [
13
+ "javascript",
14
+ "frontend",
15
+ "react",
16
+ "toolkit"
17
+ ],
18
+ "license": "MIT",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "main": "dist/commercetools-frontend-sentry.cjs.js",
23
+ "module": "dist/commercetools-frontend-sentry.esm.js",
24
+ "files": [
25
+ "dist",
26
+ "package.json",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "dependencies": {
31
+ "@babel/runtime": "^7.22.15",
32
+ "@babel/runtime-corejs3": "^7.22.15",
33
+ "@commercetools-frontend/browser-history": "0.0.0-CRAFT-1791-20251006162610",
34
+ "@commercetools-frontend/constants": "0.0.0-CRAFT-1791-20251006162610",
35
+ "@sentry/browser": "7.120.4",
36
+ "@sentry/react": "7.120.4",
37
+ "@sentry/types": "7.120.4",
38
+ "@types/prop-types": "^15.7.5",
39
+ "@types/react": "^19.0.3",
40
+ "prop-types": "15.8.1"
41
+ },
42
+ "devDependencies": {
43
+ "@emotion/react": "^11.14.0",
44
+ "@types/jest": "^29.5.14",
45
+ "jest": "29.7.0",
46
+ "react": "19.0.0",
47
+ "sentry-testkit": "5.0.10",
48
+ "wait-for-expect": "3.0.2"
49
+ },
50
+ "peerDependencies": {
51
+ "react": "19.x"
52
+ }
53
+ }