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

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';
@@ -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,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,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.3",
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.3",
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,6 +1,6 @@
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, captureMessage, 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';
@@ -12,8 +12,6 @@ import { env } from "../helpers/environment";
12
12
  import { transformGeneralPipelineError, getDisplayErrorMessage } from "./helpers/pipeline";
13
13
  import { historyPop } from "../actions/router";
14
14
  import showModal from "../actions/modal/showModal";
15
- import { getUserData } from "../selectors/user";
16
- import { userDidUpdate$ } from "../streams/user";
17
15
  import { clientInformationDidUpdate$ } from "../streams/client";
18
16
  import { appWillInit$, appWillStart$, appDidStart$ } from "../streams/app";
19
17
  import { appError$, pipelineError$ } from "../streams/error";
@@ -24,11 +22,34 @@ import ToastProvider from "../providers/toast";
24
22
  // Generic, translated fallback shown when a backend error carries no code we can map to a message.
25
23
  const GENERIC_ERROR_MESSAGE = 'modal.body_error';
26
24
 
25
+ /**
26
+ * @param {*} error The rejection reason or exception.
27
+ * @returns {boolean} Whether the error was created by the PipelineManager.
28
+ */
29
+ const isPipelineError = error => !!error && typeof error.code === 'string' && typeof error.handled === 'boolean';
30
+
27
31
  /**
28
32
  * App errors subscriptions.
29
33
  * @param {Function} subscribe The subscribe function.
30
34
  */
31
35
  export default subscribe => {
36
+ subscribe(appWillInit$, () => {
37
+ window.addEventListener('unhandledrejection', event => {
38
+ const {
39
+ reason
40
+ } = event;
41
+ if (!isPipelineError(reason)) {
42
+ return;
43
+ }
44
+ event.preventDefault();
45
+ if (env === 'development') {
46
+ 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';
47
+ // eslint-disable-next-line no-console
48
+ console.info(`Pipeline error not handled by the caller: ${reason.code} (${reason.message}). ${hint}; add .catch() if the caller needs to react.`);
49
+ }
50
+ });
51
+ });
52
+
32
53
  /** Set general error transformations */
33
54
  subscribe(appWillStart$, () => {
34
55
  errorManager.setMessage({
@@ -160,16 +181,30 @@ export default subscribe => {
160
181
  return;
161
182
  }
162
183
  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
184
+ [Severity.Fatal]: 'fatal',
185
+ [Severity.Error]: 'error',
186
+ [Severity.Critical]: 'fatal',
187
+ [Severity.Warning]: 'warning',
188
+ [Severity.Info]: 'info',
189
+ [Severity.Debug]: 'debug'
169
190
  };
170
191
  const ignoredDefaultBreadcrumbs = ['console', 'fetch', 'xhr', 'ui.click'];
171
- let trackedSeverities = Object.getOwnPropertySymbols(severityMap).map(s => severityMap[s]);
172
- const minSeverityIndex = trackedSeverities.indexOf(level);
192
+
193
+ /**
194
+ * @param {string} url An absolute or relative URL of the app.
195
+ * @returns {string} The URL with its route path replaced by the route pattern, e.g.
196
+ * `/orders/:orderId`, and without query string and hash.
197
+ */
198
+ const redactUrl = url => {
199
+ if (typeof url !== 'string') {
200
+ return url;
201
+ }
202
+ const [path] = url.split(/[?#]/);
203
+ const [, base, routePath] = /^(.*?index\.html)(.*)$/.exec(path) || /^([a-z][a-z\d+.-]*:\/\/[^/]+)(.*)$/i.exec(path) || [null, '', path];
204
+ return `${base}${router.findPattern(routePath || '/') || '/(unknown route)'}`;
205
+ };
206
+ let trackedSeverities = Object.values(severityMap);
207
+ const minSeverityIndex = Object.keys(severityMap).indexOf(level);
173
208
  if (minSeverityIndex > -1) {
174
209
  trackedSeverities = trackedSeverities.slice(0, minSeverityIndex + 1);
175
210
  }
@@ -183,39 +218,60 @@ export default subscribe => {
183
218
  release: pckVersion,
184
219
  attachStacktrace: true,
185
220
  sampleRate,
221
+ ignoreErrors: [/play\(\) failed because the user didn't interact/],
186
222
  beforeBreadcrumb(breadcrumb) {
187
223
  if (ignoredDefaultBreadcrumbs.includes(breadcrumb.category)) {
188
224
  return null;
189
225
  }
226
+ if (breadcrumb.category === 'navigation' && breadcrumb.data) {
227
+ return {
228
+ ...breadcrumb,
229
+ data: {
230
+ from: redactUrl(breadcrumb.data.from),
231
+ to: redactUrl(breadcrumb.data.to)
232
+ }
233
+ };
234
+ }
190
235
  return breadcrumb;
191
236
  },
192
- beforeSend(event) {
237
+ beforeSend(event, hint) {
193
238
  if (event.level && !trackedSeverities.includes(event.level)) {
194
239
  return null;
195
240
  }
196
- // eslint-disable-next-line no-param-reassign
197
- event.extra = {
198
- ...(event.extra || {}),
199
- routerStack: getRouterStack(getState()).slice(-5)
241
+ if (isPipelineError(hint?.originalException)) {
242
+ return null;
243
+ }
244
+ return {
245
+ ...event,
246
+ request: event.request && {
247
+ ...event.request,
248
+ url: redactUrl(event.request.url),
249
+ headers: event.request.headers && {
250
+ ...event.request.headers,
251
+ Referer: redactUrl(event.request.headers.Referer)
252
+ }
253
+ },
254
+ extra: {
255
+ ...(event.extra || {}),
256
+ routerStack: getRouterStack(getState()).slice(-5).map(({
257
+ pattern
258
+ }) => ({
259
+ pattern
260
+ }))
261
+ }
200
262
  };
201
- return event;
202
263
  }
203
264
  });
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);
265
+ setTags({
266
+ marketId: appConfig.marketId,
267
+ appId: appConfig.appId,
268
+ pwaVersion: pckVersion,
269
+ theme: themeName,
270
+ language: appConfig.language,
271
+ isWebsite: hasWebBridge(),
272
+ isReactNativeApp: isAvailable(),
273
+ merchantCode: appConfig.omniMerchantCode
213
274
  });
214
- if (window) {
215
- window.onerror = (message, source, lineno, colno, error) => {
216
- captureException(error);
217
- };
218
- }
219
275
  emitter.addListener(SOURCE_TRACKING, error => {
220
276
  withScope(scope => {
221
277
  if (error.context) {
@@ -226,8 +282,8 @@ export default subscribe => {
226
282
  });
227
283
  emitter.addListener(SOURCE_CONSOLE, args => {
228
284
  withScope(scope => {
229
- scope.setLevel(SentrySeverity.Error);
230
- scope.setExtra('error', args);
285
+ scope.setLevel('error');
286
+ scope.setExtra('error', args.map(arg => arg instanceof Error ? arg.message : String(arg)));
231
287
  captureMessage('Console error');
232
288
  });
233
289
  });
@@ -238,20 +294,7 @@ export default subscribe => {
238
294
  addBreadcrumb({
239
295
  category: 'redux',
240
296
  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);
297
+ level: 'info'
255
298
  });
256
299
  });
257
300
 
@@ -261,20 +304,18 @@ export default subscribe => {
261
304
  }) => {
262
305
  const {
263
306
  appVersion,
264
- libVersion,
265
- deviceId
307
+ libVersion
266
308
  } = action.data;
267
- configureScope(scope => {
268
- scope.setTag('appVersion', appVersion);
269
- scope.setTag('libVersion', libVersion);
270
- scope.setTag('deviceId', deviceId);
309
+ setTags({
310
+ appVersion,
311
+ libVersion
271
312
  });
272
313
  });
273
314
 
274
315
  // Add app start event for debugging
275
316
  subscribe(appDidStart$, () => {
276
317
  withScope(scope => {
277
- scope.setLevel(SentrySeverity.Debug);
318
+ scope.setLevel('debug');
278
319
  captureMessage('App did start');
279
320
  });
280
321
  });
@@ -299,19 +340,17 @@ export default subscribe => {
299
340
  error = {}
300
341
  } = action;
301
342
  const {
302
- code,
303
- message,
304
- meta: {
305
- message: metaMessage
306
- } = {}
343
+ code
307
344
  } = error;
308
345
  withScope(scope => {
309
346
  scope.setTag('error', 'E_USER');
310
347
  scope.setTag('errorCode', code);
311
- scope.setTag('errorMessage', message);
312
348
  captureEvent({
313
- message: metaMessage || message,
314
- extra: error
349
+ message: code ? `User error ${code}` : 'User error',
350
+ extra: {
351
+ code,
352
+ pipeline: error.context
353
+ }
315
354
  });
316
355
  });
317
356
  });
@@ -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';