@shopgate/pwa-common 7.32.2-alpha.1 → 7.32.2-alpha.10

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.
@@ -1,8 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
- import "core-js/modules/es.string.replace.js";
3
- import "core-js/modules/web.url.js";
4
- import "core-js/modules/web.url.to-json.js";
5
- import "core-js/modules/web.url-search-params.js";
6
1
  import { logger } from '@shopgate/pwa-core/helpers';
7
2
  import { DEEPLINK_CART_ADD_PRODUCT_PATTERN } from '@shopgate/pwa-common-commerce/cart/constants';
8
3
  import fetchProduct from '@shopgate/pwa-common-commerce/product/actions/fetchProduct';
@@ -44,7 +39,11 @@ export default function handleLink(payload, allowExternalLinks = false) {
44
39
  pathname
45
40
  } = new URL(link));
46
41
  } catch (linkParseError) {
47
- logger.error(`Could not parse link ${link}`, linkParseError);
42
+ // The error is sent to Sentry. Query string, hash and credentials in front of the host can
43
+ // contain secrets, so they are left out, and so is the error of the browser, which can
44
+ // contain the whole link.
45
+ const loggedLink = link.split(/[?#]/)[0].replace(/^([^/]*\/\/)[^/@]*@/, '$1');
46
+ logger.error(new Error(`Could not parse link ${loggedLink}`));
48
47
  }
49
48
  } else {
50
49
  // Remove the deeplink protocol from the link.
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
1
  import pathMatch from 'path-match';
3
2
 
4
3
  /**
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
1
  /**
3
2
  * Class to maintain the persisted redux reducers.
4
3
  */
@@ -1,5 +1,4 @@
1
1
  import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
2
- import "core-js/modules/web.url-search-params.js";
3
2
  import MediaProvider from "./MediaProvider";
4
3
 
5
4
  /* eslint-disable class-methods-use-this */
@@ -1,6 +1,7 @@
1
1
  import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
2
2
  import { PureComponent } from 'react';
3
3
  import PropTypes from 'prop-types';
4
+ import { toError } from '@shopgate/pwa-core/helpers/error';
4
5
  import connector from "./connector";
5
6
 
6
7
  /**
@@ -35,9 +36,9 @@ let ErrorBoundary = /*#__PURE__*/function (_PureComponent) {
35
36
  };
36
37
  var _proto = ErrorBoundary.prototype;
37
38
  _proto.componentDidCatch = function componentDidCatch(error, errorInfo) {
38
- // eslint-disable-next-line no-param-reassign
39
- error.stack = errorInfo.componentStack;
40
- this.props.appError(error);
39
+ this.props.appError(toError(error, {
40
+ componentStack: errorInfo.componentStack
41
+ }));
41
42
  }
42
43
 
43
44
  /**
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.reduce.js";
2
1
  import React from 'react';
3
2
  import PropTypes from 'prop-types';
4
3
  import { renderToString } from 'react-dom/server';
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.string.replace.js";
2
1
  import React, { useCallback } from 'react';
3
2
  import PropTypes from 'prop-types';
4
3
  import { hasWebBridge } from '@shopgate/engage/core';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../components/Portal/index.jsx"],"names":[],"mappings":";AAOA;;GAEG;AACH;IACE;;;;MAIE;IAEF;;;MAGE;IAEF;;;OAGG;IACH,mBAFW,MAAM,EAUhB;IALC;;MAEC;IAED,kBAAsD;IAGxD;;;;OAIG;IACH,yBAHW,KAAK,QACL,MAAM,QAKhB;IAED;;;;OAIG;IACH,wBAAyB,OAHd,MAGmB,WAa5B;IAEF;;;;OAIG;IACH,sBAAuB,MAHZ,MAGgB,WA0CzB;IAEF;;;OAGG;IACH,UAFY,GAAG,CAiCd;CACF;kBAvJ8C,OAAO;sBAChC,YAAY"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../components/Portal/index.jsx"],"names":[],"mappings":";AAQA;;GAEG;AACH;IACE;;;;MAIE;IAEF;;;MAGE;IAEF;;;OAGG;IACH,mBAFW,MAAM,EAUhB;IALC;;MAEC;IAED,kBAAsD;IAGxD;;;;OAIG;IACH,yBAHW,KAAK,QACL,MAAM,QAKhB;IAED;;;;OAIG;IACH,wBAAyB,OAHd,MAGmB,WAa5B;IAEF;;;;OAIG;IACH,sBAAuB,MAHZ,MAGgB,WA0CzB;IAEF;;;OAGG;IACH,UAFY,GAAG,CAiCd;CACF;kBAxJ8C,OAAO;sBAChC,YAAY"}
@@ -2,6 +2,7 @@ import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
2
2
  import React, { PureComponent, Suspense } from 'react';
3
3
  import PropTypes from 'prop-types';
4
4
  import { logger } from '@shopgate/pwa-core/helpers';
5
+ import { toError } from '@shopgate/pwa-core/helpers/error';
5
6
  import Loading from "../Loading";
6
7
  import portalCollection from "../../helpers/portals/portalCollection";
7
8
  import { componentsConfig } from "../../helpers/config";
@@ -104,7 +105,9 @@ let Portal = /*#__PURE__*/function (_PureComponent) {
104
105
  this.setState({
105
106
  hasError: true
106
107
  });
107
- logger.error(error, info);
108
+ logger.error(toError(error, {
109
+ componentStack: info.componentStack
110
+ }));
108
111
  };
109
112
  /**
110
113
  * Renders the component.
@@ -1,5 +1,4 @@
1
1
  import _inheritsLoose from "@babel/runtime/helpers/inheritsLoose";
2
- import "core-js/modules/es.string.replace.js";
3
2
  import React from 'react';
4
3
  import PropTypes from 'prop-types';
5
4
  import { router, stack as routeStack, onDidPush, onDidPop, onDidReplace, onDidReset, onUpdate, ACTION_POP } from '@virtuous/conductor';
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.string.replace.js";
2
1
  import pck from "../../package.json";
3
2
  import { buildThemeConfig } from "./theme";
4
3
  import { assignObjectDeep } from "../data";
@@ -1,6 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
- import "core-js/modules/es.array.reduce.js";
3
- import "core-js/modules/es.string.replace.js";
4
1
  import { logger } from '@shopgate/pwa-core/helpers';
5
2
  import { isAndroidOs } from '@shopgate/pwa-core';
6
3
  import { isObject, isArray } from "../validation";
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.string.replace.js";
2
1
  /**
3
2
  * Checks if the given child is a descendant of the given parent.
4
3
  * @param {Object} parent The parent element.
@@ -1,6 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
- import "core-js/modules/es.array.reduce.js";
3
- import "core-js/modules/es.string.replace.js";
4
1
  import event from '@shopgate/pwa-core/classes/Event/index';
5
2
 
6
3
  /**
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.reduce.js";
2
1
  import IntlMessageFormat from 'intl-messageformat';
3
2
  import curry from 'lodash/curry';
4
3
  // eslint-disable-next-line import/no-named-default
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.reduce.js";
2
1
  import isPlainObject from 'lodash/isPlainObject';
3
2
  import cloneDeep from 'lodash/cloneDeep';
4
3
  import merge from 'lodash/merge';
@@ -1,5 +1,3 @@
1
- import "core-js/modules/es.array.reduce.js";
2
- import "core-js/modules/es.string.replace.js";
3
1
  import noop from 'lodash/noop';
4
2
  import { isObject } from "../validation";
5
3
 
@@ -1,6 +1,3 @@
1
- import "core-js/modules/es.array.reduce.js";
2
- import "core-js/modules/es.string.replace.js";
3
- import "core-js/modules/web.url-search-params.js";
4
1
  import { createBrowserHistory } from 'history';
5
2
  import { router } from '@virtuous/conductor';
6
3
  import { logger } from '@shopgate/pwa-core/helpers';
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
1
  import { logger } from '@shopgate/pwa-core/helpers';
3
2
  import { isNumber } from "../validation";
4
3
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopgate/pwa-common",
3
- "version": "7.32.2-alpha.1",
3
+ "version": "7.32.2-alpha.10",
4
4
  "description": "Common library for the Shopgate Connect PWA.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Shopgate <support@shopgate.com>",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@redux-devtools/extension": "^3.3.0",
23
- "@sentry/browser": "6.0.1",
23
+ "@sentry/browser": "^7.120.4",
24
24
  "@virtuous/conductor": "~2.5.0",
25
25
  "@virtuous/react-conductor": "~2.5.0",
26
26
  "@virtuous/redux-persister": "1.1.0-beta.7",
@@ -31,22 +31,22 @@
31
31
  "history": "^4.9.0",
32
32
  "intl-messageformat": "^11.2.9",
33
33
  "path-match": "^1.2.4",
34
+ "react-cookie": "^8.1.2",
34
35
  "react-dotdotdot": "~1.3.1",
35
36
  "react-helmet": "^6.1.0",
36
37
  "react-portal": "^3.1.0",
37
38
  "react-redux": "^8.1.3",
38
39
  "redux": "^5.0.1",
39
- "redux-logger": "^3.0.6",
40
40
  "redux-thunk": "^3.1.0",
41
41
  "reselect": "^4.1.8",
42
42
  "rxjs": "~5.5.12",
43
43
  "swiper": "12.2.0"
44
44
  },
45
45
  "devDependencies": {
46
- "@shopgate/pwa-core": "7.32.2-alpha.1",
47
- "@types/lodash": "^4.17.24",
46
+ "@shopgate/pwa-core": "7.32.2-alpha.10",
47
+ "@types/lodash": "^4.17.25",
48
48
  "@types/react-portal": "^3.0.9",
49
- "lodash": "^4.17.23",
49
+ "lodash": "^4.18.1",
50
50
  "prop-types": "~15.8.1",
51
51
  "react": "^17.0.2",
52
52
  "react-dom": "^17.0.2"
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.reduce.js";
2
1
  import { ENOTFOUND } from '@shopgate/pwa-core/constants/Pipeline';
3
2
  import { REQUEST_PAGE_CONFIG, RECEIVE_PAGE_CONFIG, ERROR_PAGE_CONFIG, APP_WILL_START } from "../../constants/ActionTypes";
4
3
 
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
1
  import { createSelector } from 'reselect';
3
2
  import MobileDetect from 'mobile-detect';
4
3
  import { hasSGJavaScriptBridge } from '@shopgate/pwa-core/helpers';
package/store/index.js CHANGED
@@ -1,4 +1,3 @@
1
- import "core-js/modules/es.string.replace.js";
2
1
  import { createStore, applyMiddleware } from 'redux';
3
2
  import { thunk } from 'redux-thunk';
4
3
  import { composeWithDevTools } from '@redux-devtools/extension';
@@ -1,7 +1,114 @@
1
- import { createLogger } from 'redux-logger';
2
1
  import { logger } from '@shopgate/pwa-core';
3
- export default createLogger({
4
- logger,
5
- collapsed: true,
6
- duration: true
7
- });
2
+ const STORAGE_KEY = 'sgReduxLogger';
3
+ const MUTED = 'color: gray; font-weight: lighter;';
4
+
5
+ /**
6
+ * Reads whether the logger was switched off with sgReduxLogger.off().
7
+ * @return {boolean}
8
+ */
9
+ const isSwitchedOff = () => {
10
+ try {
11
+ return window.localStorage.getItem(STORAGE_KEY) === 'off';
12
+ } catch (e) {
13
+ return false;
14
+ }
15
+ };
16
+ let enabled = !isSwitchedOff();
17
+
18
+ /**
19
+ * Remembers the switch for the next app start.
20
+ * @param {boolean} value Whether actions are logged.
21
+ * @return {boolean} Whether the switch could be saved.
22
+ */
23
+ const saveSwitch = value => {
24
+ try {
25
+ if (value) {
26
+ window.localStorage.removeItem(STORAGE_KEY);
27
+ } else {
28
+ window.localStorage.setItem(STORAGE_KEY, 'off');
29
+ }
30
+ } catch (e) {
31
+ return false;
32
+ }
33
+ return true;
34
+ };
35
+
36
+ /**
37
+ * Switches the logger on or off.
38
+ * @param {boolean} value Whether actions are logged.
39
+ */
40
+ const setEnabled = value => {
41
+ enabled = value;
42
+ const saved = saveSwitch(value);
43
+ logger.info(`Redux logger switched ${value ? 'on' : 'off'}${saved ? '' : ' until the next reload'}`);
44
+ };
45
+ window.sgReduxLogger = {
46
+ on: () => setEnabled(true),
47
+ off: () => setEnabled(false)
48
+ };
49
+
50
+ /**
51
+ * Formats the time of a date, e.g. 14:03:07.042.
52
+ * @param {Date} date The date.
53
+ * @return {string}
54
+ */
55
+ const formatTime = date => [date.getHours(), date.getMinutes(), date.getSeconds()].map(value => String(value).padStart(2, '0')).join(':').concat(`.${String(date.getMilliseconds()).padStart(3, '0')}`);
56
+
57
+ /**
58
+ * Prints an action as a collapsed console group.
59
+ * @param {Object} entry The logged action with the states before and after it.
60
+ */
61
+ const printEntry = ({
62
+ action,
63
+ prevState,
64
+ nextState,
65
+ error,
66
+ startedAt,
67
+ took
68
+ }) => {
69
+ logger.groupCollapsed(`%c action %c${String(action.type)} %c@ ${formatTime(startedAt)} %c(in ${took.toFixed(2)} ms)`, MUTED, 'color: inherit;', MUTED, MUTED);
70
+ logger.log('%c prev state', 'color: #9E9E9E; font-weight: bold', prevState);
71
+ logger.log('%c action ', 'color: #03A9F4; font-weight: bold', action);
72
+ if (error) {
73
+ logger.log('%c error ', 'color: #F20404; font-weight: bold;', error);
74
+ }
75
+ logger.log('%c next state', 'color: #4CAF50; font-weight: bold', nextState);
76
+ logger.groupEnd();
77
+ };
78
+
79
+ /**
80
+ * Logs every action with the state before and after it to the console, unless the logger was
81
+ * switched off with sgReduxLogger.off() in the console.
82
+ * @param {Object} store The redux store.
83
+ * @return {Function}
84
+ */
85
+ const loggerMiddleware = ({
86
+ getState
87
+ }) => next => action => {
88
+ if (!enabled) {
89
+ return next(action);
90
+ }
91
+ const startedAt = new Date();
92
+ const started = performance.now();
93
+ const prevState = getState();
94
+ let result;
95
+ let error;
96
+ try {
97
+ result = next(action);
98
+ } catch (e) {
99
+ error = e;
100
+ }
101
+ printEntry({
102
+ action,
103
+ prevState,
104
+ nextState: getState(),
105
+ error,
106
+ startedAt,
107
+ took: performance.now() - started
108
+ });
109
+ if (error) {
110
+ throw error;
111
+ }
112
+ return result;
113
+ };
114
+ export default loggerMiddleware;
@@ -1,9 +1,10 @@
1
- import "core-js/modules/es.array.includes.js";
2
1
  import { isAvailable } from '@shopgate/native-modules';
3
- import { init, addBreadcrumb, configureScope, captureException, captureMessage, captureEvent, withScope, Severity as SentrySeverity } from '@sentry/browser';
2
+ import { init, addBreadcrumb, setTags, captureException, captureEvent, withScope } from '@sentry/browser';
3
+ import { router } from '@virtuous/conductor';
4
4
  import { EBIGAPI, emitter, errorManager, ETIMEOUT, ENETUNREACH, EUNKNOWN, EFAVORITE } from '@shopgate/pwa-core';
5
5
  import { hasWebBridge } from '@shopgate/engage/core';
6
6
  import { SOURCE_TRACKING, SOURCE_CONSOLE, Severity } from '@shopgate/pwa-core/constants/ErrorManager';
7
+ import { describeValue } from '@shopgate/pwa-core/helpers/error';
7
8
  import { main$ } from "../streams/main";
8
9
  import {
9
10
  // eslint-disable-next-line import/no-named-default
@@ -12,23 +13,45 @@ import { env } from "../helpers/environment";
12
13
  import { transformGeneralPipelineError, getDisplayErrorMessage } from "./helpers/pipeline";
13
14
  import { historyPop } from "../actions/router";
14
15
  import showModal from "../actions/modal/showModal";
15
- import { getUserData } from "../selectors/user";
16
- import { userDidUpdate$ } from "../streams/user";
17
16
  import { clientInformationDidUpdate$ } from "../streams/client";
18
- import { appWillInit$, appWillStart$, appDidStart$ } from "../streams/app";
17
+ import { appWillInit$, appWillStart$ } from "../streams/app";
19
18
  import { appError$, pipelineError$ } from "../streams/error";
20
19
  import { getRouterStack } from "../selectors/router";
21
20
  import { MODAL_PIPELINE_ERROR } from "../constants/ModalTypes";
21
+ import { APP_ERROR } from "../constants/ActionTypes";
22
22
  import ToastProvider from "../providers/toast";
23
23
 
24
24
  // Generic, translated fallback shown when a backend error carries no code we can map to a message.
25
25
  const GENERIC_ERROR_MESSAGE = 'modal.body_error';
26
26
 
27
+ /**
28
+ * @param {*} error The rejection reason or exception.
29
+ * @returns {boolean} Whether the error was created by the PipelineManager.
30
+ */
31
+ const isPipelineError = error => !!error && typeof error.code === 'string' && typeof error.handled === 'boolean';
32
+
27
33
  /**
28
34
  * App errors subscriptions.
29
35
  * @param {Function} subscribe The subscribe function.
30
36
  */
31
37
  export default subscribe => {
38
+ subscribe(appWillInit$, () => {
39
+ window.addEventListener('unhandledrejection', event => {
40
+ const {
41
+ reason
42
+ } = event;
43
+ if (!isPipelineError(reason)) {
44
+ return;
45
+ }
46
+ event.preventDefault();
47
+ if (env === 'development') {
48
+ const hint = reason.handled ? 'The default error handling already handled it' : 'The request excluded it from the default error handling (blacklisted or suppressed), so the action is expected to handle it';
49
+ // eslint-disable-next-line no-console
50
+ console.info(`Pipeline error not handled by the caller: ${reason.code} (${reason.message}). ${hint}; add .catch() if the caller needs to react.`);
51
+ }
52
+ });
53
+ });
54
+
32
55
  /** Set general error transformations */
33
56
  subscribe(appWillStart$, () => {
34
57
  errorManager.setMessage({
@@ -160,16 +183,30 @@ export default subscribe => {
160
183
  return;
161
184
  }
162
185
  const severityMap = {
163
- [Severity.Fatal]: SentrySeverity.Fatal,
164
- [Severity.Error]: SentrySeverity.Error,
165
- [Severity.Critical]: SentrySeverity.Critical,
166
- [Severity.Warning]: SentrySeverity.Warning,
167
- [Severity.Info]: SentrySeverity.Info,
168
- [Severity.Debug]: SentrySeverity.Debug
186
+ [Severity.Fatal]: 'fatal',
187
+ [Severity.Error]: 'error',
188
+ [Severity.Critical]: 'fatal',
189
+ [Severity.Warning]: 'warning',
190
+ [Severity.Info]: 'info',
191
+ [Severity.Debug]: 'debug'
169
192
  };
170
193
  const ignoredDefaultBreadcrumbs = ['console', 'fetch', 'xhr', 'ui.click'];
171
- let trackedSeverities = Object.getOwnPropertySymbols(severityMap).map(s => severityMap[s]);
172
- const minSeverityIndex = trackedSeverities.indexOf(level);
194
+
195
+ /**
196
+ * @param {string} url An absolute or relative URL of the app.
197
+ * @returns {string} The URL with its route path replaced by the route pattern, e.g.
198
+ * `/orders/:orderId`, and without query string and hash.
199
+ */
200
+ const redactUrl = url => {
201
+ if (typeof url !== 'string') {
202
+ return url;
203
+ }
204
+ const [path] = url.split(/[?#]/);
205
+ const [, base, routePath] = /^(.*?index\.html)(.*)$/.exec(path) || /^([a-z][a-z\d+.-]*:\/\/[^/]+)(.*)$/i.exec(path) || [null, '', path];
206
+ return `${base}${router.findPattern(routePath || '/') || '/(unknown route)'}`;
207
+ };
208
+ let trackedSeverities = Object.values(severityMap);
209
+ const minSeverityIndex = Object.keys(severityMap).indexOf(level);
173
210
  if (minSeverityIndex > -1) {
174
211
  trackedSeverities = trackedSeverities.slice(0, minSeverityIndex + 1);
175
212
  }
@@ -183,54 +220,86 @@ export default subscribe => {
183
220
  release: pckVersion,
184
221
  attachStacktrace: true,
185
222
  sampleRate,
223
+ ignoreErrors: [/play\(\) failed because the user didn't interact/],
186
224
  beforeBreadcrumb(breadcrumb) {
187
225
  if (ignoredDefaultBreadcrumbs.includes(breadcrumb.category)) {
188
226
  return null;
189
227
  }
228
+ if (breadcrumb.category === 'navigation' && breadcrumb.data) {
229
+ return {
230
+ ...breadcrumb,
231
+ data: {
232
+ from: redactUrl(breadcrumb.data.from),
233
+ to: redactUrl(breadcrumb.data.to)
234
+ }
235
+ };
236
+ }
190
237
  return breadcrumb;
191
238
  },
192
- beforeSend(event) {
239
+ beforeSend(event, hint) {
193
240
  if (event.level && !trackedSeverities.includes(event.level)) {
194
241
  return null;
195
242
  }
196
- // eslint-disable-next-line no-param-reassign
197
- event.extra = {
198
- ...(event.extra || {}),
199
- routerStack: getRouterStack(getState()).slice(-5)
243
+ if (isPipelineError(hint?.originalException)) {
244
+ return null;
245
+ }
246
+ return {
247
+ ...event,
248
+ request: event.request && {
249
+ ...event.request,
250
+ url: redactUrl(event.request.url),
251
+ headers: event.request.headers && {
252
+ ...event.request.headers,
253
+ Referer: redactUrl(event.request.headers.Referer)
254
+ }
255
+ },
256
+ extra: {
257
+ ...(event.extra || {}),
258
+ routerStack: getRouterStack(getState()).slice(-5).map(({
259
+ pattern
260
+ }) => ({
261
+ pattern
262
+ }))
263
+ }
200
264
  };
201
- return event;
202
265
  }
203
266
  });
204
- configureScope(scope => {
205
- scope.setTag('marketId', appConfig.marketId);
206
- scope.setTag('appId', appConfig.appId);
207
- scope.setTag('pwaVersion', pckVersion);
208
- scope.setTag('theme', themeName);
209
- scope.setTag('language', appConfig.language);
210
- scope.setTag('isWebsite', hasWebBridge());
211
- scope.setTag('isReactNativeApp', isAvailable());
212
- scope.setTag('merchantCode', appConfig.omniMerchantCode);
267
+ setTags({
268
+ marketId: appConfig.marketId,
269
+ appId: appConfig.appId,
270
+ pwaVersion: pckVersion,
271
+ theme: themeName,
272
+ language: appConfig.language,
273
+ isWebsite: hasWebBridge(),
274
+ isReactNativeApp: isAvailable(),
275
+ merchantCode: appConfig.omniMerchantCode
213
276
  });
214
- if (window) {
215
- window.onerror = (message, source, lineno, colno, error) => {
216
- captureException(error);
217
- };
218
- }
219
- emitter.addListener(SOURCE_TRACKING, error => {
277
+ emitter.addListener(SOURCE_CONSOLE, args => {
278
+ const error = args.find(arg => arg instanceof Error);
279
+ const texts = args.filter(arg => arg !== error).map(describeValue);
280
+ if (!error) {
281
+ addBreadcrumb({
282
+ category: 'logger',
283
+ message: texts.join(' '),
284
+ level: 'error'
285
+ });
286
+ return;
287
+ }
220
288
  withScope(scope => {
221
- if (error.context) {
289
+ scope.setLevel('error');
290
+ scope.setTag('source', SOURCE_CONSOLE);
291
+ if (texts.length > 0) {
292
+ scope.setExtra('details', texts);
293
+ }
294
+ if (error.source === SOURCE_TRACKING && error.context) {
222
295
  scope.setExtra('trackerName', error.context);
223
296
  }
297
+ if (error.componentStack) {
298
+ scope.setExtra('componentStack', error.componentStack);
299
+ }
224
300
  captureException(error);
225
301
  });
226
302
  });
227
- emitter.addListener(SOURCE_CONSOLE, args => {
228
- withScope(scope => {
229
- scope.setLevel(SentrySeverity.Error);
230
- scope.setExtra('error', args);
231
- captureMessage('Console error');
232
- });
233
- });
234
303
  });
235
304
  subscribe(main$, ({
236
305
  action
@@ -238,20 +307,7 @@ export default subscribe => {
238
307
  addBreadcrumb({
239
308
  category: 'redux',
240
309
  message: `[Redux] ${action.type}`,
241
- level: SentrySeverity.Info,
242
- data: {
243
- ...action
244
- }
245
- });
246
- });
247
- subscribe(userDidUpdate$, ({
248
- getState
249
- }) => {
250
- const {
251
- id: userId
252
- } = getUserData(getState());
253
- configureScope(scope => {
254
- scope.setTag('userId', userId);
310
+ level: 'info'
255
311
  });
256
312
  });
257
313
 
@@ -261,21 +317,11 @@ export default subscribe => {
261
317
  }) => {
262
318
  const {
263
319
  appVersion,
264
- libVersion,
265
- deviceId
320
+ libVersion
266
321
  } = action.data;
267
- configureScope(scope => {
268
- scope.setTag('appVersion', appVersion);
269
- scope.setTag('libVersion', libVersion);
270
- scope.setTag('deviceId', deviceId);
271
- });
272
- });
273
-
274
- // Add app start event for debugging
275
- subscribe(appDidStart$, () => {
276
- withScope(scope => {
277
- scope.setLevel(SentrySeverity.Debug);
278
- captureMessage('App did start');
322
+ setTags({
323
+ appVersion,
324
+ libVersion
279
325
  });
280
326
  });
281
327
 
@@ -283,9 +329,12 @@ export default subscribe => {
283
329
  subscribe(appError$, ({
284
330
  action
285
331
  }) => {
332
+ if (!(action.error instanceof Error)) {
333
+ return;
334
+ }
286
335
  withScope(scope => {
287
- if (action.error.stack) {
288
- scope.setExtra('stack', action.error.stack);
336
+ if (action.error.componentStack) {
337
+ scope.setExtra('componentStack', action.error.componentStack);
289
338
  }
290
339
  captureException(action.error);
291
340
  });
@@ -299,19 +348,20 @@ export default subscribe => {
299
348
  error = {}
300
349
  } = action;
301
350
  const {
302
- code,
303
- message,
304
- meta: {
305
- message: metaMessage
306
- } = {}
351
+ code
307
352
  } = error;
353
+ if (action.type === APP_ERROR && error instanceof Error) {
354
+ return;
355
+ }
308
356
  withScope(scope => {
309
357
  scope.setTag('error', 'E_USER');
310
358
  scope.setTag('errorCode', code);
311
- scope.setTag('errorMessage', message);
312
359
  captureEvent({
313
- message: metaMessage || message,
314
- extra: error
360
+ message: code ? `User error ${code}` : 'User error',
361
+ extra: {
362
+ code,
363
+ pipeline: error.context
364
+ }
315
365
  });
316
366
  });
317
367
  });
@@ -1,7 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
- import "core-js/modules/web.url.js";
3
- import "core-js/modules/web.url.to-json.js";
4
- import "core-js/modules/web.url-search-params.js";
5
1
  import { router, ACTION_REPLACE } from '@virtuous/conductor';
6
2
  import flushTab from '@shopgate/pwa-core/commands/flushTab';
7
3
  import openPage from '@shopgate/pwa-core/commands/openPage';
@@ -1,8 +1,3 @@
1
- import "core-js/modules/es.array.includes.js";
2
- import "core-js/modules/es.string.replace.js";
3
- import "core-js/modules/web.url.js";
4
- import "core-js/modules/web.url.to-json.js";
5
- import "core-js/modules/web.url-search-params.js";
6
1
  import queryString from 'query-string';
7
2
  import { router, ACTION_POP, ACTION_PUSH, ACTION_REPLACE, ACTION_RESET } from '@virtuous/conductor';
8
3
  import Route from '@virtuous/conductor/Route';