@shopgate/pwa-tracking 7.30.0-alpha.7 → 7.30.0-alpha.8
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/helpers/index.js +412 -15
- package/package.json +5 -5
- package/selectors/cart.js +30 -5
- package/selectors/category.js +25 -2
- package/selectors/favorites.js +7 -2
- package/selectors/index.js +29 -3
- package/selectors/page.js +46 -8
- package/selectors/product.js +30 -4
- package/selectors/search.js +34 -4
- package/selectors/user.js +31 -2
- package/spec.js +6 -1
- package/streams/app.js +9 -3
- package/streams/cart.js +55 -12
- package/streams/category.js +72 -11
- package/streams/checkout.js +26 -5
- package/streams/page.js +49 -7
- package/streams/pages.js +33 -6
- package/streams/product.js +33 -7
- package/streams/scanner.js +43 -2
- package/streams/search.js +44 -7
- package/streams/user.js +13 -4
- package/subscriptions/cart.js +23 -3
- package/subscriptions/checkout.js +71 -3
- package/subscriptions/deeplinkPush.js +70 -5
- package/subscriptions/favorites.js +39 -6
- package/subscriptions/pages.js +28 -3
- package/subscriptions/product.js +40 -4
- package/subscriptions/scanner.js +94 -3
- package/subscriptions/search.js +17 -2
- package/subscriptions/setup.js +113 -10
- package/subscriptions/user.js +27 -3
package/selectors/page.js
CHANGED
|
@@ -1,27 +1,65 @@
|
|
|
1
|
-
import{createSelector}from'reselect';
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { getCurrentPathname, makeGetRouteParam } from '@shopgate/engage/core/selectors';
|
|
3
|
+
import { isDev } from '@shopgate/engage/core/helpers';
|
|
4
|
+
import { makeGetUnifiedCMSPageData } from '@shopgate/engage/page/selectors';
|
|
5
|
+
import { shopNumber } from '@shopgate/pwa-common/helpers/config';
|
|
6
|
+
|
|
7
|
+
/**
|
|
2
8
|
* The tracking base URL.
|
|
3
9
|
* @type {string}
|
|
4
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
const baseUrl = `https://rapid.shopgate.com${isDev ? '/php/shopgate' : ''}/sg_app_resources`;
|
|
12
|
+
|
|
13
|
+
/**
|
|
5
14
|
* The mapping of page names to tracking page names.
|
|
6
15
|
* @type {Object}
|
|
7
|
-
*/
|
|
16
|
+
*/
|
|
17
|
+
const pageNameMap = {
|
|
18
|
+
'': 'startpage',
|
|
19
|
+
product: 'productDetails',
|
|
20
|
+
item: 'productDetails',
|
|
21
|
+
category: 'productList'
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
8
25
|
* Selects the current tracking URL.
|
|
9
26
|
* @param {Object} state The current state.
|
|
10
27
|
* @returns {string} The URL.
|
|
11
|
-
*/
|
|
28
|
+
*/
|
|
29
|
+
const getTrackingUrl = createSelector(getCurrentPathname, pathname => `${baseUrl}/${shopNumber}${pathname}`);
|
|
30
|
+
|
|
31
|
+
/**
|
|
12
32
|
* Extracts the name of the current path.
|
|
13
33
|
* @param {Object} state The current state.
|
|
14
34
|
* @returns {string} The name.
|
|
15
|
-
*/
|
|
35
|
+
*/
|
|
36
|
+
const getPageName = createSelector(getCurrentPathname, pathname => pathname.split('?')[0].split('/')[1]);
|
|
37
|
+
|
|
38
|
+
/**
|
|
16
39
|
* Selects the tracking page name based on the current page name.
|
|
17
40
|
* @param {Object} state The current state.
|
|
18
41
|
* @returns {string} The page name.
|
|
19
|
-
*/
|
|
42
|
+
*/
|
|
43
|
+
const getPageTrackingName = createSelector(getPageName, pageName => pageNameMap[pageName] || pageName);
|
|
44
|
+
|
|
45
|
+
/**
|
|
20
46
|
* Creates a selector that retrieves a page config for the current route from the store.
|
|
21
47
|
* @param {string} name The name of the desired parameter.
|
|
22
48
|
* @returns {Function}
|
|
23
|
-
*/
|
|
49
|
+
*/
|
|
50
|
+
export const makeGetRoutePageConfig = () => {
|
|
51
|
+
const getPageIdRouteParam = makeGetRouteParam('pageId');
|
|
52
|
+
return createSelector(state => state, getPageIdRouteParam, (state, pageId) => pageId ? makeGetUnifiedCMSPageData({
|
|
53
|
+
slug: pageId
|
|
54
|
+
})(state) : null);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
24
58
|
* Selects the page information.
|
|
25
59
|
* @param {Object} state The current state.
|
|
26
60
|
* @returns {Object} The page information.
|
|
27
|
-
*/
|
|
61
|
+
*/
|
|
62
|
+
export default createSelector(getTrackingUrl, getPageTrackingName, (link, name) => ({
|
|
63
|
+
link,
|
|
64
|
+
name
|
|
65
|
+
}));
|
package/selectors/product.js
CHANGED
|
@@ -1,12 +1,38 @@
|
|
|
1
|
-
import{createSelector}from'reselect';
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { makeGetRouteParam, makeGetRoutePattern } from '@shopgate/engage/core/selectors';
|
|
3
|
+
import { hex2bin } from '@shopgate/engage/core/helpers';
|
|
4
|
+
import { ITEM_PATTERN } from '@shopgate/engage/product/constants';
|
|
5
|
+
import { getBaseProduct, getProduct } from '@shopgate/pwa-common-commerce/product/selectors/product';
|
|
6
|
+
import { formatProductData } from "../helpers";
|
|
7
|
+
|
|
8
|
+
/**
|
|
2
9
|
* Gets the current base product in a formatted way.
|
|
3
10
|
* @param {Object} state The current state.
|
|
4
11
|
* @returns {Object} The formatted selected variant.
|
|
5
|
-
*/
|
|
12
|
+
*/
|
|
13
|
+
export const getBaseProductFormatted = createSelector(getBaseProduct, formatProductData);
|
|
14
|
+
|
|
15
|
+
/**
|
|
6
16
|
* Gets the current product in a formatted way.
|
|
7
17
|
* @param {Object} state The current state.
|
|
8
18
|
* @returns {Object} The formatted selected variant.
|
|
9
|
-
*/
|
|
19
|
+
*/
|
|
20
|
+
export const getProductFormatted = createSelector(getProduct, formatProductData);
|
|
21
|
+
|
|
22
|
+
/**
|
|
10
23
|
* Creates a selector that retrieves a formatted product for the current route.
|
|
11
24
|
* @returns {Function}
|
|
12
|
-
*/
|
|
25
|
+
*/
|
|
26
|
+
export const makeGetRouteProduct = () => {
|
|
27
|
+
const getRoutePattern = makeGetRoutePattern();
|
|
28
|
+
const getProductIdRouteParam = makeGetRouteParam('productId');
|
|
29
|
+
return createSelector(state => state, getRoutePattern, getProductIdRouteParam, (state, pattern, productId) => {
|
|
30
|
+
const decodedProductId = productId ? hex2bin(productId) : null;
|
|
31
|
+
if (pattern === ITEM_PATTERN && decodedProductId) {
|
|
32
|
+
return getProductFormatted(state, {
|
|
33
|
+
productId: decodedProductId
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
});
|
|
38
|
+
};
|
package/selectors/search.js
CHANGED
|
@@ -1,13 +1,43 @@
|
|
|
1
|
-
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { getSortOrder, getSearchPhrase } from '@shopgate/engage/core/selectors';
|
|
3
|
+
import { generateResultHash } from '@shopgate/engage/core/helpers';
|
|
4
|
+
import { getFulfillmentParams } from '@shopgate/pwa-common-commerce/product';
|
|
5
|
+
|
|
6
|
+
/**
|
|
2
7
|
* Selects the container for search results.
|
|
3
8
|
* @param {Object} state The current state.
|
|
4
9
|
* @returns {Object} The search results.
|
|
5
|
-
*/
|
|
10
|
+
*/
|
|
11
|
+
const resultsSelector = state => state.product.resultsByHash;
|
|
12
|
+
|
|
13
|
+
/**
|
|
6
14
|
* Selects the product count for a search result.
|
|
7
15
|
* @param {Object} state The current state.
|
|
8
16
|
* @returns {number} The total product count.
|
|
9
|
-
*/
|
|
17
|
+
*/
|
|
18
|
+
const resultCountSelector = createSelector(getSearchPhrase, getSortOrder, resultsSelector, getFulfillmentParams, (searchPhrase, sort, results, fulfillmentParams) => {
|
|
19
|
+
const hash = searchPhrase && generateResultHash({
|
|
20
|
+
sort,
|
|
21
|
+
searchPhrase,
|
|
22
|
+
...fulfillmentParams
|
|
23
|
+
});
|
|
24
|
+
if (!hash || !results[hash]) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return results[hash].totalResultCount;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
10
31
|
* Selects the search information.
|
|
11
32
|
* @param {Object} state The current state.
|
|
12
33
|
* @returns {Object} The search information.
|
|
13
|
-
*/
|
|
34
|
+
*/
|
|
35
|
+
export default createSelector(getSearchPhrase, resultCountSelector, (query, resultCount) => {
|
|
36
|
+
if (!query && !resultCount) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
query,
|
|
41
|
+
resultCount
|
|
42
|
+
};
|
|
43
|
+
});
|
package/selectors/user.js
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
|
-
|
|
1
|
+
import { createSelector } from 'reselect';
|
|
2
|
+
import { DEFAULT_LOGIN_STRATEGY, isUserLoggedIn, getUserData, makeGetLoginStrategy } from '@shopgate/engage/user';
|
|
3
|
+
|
|
4
|
+
/**
|
|
2
5
|
* Creates a selector that retrieves user tracking data from the store.
|
|
3
6
|
* @param {string} name The name of the desired parameter.
|
|
4
7
|
* @returns {Function}
|
|
5
|
-
*/
|
|
8
|
+
*/
|
|
9
|
+
export function makeGetUser() {
|
|
10
|
+
const getLoginStrategy = makeGetLoginStrategy();
|
|
11
|
+
return createSelector(isUserLoggedIn, getUserData, getLoginStrategy, (isLoggedIn, userData, loginStrategy) => {
|
|
12
|
+
if (!isLoggedIn || !userData) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const {
|
|
16
|
+
isFetching,
|
|
17
|
+
loginType,
|
|
18
|
+
...trackedData
|
|
19
|
+
} = userData || {};
|
|
20
|
+
if (Object.keys(trackedData).length === 0) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
let type = null;
|
|
24
|
+
if (loginStrategy !== DEFAULT_LOGIN_STRATEGY) {
|
|
25
|
+
type = loginStrategy;
|
|
26
|
+
} else if (['userGuest', 'userAccount'].includes(loginType)) {
|
|
27
|
+
type = loginType === 'userGuest' ? 'guest' : 'standard';
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
...trackedData,
|
|
31
|
+
type
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
package/spec.js
CHANGED
package/streams/app.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import{pwaDidAppear$,pwaDidDisappear$}from'@shopgate/pwa-common/streams/app'
|
|
1
|
+
import { pwaDidAppear$, pwaDidDisappear$ } from '@shopgate/pwa-common/streams/app';
|
|
2
|
+
|
|
3
|
+
/**
|
|
2
4
|
* Emits when the PWA visibility changes
|
|
3
|
-
*/
|
|
5
|
+
*/
|
|
6
|
+
export const pwaVisibility$ = pwaDidAppear$.merge(pwaDidDisappear$);
|
|
7
|
+
|
|
8
|
+
/**
|
|
4
9
|
* Export the streams again from here, since e.g. checkout extensions import them.
|
|
5
10
|
* @deprecated
|
|
6
|
-
*/
|
|
11
|
+
*/
|
|
12
|
+
export { pwaDidAppear$, pwaDidDisappear$ };
|
package/streams/cart.js
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
|
-
import
|
|
1
|
+
import 'rxjs/add/operator/switchMap';
|
|
2
|
+
import 'rxjs/add/observable/of';
|
|
3
|
+
import 'rxjs/add/observable/defer';
|
|
4
|
+
import { Observable } from 'rxjs/Observable';
|
|
5
|
+
import { productsAdded$ as originalProductsAdded$ } from '@shopgate/engage/cart/streams';
|
|
6
|
+
import { getProductById, fetchProductsById } from '@shopgate/engage/product';
|
|
7
|
+
|
|
8
|
+
/**
|
|
2
9
|
* Emits when a product was added to the cart and product data is available
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
*/
|
|
11
|
+
export const productsAdded$ = originalProductsAdded$.switchMap(input => {
|
|
12
|
+
const {
|
|
13
|
+
getState,
|
|
14
|
+
action,
|
|
15
|
+
dispatch
|
|
16
|
+
} = input;
|
|
17
|
+
const {
|
|
18
|
+
products = []
|
|
19
|
+
} = action;
|
|
20
|
+
|
|
21
|
+
// Collect product ids from the add-to-cart action
|
|
22
|
+
const productIds = products.map(({
|
|
23
|
+
productId
|
|
24
|
+
}) => productId).filter(Boolean);
|
|
25
|
+
|
|
26
|
+
// Check if we have product data for all productIds
|
|
27
|
+
const productDataAvailable = productIds.every(productId => getProductById(getState(), {
|
|
28
|
+
productId
|
|
29
|
+
}));
|
|
30
|
+
if (productDataAvailable) {
|
|
31
|
+
// All product data for the tracking event is available - handover action data to subscribers
|
|
32
|
+
return Observable.of(input);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Incomplete product data can be caused by an add-to-cart push message. In that case the
|
|
37
|
+
* product was never fetched before due to visiting PDP. So we need to fetch missing product
|
|
38
|
+
* data first.
|
|
39
|
+
* Subscribers will receive the original action when product data comes available.
|
|
40
|
+
*/
|
|
41
|
+
return Observable.defer(async () => {
|
|
42
|
+
let result = {
|
|
43
|
+
products: []
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
result = await dispatch(fetchProductsById(productIds));
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// nothing to do here - default result will be returned
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
}).first()
|
|
52
|
+
// do not proceed when request didn't return any products (no suitable tracking data)
|
|
53
|
+
.filter(data => Array.isArray(data.products) && data.products.length > 0)
|
|
54
|
+
// handover original action to subscribers
|
|
55
|
+
.switchMap(() => Observable.of(input));
|
|
56
|
+
});
|
package/streams/category.js
CHANGED
|
@@ -1,19 +1,80 @@
|
|
|
1
|
-
import'rxjs/add/operator/switchMap';
|
|
1
|
+
import 'rxjs/add/operator/switchMap';
|
|
2
|
+
import 'rxjs/add/observable/of';
|
|
3
|
+
import { Observable } from 'rxjs/Observable';
|
|
4
|
+
import { routeDidEnter$, pwaDidAppear$ } from '@shopgate/engage/core/streams';
|
|
5
|
+
import { hex2bin } from '@shopgate/engage/core/helpers';
|
|
6
|
+
import { ROOT_CATEGORY_PATTERN, CATEGORY_PATTERN } from '@shopgate/engage/category/constants';
|
|
7
|
+
import { getProductsResult } from '@shopgate/pwa-common-commerce/product/selectors/product';
|
|
8
|
+
import { receivedRootCategories$ } from '@shopgate/engage/category/streams';
|
|
9
|
+
import { getRootCategories } from '@shopgate/engage/category/selectors';
|
|
10
|
+
import { getIsAppWebViewVisible } from '@shopgate/engage/core/selectors';
|
|
11
|
+
import { productsReceived$ } from "./product";
|
|
12
|
+
|
|
13
|
+
/**
|
|
2
14
|
* Emits when the root category was entered.
|
|
3
|
-
*/
|
|
15
|
+
*/
|
|
16
|
+
const rootCategoryDidEnter$ = routeDidEnter$.filter(({
|
|
17
|
+
action
|
|
18
|
+
}) => action.route.pattern === ROOT_CATEGORY_PATTERN);
|
|
19
|
+
|
|
20
|
+
/**
|
|
4
21
|
* Emits when a regular category was entered.
|
|
5
|
-
*/
|
|
22
|
+
*/
|
|
23
|
+
const categoryDidEnter$ = routeDidEnter$.filter(({
|
|
24
|
+
action
|
|
25
|
+
}) => action.route.pattern === CATEGORY_PATTERN);
|
|
26
|
+
|
|
27
|
+
/**
|
|
6
28
|
* Emits when the root category data has been received.
|
|
7
|
-
*/
|
|
29
|
+
*/
|
|
30
|
+
const rootCategoryLoaded$ = rootCategoryDidEnter$.switchMap(() => receivedRootCategories$);
|
|
31
|
+
|
|
32
|
+
/**
|
|
8
33
|
* Emits when a root category's data is already available.
|
|
9
|
-
*/
|
|
34
|
+
*/
|
|
35
|
+
const rootCategoryPreloaded$ = rootCategoryDidEnter$.filter(({
|
|
36
|
+
getState
|
|
37
|
+
}) => {
|
|
38
|
+
const rootCategories = getRootCategories(getState());
|
|
39
|
+
return rootCategories ? !!rootCategories.length : false;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
10
43
|
* Emits when a category's data is available.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
44
|
+
*/
|
|
45
|
+
const categoryDataLoaded$ = categoryDidEnter$
|
|
46
|
+
// Do not track while PWA webview is in the background
|
|
47
|
+
.filter(({
|
|
48
|
+
getState
|
|
49
|
+
}) => getIsAppWebViewVisible(getState())).switchMap(data => {
|
|
50
|
+
const {
|
|
51
|
+
action,
|
|
52
|
+
getState
|
|
53
|
+
} = data;
|
|
54
|
+
const {
|
|
55
|
+
categoryId
|
|
56
|
+
} = action.route.params;
|
|
57
|
+
|
|
58
|
+
// Check if products for the current route are already available within Redux.
|
|
59
|
+
const productsLoaded = getProductsResult(getState(), {
|
|
60
|
+
categoryId: hex2bin(categoryId)
|
|
61
|
+
}).totalProductCount !== null;
|
|
62
|
+
if (!productsLoaded) {
|
|
63
|
+
// Wait for incoming products if they are not available yet.
|
|
64
|
+
return productsReceived$.first();
|
|
65
|
+
}
|
|
66
|
+
return Observable.of(data);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
15
70
|
* Emits when the category route comes active again after a legacy page was active.
|
|
16
|
-
*/
|
|
71
|
+
*/
|
|
72
|
+
const categoryRouteReappeared$ = pwaDidAppear$.filter(({
|
|
73
|
+
action
|
|
74
|
+
}) => [CATEGORY_PATTERN, ROOT_CATEGORY_PATTERN].includes(action.route.pattern));
|
|
75
|
+
|
|
76
|
+
/**
|
|
17
77
|
* Emits when a category or root category is ready to be tracked,
|
|
18
78
|
* considering loaded or preloaded data.
|
|
19
|
-
*/
|
|
79
|
+
*/
|
|
80
|
+
export const categoryIsReady$ = categoryDataLoaded$.merge(rootCategoryLoaded$, rootCategoryPreloaded$, categoryRouteReappeared$);
|
package/streams/checkout.js
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
|
-
import{ACTION_REPLACE,ACTION_PUSH}from'@virtuous/conductor';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { ACTION_REPLACE, ACTION_PUSH } from '@virtuous/conductor';
|
|
2
|
+
import { navigate$ } from '@shopgate/pwa-common/streams/router';
|
|
3
|
+
import { isUserLoggedIn } from '@shopgate/pwa-common/selectors/user';
|
|
4
|
+
import { CHECKOUT_PATH } from '@shopgate/pwa-common/constants/RoutePaths';
|
|
5
|
+
const actionsWl = [ACTION_PUSH, ACTION_REPLACE];
|
|
6
|
+
export const checkoutDidEnter$ = navigate$.filter(({
|
|
7
|
+
action,
|
|
8
|
+
getState
|
|
9
|
+
}) => {
|
|
10
|
+
if (action.params.pathname !== CHECKOUT_PATH) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const userLoggedIn = isUserLoggedIn(getState());
|
|
14
|
+
const navigateAction = action.params.action;
|
|
15
|
+
|
|
16
|
+
// When not logged in users try to navigate to the checkout, they are redirected to the login.
|
|
17
|
+
if (!userLoggedIn && navigateAction === ACTION_PUSH) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A checkout route can be pushed when a logged in user opens the checkout. It can also replace
|
|
23
|
+
* the current route when a user is redirected from the login form after a successful login.
|
|
24
|
+
*/
|
|
25
|
+
return actionsWl.includes(navigateAction);
|
|
26
|
+
});
|
package/streams/page.js
CHANGED
|
@@ -1,10 +1,52 @@
|
|
|
1
|
-
import'rxjs/add/operator/switchMap';
|
|
1
|
+
import 'rxjs/add/operator/switchMap';
|
|
2
|
+
import 'rxjs/add/observable/of';
|
|
3
|
+
import { Observable } from 'rxjs/Observable';
|
|
4
|
+
import { RECEIVE_PAGE_CONFIG } from '@shopgate/engage/core/constants';
|
|
5
|
+
import { main$, routeDidEnter$, pwaDidAppear$ } from '@shopgate/engage/core/streams';
|
|
6
|
+
import { getIsAppWebViewVisible } from '@shopgate/engage/core/selectors';
|
|
7
|
+
import { PAGE_PATTERN, RECEIVE_PAGE_CONFIG_V2 } from '@shopgate/engage/page/constants';
|
|
8
|
+
import { makeGetUnifiedCMSPageData } from '@shopgate/engage/page/selectors';
|
|
9
|
+
|
|
10
|
+
/**
|
|
2
11
|
* Emits when a "page" was entered.
|
|
3
|
-
*/
|
|
12
|
+
*/
|
|
13
|
+
const pageDidEnter$ = routeDidEnter$.filter(({
|
|
14
|
+
action
|
|
15
|
+
}) => action.route.pattern === PAGE_PATTERN);
|
|
16
|
+
|
|
17
|
+
/**
|
|
4
18
|
* Emits when the page route comes active again after a legacy page was active.
|
|
5
|
-
*/
|
|
19
|
+
*/
|
|
20
|
+
const pageRouteReappeared$ = pwaDidAppear$.filter(({
|
|
21
|
+
action
|
|
22
|
+
}) => action.route.pattern === PAGE_PATTERN);
|
|
23
|
+
const pageConfigReceived$ = main$.filter(({
|
|
24
|
+
action
|
|
25
|
+
}) => [RECEIVE_PAGE_CONFIG, RECEIVE_PAGE_CONFIG_V2].includes(action.type));
|
|
26
|
+
|
|
27
|
+
/**
|
|
6
28
|
* Emits when a "page" was opened, and its config is available.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
29
|
+
*/
|
|
30
|
+
export const pageIsReady$ = pageDidEnter$
|
|
31
|
+
// Do not track while PWA webview is in the background
|
|
32
|
+
.filter(({
|
|
33
|
+
getState
|
|
34
|
+
}) => getIsAppWebViewVisible(getState())).switchMap(data => {
|
|
35
|
+
const {
|
|
36
|
+
action,
|
|
37
|
+
getState
|
|
38
|
+
} = data;
|
|
39
|
+
const {
|
|
40
|
+
pageId
|
|
41
|
+
} = action.route.params;
|
|
42
|
+
|
|
43
|
+
// Check if the page config for the current route is already available within Redux.
|
|
44
|
+
const pageConfig = makeGetUnifiedCMSPageData({
|
|
45
|
+
slug: pageId
|
|
46
|
+
})(getState());
|
|
47
|
+
if (!pageConfig || pageConfig.isFetching) {
|
|
48
|
+
// Wait for incoming page data if it's not available yet.
|
|
49
|
+
return pageConfigReceived$.first().switchMap(() => Observable.of(data));
|
|
50
|
+
}
|
|
51
|
+
return Observable.of(data);
|
|
52
|
+
}).merge(pageRouteReappeared$);
|
package/streams/pages.js
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
|
-
|
|
1
|
+
import { appWillStart$, pwaDidAppear$, pwaDidDisappear$, routeDidEnter$ } from '@shopgate/engage/core/streams';
|
|
2
|
+
import { APP_WILL_START, PWA_DID_APPEAR } from '@shopgate/engage/core/constants';
|
|
3
|
+
import { SEARCH_PATH, SEARCH_FILTER_PATTERN } from '@shopgate/engage/search/constants';
|
|
4
|
+
import { ROOT_CATEGORY_PATTERN, CATEGORY_PATTERN, CATEGORY_FILTER_PATTERN } from '@shopgate/engage/category/constants';
|
|
5
|
+
import { ITEM_PATTERN, ITEM_GALLERY_PATTERN, ITEM_REVIEWS_PATTERN, ITEM_WRITE_REVIEW_PATTERN } from '@shopgate/engage/product/constants';
|
|
6
|
+
import { PAGE_PATTERN } from '@shopgate/engage/page/constants';
|
|
7
|
+
import { pwaVisibility$ } from "./app";
|
|
8
|
+
import { checkoutDidEnter$ } from "./checkout";
|
|
9
|
+
|
|
10
|
+
/**
|
|
2
11
|
* A blacklist of paths that should be tracked within their individual subscriptions.
|
|
3
12
|
* @type {Array}
|
|
4
|
-
*/
|
|
5
|
-
|
|
13
|
+
*/
|
|
14
|
+
export const blacklistedPatterns = [ROOT_CATEGORY_PATTERN, CATEGORY_PATTERN, ITEM_PATTERN, SEARCH_PATH,
|
|
15
|
+
// Patterns for routes which are not supported for tracking at the moment.
|
|
16
|
+
CATEGORY_FILTER_PATTERN, SEARCH_FILTER_PATTERN, ITEM_GALLERY_PATTERN, ITEM_REVIEWS_PATTERN, ITEM_WRITE_REVIEW_PATTERN, PAGE_PATTERN];
|
|
17
|
+
const latestAppActions$ = appWillStart$.merge(pwaVisibility$, checkoutDidEnter$);
|
|
18
|
+
|
|
19
|
+
/**
|
|
6
20
|
* Route did enter and PWA is visible and route is not blacklisted
|
|
7
21
|
* @type {Rx.Observable<*[]>}
|
|
8
|
-
*/
|
|
22
|
+
*/
|
|
23
|
+
const routeDidEnterForVisiblePwa$ = routeDidEnter$.withLatestFrom(latestAppActions$).filter(([, {
|
|
24
|
+
action: {
|
|
25
|
+
type
|
|
26
|
+
}
|
|
27
|
+
}]) => type === APP_WILL_START || type === PWA_DID_APPEAR).map(([routeDidEnter]) => routeDidEnter);
|
|
28
|
+
|
|
29
|
+
/**
|
|
9
30
|
* PWA reappear after disappear
|
|
10
31
|
* @type {Rx.Observable<any>}
|
|
11
|
-
*/
|
|
32
|
+
*/
|
|
33
|
+
const pwaDidAppearAfterDisappear = pwaDidDisappear$.switchMap(() => pwaDidAppear$.first());
|
|
34
|
+
|
|
35
|
+
/**
|
|
12
36
|
* Emits when one of the tracked paths is entered or pwa reappear
|
|
13
|
-
*/
|
|
37
|
+
*/
|
|
38
|
+
export const pagesAreReady$ = routeDidEnterForVisiblePwa$.merge(pwaDidAppearAfterDisappear).filter(({
|
|
39
|
+
action
|
|
40
|
+
}) => !blacklistedPatterns.find(pattern => action.route.pattern === pattern));
|
package/streams/product.js
CHANGED
|
@@ -1,11 +1,37 @@
|
|
|
1
|
-
import'rxjs/add/operator/switchMap';
|
|
1
|
+
import 'rxjs/add/operator/switchMap';
|
|
2
|
+
import { main$, routeWillEnter$, pwaDidAppear$ } from '@shopgate/engage/core/streams';
|
|
3
|
+
import { getIsAppWebViewVisible } from '@shopgate/engage/core/selectors';
|
|
4
|
+
import { receivedVisibleProduct$ } from '@shopgate/pwa-common-commerce/product/streams';
|
|
5
|
+
import { RECEIVE_PRODUCTS, ITEM_PATTERN } from '@shopgate/pwa-common-commerce/product/constants';
|
|
6
|
+
|
|
7
|
+
/**
|
|
2
8
|
* Emits when product results has been received.
|
|
3
|
-
*/
|
|
9
|
+
*/
|
|
10
|
+
export const productsReceived$ = main$.filter(({
|
|
11
|
+
action
|
|
12
|
+
}) => action.type === RECEIVE_PRODUCTS);
|
|
13
|
+
|
|
14
|
+
/**
|
|
4
15
|
* Emits when the category route comes active again after a legacy page was active.
|
|
5
|
-
*/
|
|
16
|
+
*/
|
|
17
|
+
export const productRouteReappeared$ = pwaDidAppear$.filter(({
|
|
18
|
+
action
|
|
19
|
+
}) => action.route.pattern === ITEM_PATTERN);
|
|
20
|
+
|
|
21
|
+
/**
|
|
6
22
|
* Emits when a product page was initially opened.
|
|
7
|
-
*/
|
|
23
|
+
*/
|
|
24
|
+
export const productWillEnter$ = routeWillEnter$.filter(({
|
|
25
|
+
action
|
|
26
|
+
}) => action.route.pattern === ITEM_PATTERN);
|
|
27
|
+
|
|
28
|
+
/**
|
|
8
29
|
* Emits when a product page was initially opened and its data is present.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
|
|
30
|
+
*/
|
|
31
|
+
export const productIsReady$ = productWillEnter$
|
|
32
|
+
// Do not track while PWA webview is in the background
|
|
33
|
+
.filter(({
|
|
34
|
+
getState
|
|
35
|
+
}) => getIsAppWebViewVisible(getState()))
|
|
36
|
+
// Take care that the stream only emits when underlying streams emit within the correct order.
|
|
37
|
+
.switchMap(() => receivedVisibleProduct$.first()).merge(productRouteReappeared$);
|
package/streams/scanner.js
CHANGED
|
@@ -1,2 +1,43 @@
|
|
|
1
|
-
import{main$}from'@shopgate/engage/core/streams';
|
|
2
|
-
|
|
1
|
+
import { main$ } from '@shopgate/engage/core/streams';
|
|
2
|
+
import { scannerDidEnter$ } from '@shopgate/engage/scanner/streams';
|
|
3
|
+
import { SUCCESS_HANDLE_SCANNER, ERROR_HANDLE_SCANNER } from '@shopgate/engage/scanner/constants';
|
|
4
|
+
import { SCANNER_SCOPE_DEFAULT, SCANNER_TYPE_BARCODE } from '@shopgate/engage/core/constants';
|
|
5
|
+
|
|
6
|
+
/** @type {Observable} */
|
|
7
|
+
export const scanActivated$ = scannerDidEnter$.filter(({
|
|
8
|
+
action
|
|
9
|
+
}) => {
|
|
10
|
+
const {
|
|
11
|
+
route: {
|
|
12
|
+
query: {
|
|
13
|
+
type = SCANNER_TYPE_BARCODE,
|
|
14
|
+
scope = SCANNER_SCOPE_DEFAULT
|
|
15
|
+
} = {}
|
|
16
|
+
}
|
|
17
|
+
} = action;
|
|
18
|
+
|
|
19
|
+
// The stream in supposed to emit only for the Shopgate default scanner.
|
|
20
|
+
return type === SCANNER_TYPE_BARCODE && scope === SCANNER_SCOPE_DEFAULT;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/** @type {Observable} */
|
|
24
|
+
export const scanSuccess$ = main$.filter(({
|
|
25
|
+
action
|
|
26
|
+
}) => {
|
|
27
|
+
const {
|
|
28
|
+
type,
|
|
29
|
+
scope
|
|
30
|
+
} = action;
|
|
31
|
+
return type === SUCCESS_HANDLE_SCANNER && scope === SCANNER_SCOPE_DEFAULT;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** @type {Observable} */
|
|
35
|
+
export const scanFail$ = main$.filter(({
|
|
36
|
+
action
|
|
37
|
+
}) => {
|
|
38
|
+
const {
|
|
39
|
+
type,
|
|
40
|
+
scope
|
|
41
|
+
} = action;
|
|
42
|
+
return type === ERROR_HANDLE_SCANNER && scope === SCANNER_SCOPE_DEFAULT;
|
|
43
|
+
});
|