@shopgate/engage 7.30.1-beta.4 → 7.30.1-beta.5

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.
@@ -18,15 +18,15 @@ import GeolocationRequest from "../classes/GeolocationRequest";
18
18
  * @return { Function } A redux thunk.
19
19
  */
20
20
  const getGeolocation = (options = {}) => async dispatch => {
21
- const result = await dispatch(grantGeolocationPermissions({
21
+ const granted = await dispatch(grantGeolocationPermissions({
22
22
  resolveWithData: true,
23
23
  ...options
24
24
  }));
25
- if (result?.data) {
25
+ if (typeof granted === 'object') {
26
26
  // Other than the app, within browsers we might already have a geolocation.
27
- return Promise.resolve(result.data);
27
+ return Promise.resolve(granted);
28
28
  }
29
- if (!result.success) {
29
+ if (!granted) {
30
30
  const error = new Error('Geolocation permissions not granted.');
31
31
  error.code = GEOLOCATION_ERROR_DENIED;
32
32
  throw error;
@@ -7,31 +7,15 @@ import grantPermissions from "./grantPermissions";
7
7
  * The action returns a promise which resolves with a boolean value, that indicates the state.
8
8
  * @param {Object} options Action options.
9
9
  * @param {Object} [options.meta={}] Additional meta data used for opt-in tracking actions
10
- * @param {Object} [options.modal={}] Options for the settings modal.
11
- * @param {string} options.modal.title Modal title.
12
- * @param {string} options.modal.message Modal message.
13
- * @param {string} options.modal.confirm Label for the confirm button.
14
- * @param {string} options.modal.dismiss Label for the dismiss button.
15
- * @param {Object} options.modal.params Additional parameters for i18n strings.
16
10
  * @return { Function } A redux thunk.
17
11
  */
18
12
  const grantAppTrackingTransparencyPermission = (options = {}) => dispatch => {
19
13
  const {
20
- permissionId,
21
- meta = {},
22
- modal = {},
23
- ...rest
14
+ meta = {}
24
15
  } = options;
25
16
  return dispatch(grantPermissions({
26
17
  permissionId: PERMISSION_ID_APP_TRACKING_TRANSPARENCY,
27
- meta,
28
- modal: {
29
- message: 'permissions.access_denied.trackingMessage',
30
- confirm: 'permissions.access_denied.settings_button',
31
- dismiss: 'modal.dismiss',
32
- ...modal
33
- },
34
- ...rest
18
+ meta
35
19
  }));
36
20
  };
37
21
  export default grantAppTrackingTransparencyPermission;
@@ -18,10 +18,8 @@ import grantPermissions from "./grantPermissions";
18
18
  */
19
19
  const grantCameraPermissions = (options = {}) => dispatch => {
20
20
  const {
21
- permissionId,
22
21
  useSettingsModal = false,
23
- modal = {},
24
- ...rest
22
+ modal = {}
25
23
  } = options;
26
24
  return dispatch(grantPermissions({
27
25
  permissionId: PERMISSION_ID_CAMERA,
@@ -32,8 +30,7 @@ const grantCameraPermissions = (options = {}) => dispatch => {
32
30
  confirm: 'permissions.access_denied.settings_button',
33
31
  dismiss: 'common.close',
34
32
  ...modal
35
- },
36
- ...rest
33
+ }
37
34
  }));
38
35
  };
39
36
  export default grantCameraPermissions;
@@ -1,4 +1,4 @@
1
- import { PERMISSION_ID_LOCATION, PERMISSION_USAGE_ALWAYS } from '@shopgate/engage/core/constants';
1
+ import { PERMISSION_ID_LOCATION } from '@shopgate/engage/core/constants';
2
2
  import { hasWebBridge } from '@shopgate/engage/core';
3
3
  import grantPermissions from "./grantPermissions";
4
4
 
@@ -7,15 +7,12 @@ import grantPermissions from "./grantPermissions";
7
7
  * If not already happened, the user will be prompted to grant permissions.
8
8
  * The action returns a promise which resolves with a boolean value, that indicates the state.
9
9
  * @param {Object} options Action options.
10
- * @param {boolean} [options.requireBackgroundAccess=false] When set to TRUE, the action will
11
- * attempt to request background location permissions,
12
10
  * @param {boolean} [options.useSettingsModal=false] Whether in case of declined permissions a modal
13
11
  * shall be presented, which redirects to the app settings.
14
12
  * @param {boolean} [options.requestPermissions=true] When set to TRUE the logic will not
15
13
  * attempt to request permissions, if they are not granted or determined yet.
16
- * @param {boolean} [options.resolveWithData=true] When set to TRUE the promise will resolve with
17
- * an object containing the permission status and whether the opt-in dialog was shown,
18
- * instead of a boolean value.
14
+ * @param {boolean} [options.resolveWithData=false] If set to TRUE, the promise will resolve
15
+ * with data if available.
19
16
  * @param {Object} [options.modal={}] Options for the settings modal.
20
17
  * @param {string} options.modal.title Modal title.
21
18
  * @param {string} options.modal.message Modal message.
@@ -26,32 +23,23 @@ import grantPermissions from "./grantPermissions";
26
23
  */
27
24
  const grantGeolocationPermissions = (options = {}) => dispatch => {
28
25
  const {
29
- permissionId,
30
26
  useSettingsModal = false,
31
27
  modal = {},
32
28
  requestPermissions = true,
33
- resolveWithData = false,
34
- requireBackgroundAccess = false,
35
- ...rest
29
+ resolveWithData = false
36
30
  } = options;
37
31
  return dispatch(grantPermissions({
38
32
  permissionId: PERMISSION_ID_LOCATION,
39
- ...(requireBackgroundAccess ? {
40
- permissionOptions: {
41
- usage: PERMISSION_USAGE_ALWAYS
42
- }
43
- } : {}),
44
33
  requestPermissions,
45
34
  resolveWithData,
46
35
  useSettingsModal,
47
36
  modal: {
48
37
  title: null,
49
- message: `permissions.access_denied.${requireBackgroundAccess ? 'geolocationMessageBackground' : 'geolocation_message'}`,
38
+ message: 'permissions.access_denied.geolocation_message',
50
39
  confirm: !hasWebBridge() ? 'permissions.access_denied.settings_button' : null,
51
40
  dismiss: 'modal.dismiss',
52
41
  ...modal
53
- },
54
- ...rest
42
+ }
55
43
  }));
56
44
  };
57
45
  export default grantGeolocationPermissions;
@@ -1,33 +1,22 @@
1
1
  import event from '@shopgate/pwa-core/classes/Event';
2
2
  import { openAppSettings } from '@shopgate/engage/core/commands';
3
3
  import { showModal } from '@shopgate/engage/core/actions';
4
- import { getIsAndroidApp } from '@shopgate/engage/core/selectors';
5
- import { PERMISSION_ID_LOCATION, PERMISSION_ID_BACKGROUND_LOCATION, PERMISSION_ID_CAMERA, PERMISSION_STATUS_DENIED, PERMISSION_STATUS_GRANTED, PERMISSION_STATUS_NOT_DETERMINED, PERMISSION_STATUS_NOT_SUPPORTED, PERMISSION_USAGE_ALWAYS, APP_EVENT_APPLICATION_WILL_ENTER_FOREGROUND, availablePermissionsIds } from '@shopgate/engage/core/constants';
4
+ import { PERMISSION_STATUS_DENIED, PERMISSION_STATUS_GRANTED, PERMISSION_STATUS_NOT_DETERMINED, PERMISSION_STATUS_NOT_SUPPORTED, APP_EVENT_APPLICATION_WILL_ENTER_FOREGROUND, availablePermissionsIds } from '@shopgate/engage/core/constants';
6
5
  import { logger, hasSGJavaScriptBridge, hasWebBridge } from '@shopgate/engage/core/helpers';
7
6
  import { softOptInShown, softOptInSelected, hardOptInShown, hardOptInSelected } from "../action-creators";
8
7
  import requestAppPermission from "./requestAppPermission";
9
8
  import requestAppPermissionStatus from "./requestAppPermissionStatus";
10
9
  import { createMockedPermissions } from "../helpers/appPermissions";
11
10
 
12
- // List of permissions that need a timing based user interaction check on Android, since we can't
13
- // reliably handle this in the native layer.
14
- const ANDROID_PERMISSIONS_WITH_USER_INTERACTION_CHECK = [PERMISSION_ID_LOCATION, PERMISSION_ID_BACKGROUND_LOCATION, PERMISSION_ID_CAMERA];
15
-
16
11
  /**
17
12
  * Determines the current state of a specific permission for an app feature. If not already
18
13
  * happened, the user will be prompted to grant permissions.
19
- *
20
14
  * The action returns a promise which resolves with a boolean value, that indicates the state.
21
- *
22
- * When the "resolveWithData" option is set to TRUE, the promise will resolve with an object
23
- * containing the permission status and additional data, instead of a boolean value.
24
15
  * @param {Object} options Action options.
25
16
  * @param {string} options.permissionId The id of the permission to request.
26
- * @param {Object} [options.permissionOptions={}] Additional options for the permission request.
27
17
  * @param {boolean} [options.useSettingsModal=false] Whether in case of declined permissions a modal
28
18
  * shall be presented, which redirects to the app settings.
29
- * @param {boolean} [options.useRationaleModal=false] Whether a rational modal should be shown that
30
- * describes why the permission is needed before requesting the permission.
19
+ * @param {boolean} [options.useRationaleModal=false] Whether a rational modal should be shown
31
20
  * @param {Object} [options.rationaleModal={}] Options for the rationale modal.
32
21
  * @param {string} options.rationaleModal.title Modal title.
33
22
  * @param {string} options.rationaleModal.message Modal message.
@@ -42,16 +31,14 @@ const ANDROID_PERMISSIONS_WITH_USER_INTERACTION_CHECK = [PERMISSION_ID_LOCATION,
42
31
  * @param {Object} options.modal.params Additional parameters for i18n strings.
43
32
  * @param {boolean} [options.requestPermissions=true] If set to TRUE no permissions will be
44
33
  * requested if not already granted,
45
- * @param {boolean} [options.resolveWithData=true] When set to TRUE the promise will resolve with
46
- * an object containing the permission status and whether the opt-in dialog was shown,
47
- * instead of a boolean value.
34
+ * @param {boolean} [options.resolveWithData=false] If set to TRUE the Promise will resolve with
35
+ * data if available (e.g. geolocation).
48
36
  * @param {Object} [options.meta={}] Additional meta data used for opt-in tracking actions
49
37
  * @return { Function } A redux thunk.
50
38
  */
51
- const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(async resolve => {
39
+ const grantPermissions = (options = {}) => dispatch => new Promise(async resolve => {
52
40
  const {
53
41
  permissionId,
54
- permissionOptions,
55
42
  useSettingsModal = false,
56
43
  useRationaleModal = false,
57
44
  rationaleModal: rationaleModalOptions = {},
@@ -61,8 +48,6 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
61
48
  meta = {}
62
49
  } = options;
63
50
  let dispatchMock;
64
- let optInRequested = false;
65
- const isAndroidApp = getIsAndroidApp(getState());
66
51
  if (!hasSGJavaScriptBridge() || hasWebBridge()) {
67
52
  /**
68
53
  * The fallbackStatus will be used at browsers that don't support the permissions API. By
@@ -75,54 +60,30 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
75
60
  }
76
61
  if (!availablePermissionsIds.includes(permissionId)) {
77
62
  logger.error('grandPermissions: %s is no valid permission id', permissionId);
78
- resolve(resolveWithData ? {
79
- success: false,
80
- optInRequested
81
- } : false);
63
+ resolve(false);
82
64
  return;
83
65
  }
84
66
  let status;
85
- let appPermissionOptions;
86
67
  let data;
87
68
 
88
69
  // Check the current status of the camera permissions.
89
70
  ({
90
- status,
91
- options: appPermissionOptions
71
+ status
92
72
  } = await dispatch(requestAppPermissionStatus({
93
73
  permissionId,
94
74
  dispatchMock
95
75
  })));
96
76
 
97
- // When the location permission is requested for "always" usage and the permissions where already
98
- // granted for "whenInUse" we need to trigger the permission request again to get extended
99
- // permissions.
100
- const upgradeLocationPermission = permissionId === PERMISSION_ID_LOCATION && permissionOptions?.usage === PERMISSION_USAGE_ALWAYS && status === PERMISSION_STATUS_GRANTED;
101
-
102
77
  // Stop the process when the permission type is not supported.
103
78
  if (status === PERMISSION_STATUS_NOT_SUPPORTED) {
104
- resolve(resolveWithData ? {
105
- success: false,
106
- optInRequested,
107
- status,
108
- ...(appPermissionOptions ? {
109
- options: appPermissionOptions
110
- } : {})
111
- } : false);
79
+ resolve(false);
112
80
  return;
113
81
  }
114
82
 
115
83
  // The user never seen the permissions dialog yet, or temporary denied the permissions (Android).
116
- if (status === PERMISSION_STATUS_NOT_DETERMINED || upgradeLocationPermission) {
84
+ if (status === PERMISSION_STATUS_NOT_DETERMINED) {
117
85
  if (!requestPermissions) {
118
- resolve(resolveWithData ? {
119
- success: false,
120
- optInRequested,
121
- status,
122
- ...(appPermissionOptions ? {
123
- options: appPermissionOptions
124
- } : {})
125
- } : false);
86
+ resolve(false);
126
87
  return;
127
88
  }
128
89
  if (useRationaleModal) {
@@ -140,14 +101,7 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
140
101
  meta
141
102
  }));
142
103
  if (requestAllowed === false) {
143
- resolve(resolveWithData ? {
144
- success: false,
145
- optInRequested,
146
- status,
147
- ...(appPermissionOptions ? {
148
- options: appPermissionOptions
149
- } : {})
150
- } : false);
104
+ resolve(false);
151
105
  return;
152
106
  }
153
107
  }
@@ -155,42 +109,15 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
155
109
  permissionId,
156
110
  meta
157
111
  }));
158
- const tsBeforeRequest = Date.now();
159
- let nativeRequestDuration;
160
112
 
161
113
  // Trigger the native permissions dialog.
162
114
  ({
163
115
  status,
164
- data,
165
- options: appPermissionOptions,
166
- duration: nativeRequestDuration
116
+ data
167
117
  } = await dispatch(requestAppPermission({
168
118
  permissionId,
169
- dispatchMock,
170
- ...(permissionOptions ? {
171
- options: permissionOptions
172
- } : {})
119
+ dispatchMock
173
120
  })));
174
- let wasUserInteraction = true;
175
-
176
- /**
177
- * On iOS it's not possible to get "notDetermined" status for "always" location permissions.
178
- * So we might run into this decision branch of the code for when "always" permissions are
179
- * requested, even if we don't know if a dialog will be shown.
180
- * We can only guess that the user interacted with the dialog and if we might need to show
181
- * the settings modal.
182
- *
183
- * Additionally, at some permissions requests on Android we can't prevent that the
184
- * getPermissions request returns "notDetermined" since there is an user option to ask for
185
- * permission acceptance every time its needed. So we also need to try a user interaction guess.
186
- */
187
- if (upgradeLocationPermission || isAndroidApp && ANDROID_PERMISSIONS_WITH_USER_INTERACTION_CHECK.includes(permissionId)) {
188
- // When available, use the duration of the native request as heuristic for user interaction.
189
- // It's more accurate, since it won't include the time that communication with the app takes.
190
- const requestDuration = typeof nativeRequestDuration === 'number' ? nativeRequestDuration : Date.now() - tsBeforeRequest;
191
- wasUserInteraction = requestDuration > 1000;
192
- }
193
- optInRequested = wasUserInteraction;
194
121
  dispatch(hardOptInSelected({
195
122
  permissionId,
196
123
  status,
@@ -199,45 +126,19 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
199
126
 
200
127
  // The user denied the permissions within the native dialog.
201
128
  if ([PERMISSION_STATUS_DENIED, PERMISSION_STATUS_NOT_DETERMINED].includes(status)) {
202
- if (wasUserInteraction) {
203
- resolve(resolveWithData ? {
204
- success: false,
205
- optInRequested,
206
- status,
207
- ...(appPermissionOptions ? {
208
- options: appPermissionOptions
209
- } : {})
210
- } : false);
211
- return;
212
- }
129
+ resolve(false);
130
+ return;
213
131
  }
214
132
  }
215
133
  if (status === PERMISSION_STATUS_GRANTED) {
216
- resolve(resolveWithData ? {
217
- success: true,
218
- optInRequested,
219
- status,
220
- ...(appPermissionOptions ? {
221
- options: appPermissionOptions
222
- } : {}),
223
- ...(data !== undefined ? {
224
- data
225
- } : {})
226
- } : true);
134
+ resolve(resolveWithData && data ? data : true);
227
135
  return;
228
136
  }
229
137
 
230
138
  // The user permanently denied the permissions before.
231
139
  if (status === PERMISSION_STATUS_DENIED) {
232
140
  if (!useSettingsModal) {
233
- resolve(resolveWithData ? {
234
- success: false,
235
- optInRequested,
236
- status,
237
- ...(appPermissionOptions ? {
238
- options: appPermissionOptions
239
- } : {})
240
- } : false);
141
+ resolve(false);
241
142
  return;
242
143
  }
243
144
 
@@ -252,14 +153,7 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
252
153
 
253
154
  // The user just closed the modal.
254
155
  if (!openSettings) {
255
- resolve(resolveWithData ? {
256
- success: false,
257
- optInRequested,
258
- status,
259
- ...(appPermissionOptions ? {
260
- options: appPermissionOptions
261
- } : {})
262
- } : false);
156
+ resolve(false);
263
157
  return;
264
158
  }
265
159
 
@@ -274,14 +168,6 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
274
168
  permissionId
275
169
  })));
276
170
  resolve(status === PERMISSION_STATUS_GRANTED);
277
- resolve(resolveWithData ? {
278
- success: status === PERMISSION_STATUS_GRANTED,
279
- optInRequested,
280
- status,
281
- ...(appPermissionOptions ? {
282
- options: appPermissionOptions
283
- } : {})
284
- } : status === PERMISSION_STATUS_GRANTED);
285
171
  };
286
172
 
287
173
  /**
@@ -294,15 +180,6 @@ const grantPermissions = (options = {}) => (dispatch, getState) => new Promise(a
294
180
  setTimeout(() => {
295
181
  openAppSettings();
296
182
  }, 0);
297
- return;
298
183
  }
299
- resolve(resolveWithData ? {
300
- success: false,
301
- optInRequested,
302
- status,
303
- ...(appPermissionOptions ? {
304
- options: appPermissionOptions
305
- } : {})
306
- } : false);
307
184
  });
308
185
  export default grantPermissions;
@@ -22,20 +22,15 @@ import grantPermissions from "./grantPermissions";
22
22
  * @param {string} options.modal.dismiss Label for the dismiss button.
23
23
  * @param {Object} options.modal.params Additional parameters for i18n strings.
24
24
  * @param {Object} [options.meta={}] Additional meta data used for opt-in tracking actions
25
- * @param {boolean} [options.resolveWithData=true] When set to TRUE the promise will resolve with
26
- * an object containing the permission status and whether the opt-in dialog was shown,
27
- * instead of a boolean value.
28
25
  * @return { Function } A redux thunk.
29
26
  */
30
27
  const grantPushPermissions = (options = {}) => dispatch => {
31
28
  const {
32
- permissionId,
33
29
  useSettingsModal = true,
34
30
  useRationaleModal = false,
35
31
  modal = {},
36
32
  rationaleModal = {},
37
- meta = {},
38
- ...rest
33
+ meta = {}
39
34
  } = options;
40
35
  return dispatch(grantPermissions({
41
36
  permissionId: PERMISSION_ID_PUSH,
@@ -45,12 +40,10 @@ const grantPushPermissions = (options = {}) => dispatch => {
45
40
  title: null,
46
41
  message: 'permissions.access_denied.push_message',
47
42
  confirm: 'permissions.access_denied.settings_button',
48
- dismiss: 'modal.dismiss',
49
43
  ...modal
50
44
  },
51
45
  rationaleModal,
52
- meta,
53
- ...rest
46
+ meta
54
47
  }));
55
48
  };
56
49
  export default grantPushPermissions;
@@ -8,8 +8,6 @@ import { appPermissionStatusReceived } from "../action-creators";
8
8
  * Additionally it propagates the received status via the APP_PERMISSION_STATUS_RECEIVED action.
9
9
  * @param {Object} params The action params
10
10
  * @param {string} params.permissionId The desired app permission id
11
- * @param {Object} [params.options={}] Additional options for the permission request.
12
- * E.g. the usage object for geolocation permissions.
13
11
  * @param {Function} [params.dispatchMock=null] An optional mock for the request dispatch logic.
14
12
  * Usually used when PWA is running inside a browser and app command logic can be simulated via
15
13
  * browser APIs like for geolocation access.
@@ -17,7 +15,6 @@ import { appPermissionStatusReceived } from "../action-creators";
17
15
  */
18
16
  const requestAppPermission = ({
19
17
  permissionId,
20
- options: requestOptions,
21
18
  dispatchMock: dispatchMockParam
22
19
  }) => async dispatch => {
23
20
  let dispatchMock = dispatchMockParam;
@@ -29,15 +26,11 @@ const requestAppPermission = ({
29
26
  const [{
30
27
  status,
31
28
  options,
32
- data,
33
- duration
29
+ data
34
30
  } = {
35
31
  status: PERMISSION_STATUS_NOT_SUPPORTED
36
32
  }] = (await requestAppPermissions([{
37
- permissionId,
38
- ...(requestOptions ? {
39
- options: requestOptions
40
- } : {})
33
+ permissionId
41
34
  }], dispatchMock)) ?? [];
42
35
  dispatch(appPermissionStatusReceived({
43
36
  permissionId,
@@ -46,9 +39,7 @@ const requestAppPermission = ({
46
39
  }));
47
40
  return {
48
41
  status,
49
- options,
50
- data,
51
- duration
42
+ data
52
43
  };
53
44
  };
54
45
  export default requestAppPermission;
@@ -36,8 +36,7 @@ const requestAppPermissionStatus = ({
36
36
  options
37
37
  }));
38
38
  return {
39
- status,
40
- options
39
+ status
41
40
  };
42
41
  };
43
42
  export default requestAppPermissionStatus;
@@ -45,27 +45,4 @@ export const EMPTY_OBJECT = Object.freeze({});
45
45
  * Helper constant that can be used as a return value in Redux selectors to prevent
46
46
  * unnecessary re-renders.
47
47
  */
48
- export const EMPTY_ARRAY = Object.freeze([]);
49
-
50
- /**
51
- * Virtual route to trigger a location permission request to the app.
52
- */
53
- export const PERMISSION_REQUEST_ROUTE_LOCATION = '/permissions/location';
54
- /**
55
- * Virtual route to trigger a background location permission request to the app.
56
- */
57
- export const PERMISSION_REQUEST_ROUTE_LOCATION_BACKGROUND = '/permissions/location_background';
58
- /**
59
- * Virtual route to trigger a push permission request to the app.
60
- */
61
- export const PERMISSION_REQUEST_ROUTE_PUSH = '/permissions/push';
62
- /**
63
- * Virtual route to trigger an app tracking transparency permission request to the app.
64
- */
65
- export const PERMISSION_REQUEST_ROUTE_TRACKING = '/permissions/tracking';
66
-
67
- /**
68
- * Virtual route to trigger a camera permission request to the app.
69
- */
70
- export const PERMISSION_REQUEST_ROUTE_CAMERA = '/permissions/camera';
71
- export const PERMISSION_REQUEST_ROUTES = [PERMISSION_REQUEST_ROUTE_LOCATION, PERMISSION_REQUEST_ROUTE_LOCATION_BACKGROUND, PERMISSION_REQUEST_ROUTE_PUSH, PERMISSION_REQUEST_ROUTE_TRACKING, PERMISSION_REQUEST_ROUTE_CAMERA];
48
+ export const EMPTY_ARRAY = Object.freeze([]);
@@ -29,7 +29,7 @@ export * from '@shopgate/pwa-core/helpers/version';
29
29
  export * from '@shopgate/pwa-common/helpers/data';
30
30
  export * from '@shopgate/pwa-common/helpers/date';
31
31
  export * from '@shopgate/pwa-common/helpers/dom';
32
- export { env, isDev, isProd, isRemote, isStaging, isWindows } from '@shopgate/pwa-common/helpers/environment';
32
+ export { env, isDev, isProd, isRemote, isStaging, isLinux, isWindows } from '@shopgate/pwa-common/helpers/environment';
33
33
  export { default as decodeHTML } from '@shopgate/pwa-common/helpers/html/decodeHTML';
34
34
  export * from '@shopgate/pwa-common/helpers/html/handleDOM';
35
35
  export { default as parseHTML } from '@shopgate/pwa-common/helpers/html/parseHTML';
@@ -1,15 +1,14 @@
1
1
  import { appWillStart$ } from '@shopgate/engage/core/streams';
2
- import { configuration, redirects } from '@shopgate/engage/core/collections';
2
+ import { configuration } from '@shopgate/engage/core/collections';
3
3
  import { hasNewServices, appSupportsAndroidEdgeToEdge, updateAndroidNavigationBarColor } from '@shopgate/engage/core/helpers';
4
4
  import { CONFIGURATION_COLLECTION_KEY_BASE_URL } from '@shopgate/engage/core/constants';
5
5
  import { appConfig } from '@shopgate/engage';
6
6
  import { reloadApp$ } from "../streams";
7
7
  import { reloadApp } from "../action-creators";
8
- import { PERMISSION_REQUEST_ROUTE_LOCATION, PERMISSION_REQUEST_ROUTE_LOCATION_BACKGROUND, PERMISSION_REQUEST_ROUTE_PUSH, PERMISSION_REQUEST_ROUTE_TRACKING, PERMISSION_REQUEST_ROUTE_CAMERA } from "../constants";
9
- import { permissionRouteRedirectHandler } from "../router/permissionRouteRedirectHandler";
10
8
  const {
11
9
  androidNavigationBarDefaultColor
12
10
  } = appConfig;
11
+
13
12
  /**
14
13
  * App subscriptions
15
14
  * @param {Function} subscribe The subscribe function
@@ -30,26 +29,6 @@ export default function app(subscribe) {
30
29
  color: androidNavigationBarDefaultColor
31
30
  });
32
31
  }
33
-
34
- // Register redirects for app permission request routes
35
- redirects.set(PERMISSION_REQUEST_ROUTE_LOCATION, permissionRouteRedirectHandler, {
36
- showLoading: false
37
- });
38
- redirects.set(PERMISSION_REQUEST_ROUTE_LOCATION_BACKGROUND, permissionRouteRedirectHandler, {
39
- showLoading: false
40
- });
41
- redirects.set(PERMISSION_REQUEST_ROUTE_PUSH, permissionRouteRedirectHandler, {
42
- showLoading: false
43
- });
44
- redirects.set(PERMISSION_REQUEST_ROUTE_TRACKING, permissionRouteRedirectHandler, {
45
- showLoading: false
46
- });
47
- redirects.set(PERMISSION_REQUEST_ROUTE_CAMERA, permissionRouteRedirectHandler, {
48
- showLoading: false
49
- });
50
- redirects.set(PERMISSION_REQUEST_ROUTE_CAMERA, permissionRouteRedirectHandler, {
51
- showLoading: false
52
- });
53
32
  });
54
33
  subscribe(reloadApp$, () => {
55
34
  // A deployed app can only be reloaded on the base url. So we first set this as current route
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopgate/engage",
3
- "version": "7.30.1-beta.4",
3
+ "version": "7.30.1-beta.5",
4
4
  "description": "Shopgate's ENGAGE library.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Shopgate <support@shopgate.com>",
@@ -17,12 +17,12 @@
17
17
  "dependencies": {
18
18
  "@emotion/react": "^11.14.0",
19
19
  "@shopgate/native-modules": "1.0.0-beta.25",
20
- "@shopgate/pwa-common": "7.30.1-beta.4",
21
- "@shopgate/pwa-common-commerce": "7.30.1-beta.4",
22
- "@shopgate/pwa-core": "7.30.1-beta.4",
23
- "@shopgate/pwa-ui-ios": "7.30.1-beta.4",
24
- "@shopgate/pwa-ui-material": "7.30.1-beta.4",
25
- "@shopgate/pwa-ui-shared": "7.30.1-beta.4",
20
+ "@shopgate/pwa-common": "7.30.1-beta.5",
21
+ "@shopgate/pwa-common-commerce": "7.30.1-beta.5",
22
+ "@shopgate/pwa-core": "7.30.1-beta.5",
23
+ "@shopgate/pwa-ui-ios": "7.30.1-beta.5",
24
+ "@shopgate/pwa-ui-material": "7.30.1-beta.5",
25
+ "@shopgate/pwa-ui-shared": "7.30.1-beta.5",
26
26
  "@stripe/react-stripe-js": "^1.16.5",
27
27
  "@stripe/stripe-js": "^1.44.1",
28
28
  "@virtuous/conductor": "~2.5.0",
@@ -9,12 +9,14 @@ import { getCategoriesById } from "./selectors";
9
9
  /**
10
10
  * @typedef {Object} CategoryListWidgetConfig
11
11
  * @property {string} category The parent category ID to display categories for.
12
- * @property {string} [sort] The sort order for categories
12
+ * @property {'relevance' | 'nameAsc' | 'nameDesc'} [sort] The sort order for categories
13
13
  * @property {boolean} [showImages] Whether to display images for categories.
14
14
  * @property {boolean} [showHeadline] Whether to show the headline.
15
15
  * @property {Object} [headline] The headline to be displayed.
16
16
  */
17
17
 
18
+ const EMPTY_ARRAY = [];
19
+
18
20
  /**
19
21
  * @typedef {ReturnType< typeof import('@shopgate/engage/page/hooks')
20
22
  * .useWidget<CategoryListWidgetConfig> >} UseWidgetReturnType
@@ -32,12 +34,11 @@ export const useCategoryListWidget = () => {
32
34
  const dispatch = useDispatch();
33
35
  const {
34
36
  category,
35
- sort,
37
+ sort = 'relevance',
36
38
  showImages,
37
39
  showHeadline = false,
38
40
  headline
39
41
  } = config;
40
- const sortCC = useMemo(() => _camelCase(sort), [sort]);
41
42
 
42
43
  // Get the parent category object from the selected category
43
44
  const parentCategory = useSelector(state => category ? getCategory(state, {
@@ -50,16 +51,19 @@ export const useCategoryListWidget = () => {
50
51
  }) : null);
51
52
  const sortedCategories = useMemo(() => {
52
53
  if (!categories) {
53
- return [];
54
+ return EMPTY_ARRAY;
54
55
  }
55
- if (sortCC === 'relevance') {
56
- return categories;
56
+
57
+ /** @type {CategoryListWidgetConfig['sort']} */
58
+ const sortCC = _camelCase(sort);
59
+ if (sortCC === 'nameAsc' || sortCC === 'nameDesc') {
60
+ const dir = sortCC === 'nameAsc' ? 1 : -1;
61
+ return [].concat(categories).sort((a, b) => a.name.localeCompare(b.name, undefined, {
62
+ sensitivity: 'base'
63
+ }) * dir);
57
64
  }
58
- const isAsc = sortCC === 'nameAsc';
59
- return [].concat(categories).sort((a, b) => a.name.localeCompare(b.name, undefined, {
60
- sensitivity: 'base'
61
- }) * (isAsc ? 1 : -1));
62
- }, [categories, sortCC]);
65
+ return categories;
66
+ }, [categories, sort]);
63
67
  useEffect(() => {
64
68
  dispatch(fetchCategoryOrRootCategories(category));
65
69
  }, [category, dispatch]);
@@ -1,5 +1,5 @@
1
1
  import { css } from 'glamor';
2
- import { useScrollContainer, hasWebBridge, isIOSTheme, isWindows, isDev } from '@shopgate/engage/core/helpers';
2
+ import { useScrollContainer, hasWebBridge, isIOSTheme } from '@shopgate/engage/core/helpers';
3
3
  import { themeConfig } from '@shopgate/engage';
4
4
  const {
5
5
  typography
@@ -28,8 +28,8 @@ css.global('html', {
28
28
  minHeight: '100%'
29
29
  });
30
30
 
31
- // Include Roboto font on Windows in dev mode on iOS theme, so that developers see nice fonts.
32
- const fontSuffix = isDev && iosThemeActive && isWindows && !(typography.family ?? '').includes('Roboto') ? ', Roboto' : '';
31
+ // Include Roboto font as a fallback to the iOS theme when other fonts are not available
32
+ const fontSuffix = iosThemeActive && !(typography.family || '').includes('Roboto') ? ', Roboto' : '';
33
33
  css.global('body', {
34
34
  font: `${typography.rootSize}px/${typography.lineHeight} ${typography.family}${fontSuffix}`,
35
35
  overflow: 'auto',
@@ -1,8 +1,9 @@
1
1
  import { appDidStart$ } from '@shopgate/engage/core/streams';
2
- import { historyReset, historyPop } from '@shopgate/engage/core/actions';
2
+ import { appSupportsCookieConsent } from '@shopgate/engage/core/helpers';
3
+ import { grantAppTrackingTransparencyPermission, historyReset, historyPop } from '@shopgate/engage/core/actions';
3
4
  import { softOptInShown } from '@shopgate/engage/core/action-creators';
4
5
  import { handleCookieConsent, showCookieConsentModal, hideCookieConsentModal } from "../action-creators";
5
- import { getIsCookieConsentHandled, getAreComfortCookiesAccepted, getAreStatisticsCookiesAccepted, getCookieConsentTrackingMeta, getIsCookieConsentActivated } from "../selectors/cookieConsent";
6
+ import { getIsCookieConsentHandled, getAreComfortCookiesAccepted, getAreStatisticsCookiesAccepted, getCookieConsentTrackingMeta } from "../selectors/cookieConsent";
6
7
  import { cookieConsentInitializedByUserInternal$, privacySettingsConfirmedWithoutChangeInternal$, cookieConsentModalShouldToggleInternal$ } from "../streams/cookieConsent";
7
8
 
8
9
  /**
@@ -30,7 +31,12 @@ export default function cookieConsent(subscribe) {
30
31
  comfortCookiesAccepted,
31
32
  statisticsCookiesAccepted
32
33
  }));
33
- } else if (getIsCookieConsentActivated(state)) {
34
+ if (appSupportsCookieConsent() && (comfortCookiesAccepted || statisticsCookiesAccepted)) {
35
+ await dispatch(grantAppTrackingTransparencyPermission({
36
+ meta: trackingMeta
37
+ }));
38
+ }
39
+ } else {
34
40
  // if merchant has activated cookie feature but user has not chosen cookies yet:
35
41
  // show cookie consent modal to make user choose them
36
42
  dispatch(softOptInShown({
@@ -1,24 +0,0 @@
1
- import type { UnknownAction } from 'redux';
2
- import type { ThunkAction } from 'redux-thunk';
3
-
4
- export type { GrantPermissionsOptions, GrantPermissionsResult } from './grantPermissions';
5
- export type { GrantPermissionsOptions, GrantPermissionsResult };
6
-
7
- /**
8
- * Determines the current state of the app tracking transparency permissions.
9
- * If not already happened, the user will be prompted to grant permissions.
10
- *
11
- * The action returns a promise which resolves with a boolean value, that indicates the state.
12
- *
13
- * When the "resolveWithData" option is set to TRUE, the promise will resolve with an object
14
- * containing the permission status and additional data, instead of a boolean value.
15
- */
16
- declare function grantAppTrackingTransparencyPermission<State = unknown>(
17
- options: Omit<GrantPermissionsOptions, 'permissionId'> & { resolveWithData: true }
18
- ): ThunkAction<Promise<GrantPermissionsResult>, State, unknown, UnknownAction>;
19
-
20
- declare function grantAppTrackingTransparencyPermission<State = unknown>(
21
- options?: Omit<GrantPermissionsOptions, 'permissionId'> & { resolveWithData?: false | undefined }
22
- ): ThunkAction<Promise<boolean>, State, unknown, UnknownAction>;
23
-
24
- export default grantAppTrackingTransparencyPermission;
@@ -1,24 +0,0 @@
1
- import type { UnknownAction } from 'redux';
2
- import type { ThunkAction } from 'redux-thunk';
3
-
4
- export type { GrantPermissionsOptions, GrantPermissionsResult } from './grantPermissions';
5
- export type { GrantPermissionsOptions, GrantPermissionsResult };
6
-
7
- /**
8
- * Determines the current state of the camera permissions.
9
- * If not already happened, the user will be prompted to grant permissions.
10
- *
11
- * The action returns a promise which resolves with a boolean value, that indicates the state.
12
- *
13
- * When the "resolveWithData" option is set to TRUE, the promise will resolve with an object
14
- * containing the permission status and additional data, instead of a boolean value.
15
- */
16
- declare function grantCameraPermissions<State = unknown>(
17
- options: Omit<GrantPermissionsOptions, 'permissionId'> & { resolveWithData: true }
18
- ): ThunkAction<Promise<GrantPermissionsResult>, State, unknown, UnknownAction>;
19
-
20
- declare function grantCameraPermissions<State = unknown>(
21
- options?: Omit<GrantPermissionsOptions, 'permissionId'> & { resolveWithData?: false | undefined }
22
- ): ThunkAction<Promise<boolean>, State, unknown, UnknownAction>;
23
-
24
- export default grantCameraPermissions;
@@ -1,36 +0,0 @@
1
- import type { UnknownAction } from 'redux';
2
- import type { ThunkAction } from 'redux-thunk';
3
-
4
- import type { GrantPermissionsOptions, GrantPermissionsResult } from './grantPermissions';
5
- export type { GrantPermissionsOptions, GrantPermissionsResult };
6
-
7
- type GeolocationOptions = {
8
- /**
9
- * Whether to request background location access.
10
- *
11
- * **Note:** Since background location access will cause extended reviews by Google and Apple,
12
- * this feature needs to be explicitly enabled by Shopgate.
13
- */
14
- requireBackgroundAccess?: boolean;
15
- }
16
-
17
- type GeolocationGrantOptions = Omit<GrantPermissionsOptions, 'permissionId'> & GeolocationOptions;
18
-
19
- /**
20
- * Determines the current state of the geolocation permissions.
21
- * If not already happened, the user will be prompted to grant permissions.
22
- *
23
- * The action returns a promise which resolves with a boolean value, that indicates the state.
24
- *
25
- * When the "resolveWithData" option is set to TRUE, the promise will resolve with an object
26
- * containing the permission status and additional data, instead of a boolean value.
27
- */
28
- declare function grantGeolocationPermissions<State = unknown>(
29
- options: GeolocationGrantOptions & { resolveWithData: true }
30
- ): ThunkAction<Promise<GrantPermissionsResult>, State, unknown, UnknownAction>;
31
-
32
- declare function grantGeolocationPermissions<State = unknown>(
33
- options?: GeolocationGrantOptions & { resolveWithData?: false | undefined }
34
- ): ThunkAction<Promise<boolean>, State, unknown, UnknownAction>;
35
-
36
- export default grantGeolocationPermissions;
@@ -1,142 +0,0 @@
1
- import type { UnknownAction } from 'redux';
2
- import type { ThunkAction } from 'redux-thunk';
3
-
4
- type PermissionStatus =
5
- | typeof import('@shopgate/engage/core/constants').PERMISSION_STATUS_NOT_DETERMINED
6
- | typeof import('@shopgate/engage/core/constants').PERMISSION_STATUS_DENIED
7
- | typeof import('@shopgate/engage/core/constants').PERMISSION_STATUS_GRANTED
8
- | typeof import('@shopgate/engage/core/constants').PERMISSION_STATUS_NOT_SUPPORTED
9
-
10
- type PermissionId =
11
- | typeof import('@shopgate/engage/core/constants').PERMISSION_ID_LOCATION
12
- | typeof import('@shopgate/engage/core/constants').PERMISSION_ID_BACKGROUND_LOCATION
13
- | typeof import('@shopgate/engage/core/constants').PERMISSION_ID_CAMERA
14
- | typeof import('@shopgate/engage/core/constants').PERMISSION_ID_PUSH
15
- | typeof import('@shopgate/engage/core/constants').PERMISSION_ID_APP_TRACKING_TRANSPARENCY
16
-
17
- type PermissionUsage =
18
- | typeof import('@shopgate/engage/core/constants').PERMISSION_USAGE_ALWAYS
19
- | typeof import('@shopgate/engage/core/constants').PERMISSION_USAGE_WHEN_IN_USE
20
-
21
- export interface PermissionMeta {
22
- permission: 'push' | 'location' | 'backgroundLocation' | 'camera' | 'tracking' ;
23
- context: string;
24
- contextCounter?: number;
25
- usesSoftPushOptIn?: boolean;
26
- usesSoftTrackingOptIn?: boolean;
27
- }
28
-
29
- export interface PermissionModalOptions {
30
- /**
31
- * Modal title.
32
- */
33
- title?: string | null;
34
- /**
35
- * Modal message.
36
- */
37
- message?: string;
38
- /**
39
- * Label for the confirm button.
40
- */
41
- confirm?: string;
42
- /**
43
- * Label for the dismiss button.
44
- */
45
- dismiss?: string;
46
- /**
47
- * Additional parameters for i18n strings.
48
- */
49
- params?: Record<string, unknown>;
50
- }
51
-
52
- export interface GrantPermissionsOptions {
53
- /**
54
- * The id of the permission to request.
55
- */
56
- permissionId: PermissionId;
57
-
58
- /**
59
- * Additional options for the permission request (forwarded to native).
60
- */
61
- permissionOptions?: Record<string, unknown>;
62
-
63
- /**
64
- * Whether in case of declined permissions a modal shall redirect to app settings.
65
- * Options for the settings modal can be set via `modal` property.
66
- */
67
- useSettingsModal?: boolean;
68
-
69
- /**
70
- * Whether a rationale modal should be shown before requesting the permission.
71
- * Options for the rationale modal can be set via `rationaleModal` property.
72
- */
73
- useRationaleModal?: boolean;
74
-
75
- /**
76
- * Options for the rationale modal.
77
- */
78
- rationaleModal?: PermissionModalOptions;
79
-
80
- /**
81
- * Options for the settings modal.
82
- */
83
- modal?: PermissionModalOptions;
84
-
85
- /**
86
- * If TRUE, no permissions will be requested if not already granted.
87
- * (I.e. only check status.)
88
- */
89
- requestPermissions?: boolean;
90
-
91
- /**
92
- * If TRUE, resolves with a data object instead of a boolean.
93
- * Note: your JS default currently sets `resolveWithData = false`.
94
- */
95
- resolveWithData?: boolean;
96
-
97
- /**
98
- * Meta data used for opt-in tracking actions.
99
- */
100
- meta?: PermissionMeta & Record<string, unknown>;
101
- }
102
-
103
- export interface GrantPermissionsResult {
104
- /**
105
- * Whether the permission is granted after the action has been processed.
106
- */
107
- success: boolean;
108
- /**
109
- * The current status of the permission after the action has been processed.
110
- */
111
- status?: PermissionStatus;
112
- /**
113
- * Whether the user was prompted to grant permissions during the process.
114
- */
115
- optInRequested: boolean;
116
- /**
117
- * Additional options for the permission, e.g. usage (forwarded from native).
118
- */
119
- options?: {
120
- usage: PermissionUsage;
121
- };
122
- /**
123
- * Might be set when PWA runs in browser mode. In that cause it can contain resolved
124
- * location data.
125
- */
126
- data?: unknown;
127
- }
128
-
129
- /**
130
- * Determines the current state of a specific permission for an app feature. If not already
131
- * happened, the user will be prompted to grant permissions.
132
- *
133
- * The action returns a promise which resolves with a boolean value, that indicates the state.
134
- *
135
- * When the "resolveWithData" option is set to TRUE, the promise will resolve with an object
136
- * containing the permission status and additional data, instead of a boolean value.
137
- */
138
- declare function grantPermissions<State = unknown>(
139
- options: GrantPermissionsOptions
140
- ): ThunkAction<Promise<GrantPermissionsResult|boolean>, State, unknown, UnknownAction>;
141
-
142
- export default grantPermissions;
@@ -1,24 +0,0 @@
1
- import type { UnknownAction } from 'redux';
2
- import type { ThunkAction } from 'redux-thunk';
3
-
4
- export type { GrantPermissionsOptions, GrantPermissionsResult } from './grantPermissions';
5
- export type { GrantPermissionsOptions, GrantPermissionsResult };
6
-
7
- /**
8
- * Determines the current state of the push permissions.
9
- * If not already happened, the user will be prompted to grant permissions.
10
- *
11
- * The action returns a promise which resolves with a boolean value, that indicates the state.
12
- *
13
- * When the "resolveWithData" option is set to TRUE, the promise will resolve with an object
14
- * containing the permission status and additional data, instead of a boolean value.
15
- */
16
- declare function grantPushPermissions<State = unknown>(
17
- options: Omit<GrantPermissionsOptions, 'permissionId'> & { resolveWithData: true }
18
- ): ThunkAction<Promise<GrantPermissionsResult>, State, unknown, UnknownAction>;
19
-
20
- declare function grantPushPermissions<State = unknown>(
21
- options?: Omit<GrantPermissionsOptions, 'permissionId'> & { resolveWithData?: false | undefined }
22
- ): ThunkAction<Promise<boolean>, State, unknown, UnknownAction>;
23
-
24
- export default grantPushPermissions;
@@ -1,117 +0,0 @@
1
- import { showModal } from '@shopgate/engage/core/actions';
2
- import { logger } from "../helpers";
3
- import { PERMISSION_REQUEST_ROUTE_LOCATION, PERMISSION_REQUEST_ROUTE_LOCATION_BACKGROUND, PERMISSION_REQUEST_ROUTE_PUSH, PERMISSION_REQUEST_ROUTE_TRACKING, PERMISSION_REQUEST_ROUTE_CAMERA, PERMISSION_STATUS_GRANTED } from "../constants";
4
- import { grantPushPermissions, grantAppTrackingTransparencyPermission, grantGeolocationPermissions, grantCameraPermissions } from "../actions";
5
-
6
- /**
7
- * @typedef {import('redux-thunk').ThunkDispatch<
8
- * import('../store').RootState,
9
- * any,
10
- * import('redux').AnyAction
11
- * >} AppDispatch
12
- */
13
-
14
- /**
15
- * @typedef {import('../actions/grantPermissions').GrantPermissionsResult} GrantPermissionsResult
16
- */
17
-
18
- /**
19
- * Handler for redirect collection handlers related to permission requests.
20
- * @param {{ action: any, dispatch: AppDispatch }} params Handler params
21
- */
22
- export const permissionRouteRedirectHandler = async ({
23
- action,
24
- dispatch
25
- }) => {
26
- const {
27
- route: {
28
- pathname
29
- } = {}
30
- } = action;
31
-
32
- /**
33
- * @type {GrantPermissionsResult | undefined}
34
- */
35
- let result;
36
- let grantedMessage;
37
- const metaBase = {
38
- context: 'permissionRoute'
39
- };
40
- switch (pathname) {
41
- case PERMISSION_REQUEST_ROUTE_LOCATION:
42
- {
43
- result = await dispatch(grantGeolocationPermissions({
44
- useSettingsModal: true,
45
- resolveWithData: true,
46
- meta: {
47
- ...metaBase,
48
- permission: 'location'
49
- }
50
- }));
51
- grantedMessage = 'permissions.accessGranted.locationMessage';
52
- break;
53
- }
54
- case PERMISSION_REQUEST_ROUTE_LOCATION_BACKGROUND:
55
- {
56
- result = await dispatch(grantGeolocationPermissions({
57
- useSettingsModal: true,
58
- requireBackgroundAccess: true,
59
- resolveWithData: true,
60
- meta: {
61
- ...metaBase,
62
- permission: 'backgroundLocation'
63
- }
64
- }));
65
- grantedMessage = 'permissions.accessGranted.backgroundLocationMessage';
66
- break;
67
- }
68
- case PERMISSION_REQUEST_ROUTE_PUSH:
69
- {
70
- result = await dispatch(grantPushPermissions({
71
- useSettingsModal: true,
72
- resolveWithData: true,
73
- meta: {
74
- ...metaBase,
75
- permission: 'push'
76
- }
77
- }));
78
- grantedMessage = 'permissions.accessGranted.pushMessage';
79
- break;
80
- }
81
- case PERMISSION_REQUEST_ROUTE_TRACKING:
82
- {
83
- result = await dispatch(grantAppTrackingTransparencyPermission({
84
- useSettingsModal: true,
85
- resolveWithData: true,
86
- meta: {
87
- ...metaBase,
88
- permission: 'tracking'
89
- }
90
- }));
91
- grantedMessage = 'permissions.accessGranted.trackingMessage';
92
- break;
93
- }
94
- case PERMISSION_REQUEST_ROUTE_CAMERA:
95
- {
96
- result = await dispatch(grantCameraPermissions({
97
- useSettingsModal: true,
98
- resolveWithData: true,
99
- meta: {
100
- ...metaBase,
101
- permission: 'camera'
102
- }
103
- }));
104
- grantedMessage = 'permissions.accessGranted.cameraMessage';
105
- break;
106
- }
107
- default:
108
- logger.warn('Unknown permission request route', pathname);
109
- }
110
- if (result?.status === PERMISSION_STATUS_GRANTED && result?.optInRequested === false) {
111
- dispatch(showModal({
112
- message: grantedMessage,
113
- confirm: 'modal.ok',
114
- dismiss: ''
115
- }));
116
- }
117
- };