@magento/experience-platform-connector 1.0.0-alpha.1

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.
Files changed (56) hide show
  1. package/intercept.js +24 -0
  2. package/package.json +30 -0
  3. package/src/__tests__/__snapshots__/utils.spec.js.snap +137 -0
  4. package/src/__tests__/utils.spec.js +52 -0
  5. package/src/config.js +31 -0
  6. package/src/handleEvent.js +9 -0
  7. package/src/handlers/__tests__/__mocks__/cartAddItem.js +36 -0
  8. package/src/handlers/__tests__/__mocks__/cartPageView.js +880 -0
  9. package/src/handlers/__tests__/__mocks__/categoryPageView.js +9 -0
  10. package/src/handlers/__tests__/__mocks__/checkoutPageView.js +138 -0
  11. package/src/handlers/__tests__/__mocks__/completeCheckout.js +156 -0
  12. package/src/handlers/__tests__/__mocks__/createAccount.js +9 -0
  13. package/src/handlers/__tests__/__mocks__/editAccount.js +9 -0
  14. package/src/handlers/__tests__/__mocks__/miniCartView.js +846 -0
  15. package/src/handlers/__tests__/__mocks__/orderConfirmationPageView.js +67 -0
  16. package/src/handlers/__tests__/__mocks__/pageView.js +7 -0
  17. package/src/handlers/__tests__/__mocks__/placeOrderButtonClicked.js +161 -0
  18. package/src/handlers/__tests__/__mocks__/productPageView.js +15 -0
  19. package/src/handlers/__tests__/__mocks__/searchPageRequest.js +22 -0
  20. package/src/handlers/__tests__/__mocks__/searchRequestSent.js +34 -0
  21. package/src/handlers/__tests__/__mocks__/searchResponseReceived.js +106 -0
  22. package/src/handlers/__tests__/__snapshots__/shoppingMiniCartView.spec.js.snap +137 -0
  23. package/src/handlers/__tests__/addToCart.spec.js +73 -0
  24. package/src/handlers/__tests__/categoryPageView.spec.js +55 -0
  25. package/src/handlers/__tests__/completeCheckout.spec.js +70 -0
  26. package/src/handlers/__tests__/createAccount.spec.js +43 -0
  27. package/src/handlers/__tests__/editAccount.spec.js +43 -0
  28. package/src/handlers/__tests__/pageView.spec.js +46 -0
  29. package/src/handlers/__tests__/placeOrder.spec.js +55 -0
  30. package/src/handlers/__tests__/productPageView.spec.js +64 -0
  31. package/src/handlers/__tests__/searchRequestSent.spec.js +119 -0
  32. package/src/handlers/__tests__/searchResponseReceived.spec.js +143 -0
  33. package/src/handlers/__tests__/shoppingCartPageView.spec.js +198 -0
  34. package/src/handlers/__tests__/shoppingMiniCartView.spec.js +36 -0
  35. package/src/handlers/__tests__/signIn.spec.js +73 -0
  36. package/src/handlers/__tests__/startCheckout.spec.js +193 -0
  37. package/src/handlers/addToCart.js +60 -0
  38. package/src/handlers/categoryPageView.js +34 -0
  39. package/src/handlers/completeCheckout.js +49 -0
  40. package/src/handlers/createAccount.js +25 -0
  41. package/src/handlers/editAccount.js +25 -0
  42. package/src/handlers/pageView.js +26 -0
  43. package/src/handlers/placeOrder.js +32 -0
  44. package/src/handlers/productPageView.js +41 -0
  45. package/src/handlers/searchRequestSent.js +38 -0
  46. package/src/handlers/searchResponseReceived.js +39 -0
  47. package/src/handlers/shoppingCartPageView.js +45 -0
  48. package/src/handlers/shoppingMiniCartView.js +31 -0
  49. package/src/handlers/signIn.js +29 -0
  50. package/src/handlers/startCheckout.js +45 -0
  51. package/src/hooks/useExtensionContext.js +21 -0
  52. package/src/main.js +116 -0
  53. package/src/queries/getExtensionContext.js +31 -0
  54. package/src/utils.js +101 -0
  55. package/src/wrappers/wrapUseAccountMenu.js +29 -0
  56. package/src/wrappers/wrapUseAutocomplete.js +48 -0
@@ -0,0 +1,25 @@
1
+ const canHandle = event => event.type === 'USER_ACCOUNT_UPDATE';
2
+
3
+ const handle = (sdk, event) => {
4
+ const { payload } = event;
5
+
6
+ const { email, firstName, lastName } = payload;
7
+
8
+ const accountContext = {
9
+ firstName,
10
+ lastName,
11
+ emailAddress: email
12
+ };
13
+
14
+ sdk.context.setAccount(accountContext);
15
+ sdk.publish.editAccount({
16
+ personalEmail: {
17
+ address: email
18
+ }
19
+ });
20
+ };
21
+
22
+ export default {
23
+ canHandle,
24
+ handle
25
+ };
@@ -0,0 +1,26 @@
1
+ const canHandle = event => event.type === 'CMS_PAGE_VIEW';
2
+
3
+ const handle = (sdk, event) => {
4
+ const { payload } = event;
5
+
6
+ const { title } = payload;
7
+
8
+ const context = {
9
+ pageType: 'CMS',
10
+ pageName: title,
11
+ eventType: 'visibilityHidden',
12
+ maxXOffset: 0,
13
+ maxYOffset: 0,
14
+ minXOffset: 0,
15
+ minYOffset: 0
16
+ };
17
+
18
+ sdk.context.setPage(context);
19
+
20
+ sdk.publish.pageView();
21
+ };
22
+
23
+ export default {
24
+ canHandle,
25
+ handle
26
+ };
@@ -0,0 +1,32 @@
1
+ const canHandle = event => event.type === 'CHECKOUT_PLACE_ORDER_BUTTON_CLICKED';
2
+
3
+ const handle = (sdk, event) => {
4
+ const { payload } = event;
5
+
6
+ const grandTotal = payload.amount.grand_total.value;
7
+
8
+ const { payment, shipping } = payload;
9
+
10
+ const orderContext = {
11
+ grandTotal: grandTotal,
12
+ orderType: 'checkout',
13
+ payments: [
14
+ {
15
+ paymentMethodCode: payment.title,
16
+ paymentMethodName: payment.title,
17
+ total: grandTotal
18
+ }
19
+ ],
20
+ shipping: {
21
+ shippingMethod: shipping[0].method_title,
22
+ shippingAmount: shipping[0].amount.value
23
+ }
24
+ };
25
+
26
+ sdk.context.setOrder(orderContext);
27
+ };
28
+
29
+ export default {
30
+ canHandle,
31
+ handle
32
+ };
@@ -0,0 +1,41 @@
1
+ const canHandle = event => event.type === 'PRODUCT_PAGE_VIEW';
2
+
3
+ const handle = (sdk, event) => {
4
+ const { payload } = event;
5
+
6
+ const { name, id, currency_code, price_range, sku, url_key } = payload;
7
+
8
+ const pageContext = {
9
+ pageType: 'PDP',
10
+ pageName: name,
11
+ eventType: 'visibilityHidden',
12
+ maxXOffset: 0,
13
+ maxYOffset: 0,
14
+ minXOffset: 0,
15
+ minYOffset: 0
16
+ };
17
+
18
+ sdk.context.setPage(pageContext);
19
+
20
+ sdk.publish.pageView();
21
+
22
+ const productContext = {
23
+ productId: id,
24
+ name,
25
+ sku,
26
+ pricing: {
27
+ currencyCode: currency_code,
28
+ maximalPrice: price_range.maximum_price.final_price
29
+ },
30
+ canonicalUrl: url_key
31
+ };
32
+
33
+ sdk.context.setProduct(productContext);
34
+
35
+ sdk.publish.productPageView();
36
+ };
37
+
38
+ export default {
39
+ canHandle,
40
+ handle
41
+ };
@@ -0,0 +1,38 @@
1
+ const canHandle = event =>
2
+ ['SEARCH_REQUEST', 'SEARCHBAR_REQUEST'].includes(event.type);
3
+
4
+ const handle = (sdk, event) => {
5
+ const { payload } = event;
6
+
7
+ const { query, pageSize, currentPage, refinements, sort } = payload;
8
+
9
+ const filter = refinements.map(refinement => {
10
+ const { attribute, value } = refinement;
11
+ return {
12
+ attribute: attribute,
13
+ in: Array.from(value.values())
14
+ };
15
+ });
16
+
17
+ const requestContext = {
18
+ units: [
19
+ {
20
+ searchUnitId: 'productPage',
21
+ queryTypes: ['products'],
22
+ phrase: query,
23
+ pageSize: pageSize,
24
+ currentPage: currentPage,
25
+ filter: filter,
26
+ sort: [{ attribute: sort?.attribute, direction: sort?.order }]
27
+ }
28
+ ]
29
+ };
30
+
31
+ sdk.context.setSearchInput(requestContext);
32
+ sdk.publish.searchRequestSent();
33
+ };
34
+
35
+ export default {
36
+ canHandle,
37
+ handle
38
+ };
@@ -0,0 +1,39 @@
1
+ const canHandle = event => event.type === 'SEARCH_RESPONSE';
2
+
3
+ const handle = (sdk, event) => {
4
+ const { payload } = event;
5
+
6
+ const {
7
+ categories,
8
+ facets,
9
+ page,
10
+ perPage,
11
+ products,
12
+ searchRequestId,
13
+ searchUnitId,
14
+ suggestions
15
+ } = payload;
16
+
17
+ const searchResultsContext = {
18
+ units: [
19
+ {
20
+ categories,
21
+ facets,
22
+ page,
23
+ perPage,
24
+ products,
25
+ searchRequestId,
26
+ searchUnitId,
27
+ suggestions
28
+ }
29
+ ]
30
+ };
31
+
32
+ sdk.context.setSearchResults(searchResultsContext);
33
+ sdk.publish.searchResponseReceived(searchUnitId, searchResultsContext);
34
+ };
35
+
36
+ export default {
37
+ canHandle,
38
+ handle
39
+ };
@@ -0,0 +1,45 @@
1
+ import { getCartTotal, getCurrency, getFormattedProducts } from '../utils';
2
+
3
+ const canHandle = event => event.type === 'CART_PAGE_VIEW';
4
+
5
+ const handle = (sdk, event) => {
6
+ const { payload } = event;
7
+
8
+ const { cart_id: id, products } = payload;
9
+
10
+ const cartContext = {
11
+ id,
12
+ prices: {
13
+ subtotalExcludingTax: {
14
+ value: getCartTotal(products),
15
+ currency: getCurrency(products)
16
+ }
17
+ },
18
+ items: getFormattedProducts(products),
19
+ possibleOnepageCheckout: false,
20
+ giftMessageSelected: false,
21
+ giftWrappingSelected: false
22
+ };
23
+
24
+ sdk.context.setShoppingCart(cartContext);
25
+ sdk.publish.shoppingCartView();
26
+
27
+ // Send out page view event
28
+ const pageContext = {
29
+ pageType: 'Cart',
30
+ pageName: 'Cart',
31
+ eventType: 'visibilityHidden',
32
+ maxXOffset: 0,
33
+ maxYOffset: 0,
34
+ minXOffset: 0,
35
+ minYOffset: 0
36
+ };
37
+
38
+ sdk.context.setPage(pageContext);
39
+ sdk.publish.pageView();
40
+ };
41
+
42
+ export default {
43
+ canHandle,
44
+ handle
45
+ };
@@ -0,0 +1,31 @@
1
+ import { getCartTotal, getCurrency, getFormattedProducts } from '../utils';
2
+
3
+ const canHandle = event => event.type === 'MINI_CART_VIEW';
4
+
5
+ const handle = (sdk, event) => {
6
+ const { payload } = event;
7
+
8
+ const { cartId: id, products } = payload;
9
+
10
+ const cartContext = {
11
+ id: id,
12
+ prices: {
13
+ subtotalExcludingTax: {
14
+ value: getCartTotal(products),
15
+ currency: getCurrency(products)
16
+ }
17
+ },
18
+ items: getFormattedProducts(products),
19
+ possibleOnepageCheckout: false,
20
+ giftMessageSelected: false,
21
+ giftWrappingSelected: false
22
+ };
23
+
24
+ sdk.context.setShoppingCart(cartContext);
25
+ sdk.publish.shoppingCartView();
26
+ };
27
+
28
+ export default {
29
+ canHandle,
30
+ handle
31
+ };
@@ -0,0 +1,29 @@
1
+ const canHandle = event => event.type === 'USER_SIGN_IN';
2
+
3
+ const handle = (sdk, event) => {
4
+ const { payload } = event;
5
+
6
+ sdk.context.setShopper({
7
+ shopperId: 'logged-in'
8
+ });
9
+
10
+ const { firstname, lastname, email } = payload;
11
+
12
+ const accountContext = {
13
+ firstName: firstname,
14
+ lastName: lastname,
15
+ emailAddress: email
16
+ };
17
+
18
+ sdk.context.setAccount(accountContext);
19
+ sdk.publish.signIn({
20
+ personalEmail: {
21
+ address: email
22
+ }
23
+ });
24
+ };
25
+
26
+ export default {
27
+ canHandle,
28
+ handle
29
+ };
@@ -0,0 +1,45 @@
1
+ import { getCartTotal, getCurrency, getFormattedProducts } from '../utils';
2
+
3
+ const canHandle = event => event.type === 'CHECKOUT_PAGE_VIEW';
4
+
5
+ const handle = (sdk, event) => {
6
+ const { payload } = event;
7
+
8
+ const { cart_id, products } = payload;
9
+
10
+ // Send out page view event
11
+ const pageContext = {
12
+ pageType: 'Checkout',
13
+ pageName: 'Checkout',
14
+ eventType: 'visibilityHidden',
15
+ maxXOffset: 0,
16
+ maxYOffset: 0,
17
+ minXOffset: 0,
18
+ minYOffset: 0
19
+ };
20
+
21
+ sdk.context.setPage(pageContext);
22
+ sdk.publish.pageView();
23
+
24
+ const cartContext = {
25
+ id: cart_id,
26
+ prices: {
27
+ subtotalExcludingTax: {
28
+ value: getCartTotal(products),
29
+ currency: getCurrency(products)
30
+ }
31
+ },
32
+ items: getFormattedProducts(products),
33
+ possibleOnepageCheckout: false,
34
+ giftMessageSelected: false,
35
+ giftWrappingSelected: false
36
+ };
37
+
38
+ sdk.context.setShoppingCart(cartContext);
39
+ sdk.publish.initiateCheckout();
40
+ };
41
+
42
+ export default {
43
+ canHandle,
44
+ handle
45
+ };
@@ -0,0 +1,21 @@
1
+ import { useLazyQuery } from '@apollo/client';
2
+ import { GET_EXTENSION_CONTEXT } from '../queries/getExtensionContext.js';
3
+ import { useEffect } from 'react';
4
+
5
+ const useExtensionContext = () => {
6
+ const [
7
+ fetchExtensionContext,
8
+ { called, data, loading, error }
9
+ ] = useLazyQuery(GET_EXTENSION_CONTEXT);
10
+ useEffect(() => {
11
+ fetchExtensionContext();
12
+ }, [fetchExtensionContext]);
13
+
14
+ return {
15
+ ready: called && !loading,
16
+ data,
17
+ error
18
+ };
19
+ };
20
+
21
+ export default useExtensionContext;
package/src/main.js ADDED
@@ -0,0 +1,116 @@
1
+ import { useEventingContext } from '@magento/peregrine/lib/context/eventing';
2
+ import { useUserContext } from '@magento/peregrine/lib/context/user';
3
+ import { useEffect, useState } from 'react';
4
+ import { default as handleEvent } from './handleEvent';
5
+ import useExtensionContext from './hooks/useExtensionContext';
6
+
7
+ export default original => props => {
8
+ const [{ isSignedIn, currentUser }] = useUserContext();
9
+ const [observable] = useEventingContext();
10
+
11
+ const [sdk, setSdk] = useState();
12
+
13
+ const {
14
+ data: storefrontData,
15
+ ready: storefrontDataReady,
16
+ errors
17
+ } = useExtensionContext();
18
+
19
+ useEffect(() => {
20
+ if (errors) {
21
+ console.error('Experience Platform Connector Error', errors);
22
+ return;
23
+ }
24
+
25
+ if (storefrontDataReady && storefrontData) {
26
+ const {
27
+ dataServicesStorefrontInstanceContext: storefrontContext,
28
+ experienceConnectorContext: connectorContext
29
+ } = storefrontData;
30
+
31
+ import('@adobe/magento-storefront-events-sdk').then(mse => {
32
+ if (!window.magentoStorefrontEvents) {
33
+ window.magentoStorefrontEvents = mse;
34
+ }
35
+
36
+ const orgId = storefrontContext.ims_org_id;
37
+ const datastreamId = connectorContext.datastream_id;
38
+
39
+ if (orgId && datastreamId) {
40
+ mse.context.setAEP({
41
+ imsOrgId: orgId,
42
+ datastreamId: datastreamId
43
+ });
44
+
45
+ mse.context.setEventForwarding({
46
+ aep: true
47
+ });
48
+
49
+ // Set storefront context
50
+ mse.context.setStorefrontInstance({
51
+ environmentId: storefrontContext.environment_id,
52
+ environment: storefrontContext.environment,
53
+ storeUrl: storefrontContext.store_url,
54
+ websiteId: storefrontContext.website_id,
55
+ websiteCode: storefrontContext.website_code,
56
+ storeId: storefrontContext.store_id,
57
+ storeCode: storefrontContext.store_code,
58
+ storeViewId: storefrontContext.store_view_id,
59
+ storeViewCode: storefrontContext.store_view_code,
60
+ websiteName: storefrontContext.website_name,
61
+ storeName: storefrontContext.store_name,
62
+ storeViewName: storefrontContext.store_view_name,
63
+ baseCurrencyCode: storefrontContext.base_currency_code,
64
+ storeViewCurrencyCode:
65
+ storefrontContext.store_view_currency_code,
66
+ catalogExtensionVersion:
67
+ storefrontContext.catalog_extension_version
68
+ });
69
+
70
+ import('@adobe/magento-storefront-event-collector').then(
71
+ msec => {
72
+ msec;
73
+ setSdk(mse);
74
+ }
75
+ );
76
+ }
77
+ });
78
+ }
79
+ }, [storefrontDataReady, storefrontData, setSdk, errors]);
80
+
81
+ useEffect(() => {
82
+ if (sdk) {
83
+ const sub = observable.subscribe(async event => {
84
+ handleEvent(sdk, event);
85
+ });
86
+
87
+ return () => {
88
+ sub.unsubscribe();
89
+ };
90
+ }
91
+ }, [sdk, observable]);
92
+
93
+ // Sets shopper context on initial load (when shopper context is null)
94
+ useEffect(() => {
95
+ if (sdk && !sdk.context.getShopper()) {
96
+ if (isSignedIn) {
97
+ sdk.context.setShopper({
98
+ shopperId: 'logged-in'
99
+ });
100
+
101
+ sdk.context.setAccount({
102
+ firstName: currentUser.firstname,
103
+ lastName: currentUser.lastname,
104
+ emailAddress: currentUser.email,
105
+ accountType: currentUser.__typename
106
+ });
107
+ } else {
108
+ sdk.context.setShopper({
109
+ shopperId: 'guest'
110
+ });
111
+ }
112
+ }
113
+ }, [sdk, isSignedIn, currentUser]);
114
+
115
+ return original(props);
116
+ };
@@ -0,0 +1,31 @@
1
+ import { gql } from '@apollo/client';
2
+
3
+ export const GET_EXTENSION_CONTEXT = gql`
4
+ query experiencePlatformConnectorContext {
5
+ dataServicesStorefrontInstanceContext {
6
+ environment_id
7
+ environment
8
+ store_url
9
+ website_id
10
+ website_code
11
+ store_id
12
+ store_code
13
+ store_view_id
14
+ store_view_code
15
+ website_name
16
+ store_name
17
+ store_view_name
18
+ base_currency_code
19
+ store_view_currency_code
20
+ catalog_extension_version
21
+ ims_org_id
22
+ }
23
+ experienceConnectorContext {
24
+ datastream_id
25
+ }
26
+ }
27
+ `;
28
+
29
+ export default {
30
+ getExtensionContext: GET_EXTENSION_CONTEXT
31
+ };
package/src/utils.js ADDED
@@ -0,0 +1,101 @@
1
+ /** Cart focused utils **/
2
+
3
+ /**
4
+ * Returns the total sum from an array of cart products
5
+ *
6
+ * @param {Array} products
7
+ * @returns {Number} Sum of all product prices in the array
8
+ */
9
+ export const getCartTotal = products => {
10
+ return products
11
+ ? products.reduce(
12
+ (previous, current) =>
13
+ current.prices.price.value * current.quantity + previous,
14
+ 0
15
+ )
16
+ : 0;
17
+ };
18
+
19
+ /**
20
+ * Get the currency from the first product item in an array of cart products
21
+ *
22
+ * @param {Array} products
23
+ * @returns {String} Currency code from the first product item or null if array is empty
24
+ */
25
+ export const getCurrency = products =>
26
+ products && products.length > 0 ? products[0].prices.price.currency : null;
27
+
28
+ /**
29
+ * Transforms an array of cart products into a format compatible with the
30
+ * Magento Storefront Event SDK
31
+ *
32
+ * @param {Array} products
33
+ * @returns {Array} Array of data compatible with the Magento Storefront Event SDK
34
+ */
35
+ export const getFormattedProducts = products => {
36
+ return products
37
+ ? products.map(item => {
38
+ const {
39
+ uid,
40
+ product,
41
+ prices,
42
+ quantity,
43
+ configurable_options: options
44
+ } = item;
45
+
46
+ const {
47
+ name,
48
+ sku,
49
+ __typename: type,
50
+ url_key: url,
51
+ small_image: image,
52
+ thumbnail
53
+ } = product;
54
+
55
+ const formattedOptions = options
56
+ ? options.map(option => {
57
+ const {
58
+ id,
59
+ option_label,
60
+ value_label,
61
+ configurable_product_option_value_uid: valueId
62
+ } = option;
63
+ return {
64
+ id: id,
65
+ optionLabel: option_label,
66
+ valueId: valueId,
67
+ valueLabel: value_label
68
+ };
69
+ })
70
+ : null;
71
+
72
+ const imageUrl = image
73
+ ? image.url
74
+ : thumbnail
75
+ ? thumbnail.url
76
+ : null;
77
+
78
+ return {
79
+ formattedPrice: '',
80
+ id: uid,
81
+ prices: prices,
82
+ product: {
83
+ name: name,
84
+ sku: sku,
85
+ productType: type,
86
+ pricing: {
87
+ regularPrice: prices.price.value,
88
+ minimalPrice: prices.price.value,
89
+ maximalPrice: prices.price.value,
90
+ currencyCode: prices.price.currency
91
+ },
92
+ canonicalUrl: url,
93
+ mainImageUrl: imageUrl
94
+ },
95
+
96
+ configurableOptions: formattedOptions,
97
+ quantity: quantity
98
+ };
99
+ })
100
+ : null;
101
+ };
@@ -0,0 +1,29 @@
1
+ import { useCallback } from 'react';
2
+
3
+ // Wrapper for the useAccountMenu() talon
4
+ const wrapUseAccountMenu = useAccountMenu => {
5
+ return props => {
6
+ const talonProps = useAccountMenu(props);
7
+
8
+ const { handleSignOut, ...restProps } = talonProps;
9
+
10
+ const sdk = window.magentoStorefrontEvents;
11
+
12
+ // Need to publish the sign out event before actually calling the original sign out
13
+ // callback to make sure data is sent before the page refreshes
14
+ const newHandleSignOut = useCallback(async () => {
15
+ if (sdk) {
16
+ sdk.publish.signOut();
17
+ }
18
+
19
+ handleSignOut();
20
+ }, [sdk, handleSignOut]);
21
+
22
+ return {
23
+ handleSignOut: newHandleSignOut,
24
+ ...restProps
25
+ };
26
+ };
27
+ };
28
+
29
+ export default wrapUseAccountMenu;