@shopgate/engage 7.31.7 → 7.31.8-beta.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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shopgate/engage",
|
|
3
|
-
"version": "7.31.
|
|
3
|
+
"version": "7.31.8-beta.1",
|
|
4
4
|
"description": "Shopgate's ENGAGE library.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Shopgate <support@shopgate.com>",
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@emotion/react": "^11.14.0",
|
|
20
20
|
"@shopgate/native-modules": "1.0.0-beta.34",
|
|
21
|
-
"@shopgate/pwa-common": "7.31.
|
|
22
|
-
"@shopgate/pwa-common-commerce": "7.31.
|
|
23
|
-
"@shopgate/pwa-core": "7.31.
|
|
24
|
-
"@shopgate/pwa-ui-ios": "7.31.
|
|
25
|
-
"@shopgate/pwa-ui-material": "7.31.
|
|
26
|
-
"@shopgate/pwa-ui-shared": "7.31.
|
|
21
|
+
"@shopgate/pwa-common": "7.31.8-beta.1",
|
|
22
|
+
"@shopgate/pwa-common-commerce": "7.31.8-beta.1",
|
|
23
|
+
"@shopgate/pwa-core": "7.31.8-beta.1",
|
|
24
|
+
"@shopgate/pwa-ui-ios": "7.31.8-beta.1",
|
|
25
|
+
"@shopgate/pwa-ui-material": "7.31.8-beta.1",
|
|
26
|
+
"@shopgate/pwa-ui-shared": "7.31.8-beta.1",
|
|
27
27
|
"@stripe/react-stripe-js": "^1.16.5",
|
|
28
28
|
"@stripe/stripe-js": "^1.44.1",
|
|
29
29
|
"@virtuous/conductor": "~2.5.0",
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* List of allowed
|
|
2
|
+
* List of allowed origin patterns for cms page preview iFrame communication. Incoming messages are
|
|
3
|
+
* only processed when their origin matches one of these patterns, and outgoing messages are only
|
|
4
|
+
* posted to origins that matched before.
|
|
5
|
+
*
|
|
6
|
+
* A "*" acts as a wildcard for one or more domain labels, so "https://*.shopgate.com" matches
|
|
7
|
+
* "https://app.shopgate.com" as well as "https://next.us.admin.shopgate.com", but neither
|
|
8
|
+
* "https://shopgate.com" nor "https://evil.shopgate.com.attacker.example". Every other character
|
|
9
|
+
* is matched literally, which means that scheme and port always have to match exactly.
|
|
3
10
|
*/
|
|
4
|
-
export const ALLOWED_PAGE_PREVIEW_ORIGINS = ['https
|
|
11
|
+
export const ALLOWED_PAGE_PREVIEW_ORIGINS = ['https://*.shopgate.com', 'https://*.shopgatedev.com', 'https://*.shopgatepg.com', 'http://localhost.localdev.cc', 'http://localhost:1337'];
|
|
5
12
|
|
|
6
13
|
// Whether to consider vertical margins when calculating the overlay position.
|
|
7
14
|
export const CONSIDER_CONTAINER_MARGINS_ON_SCROLL_DEFAULT = false;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import "core-js/modules/es.array.includes.js";
|
|
1
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";
|
|
2
6
|
import { PAGE_PREVIEW_PATTERN } from '@shopgate/engage/page/constants';
|
|
3
7
|
|
|
4
8
|
/**
|
|
@@ -50,4 +54,62 @@ export function checkScheduled({
|
|
|
50
54
|
isActive,
|
|
51
55
|
isExpired
|
|
52
56
|
};
|
|
53
|
-
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Regular expression source that a "*" within an allowed origin pattern is replaced with. It
|
|
61
|
+
* matches one or more domain labels, but never a dot at the very end, so that the suffix of the
|
|
62
|
+
* pattern stays anchored to the end of the origin.
|
|
63
|
+
*/
|
|
64
|
+
const ORIGIN_WILDCARD_SOURCE = '(?:[a-zA-Z0-9-]+\\.)*[a-zA-Z0-9-]+';
|
|
65
|
+
|
|
66
|
+
// Cache for regular expressions that were already created from an origin pattern.
|
|
67
|
+
const originPatternCache = new Map();
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Converts an allowed origin pattern into an anchored regular expression. Every character except
|
|
71
|
+
* the "*" wildcard is matched literally.
|
|
72
|
+
* @param {string} pattern The origin pattern e.g. "https://*.shopgate.com".
|
|
73
|
+
* @returns {RegExp} The regular expression for the pattern.
|
|
74
|
+
*/
|
|
75
|
+
const createOriginRegExp = pattern => {
|
|
76
|
+
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
77
|
+
return new RegExp(`^${escaped.split('\\*').join(ORIGIN_WILDCARD_SOURCE)}$`);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Checks if an origin is covered by a list of allowed origin patterns.
|
|
82
|
+
* @param {string} origin The origin to check e.g. "https://app.shopgate.com".
|
|
83
|
+
* @param {Array<string>} [patterns] The allowed origin patterns.
|
|
84
|
+
* @returns {boolean} Whether the origin is allowed.
|
|
85
|
+
*/
|
|
86
|
+
export const isAllowedOrigin = (origin, patterns = []) => {
|
|
87
|
+
// Opaque origins are serialized as "null" and must never be trusted.
|
|
88
|
+
if (typeof origin !== 'string' || origin === '' || origin === 'null') {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
return patterns.some(pattern => {
|
|
92
|
+
if (typeof pattern !== 'string' || pattern === '') {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (!pattern.includes('*')) {
|
|
96
|
+
return pattern === origin;
|
|
97
|
+
}
|
|
98
|
+
if (!originPatternCache.has(pattern)) {
|
|
99
|
+
originPatternCache.set(pattern, createOriginRegExp(pattern));
|
|
100
|
+
}
|
|
101
|
+
return originPatternCache.get(pattern).test(origin);
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Determines the origin of the document that embeds the current page.
|
|
107
|
+
* @returns {string|null} The referrer origin, or null when it can't be determined.
|
|
108
|
+
*/
|
|
109
|
+
export const getReferrerOrigin = () => {
|
|
110
|
+
try {
|
|
111
|
+
return new URL(document.referrer).origin;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
@@ -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 { useEffect, useCallback, useRef, useContext, useMemo } from 'react';
|
|
6
2
|
import { logger } from '@shopgate/engage/core/helpers';
|
|
7
3
|
import { useDispatch } from 'react-redux';
|
|
@@ -9,7 +5,7 @@ import { useRoute } from '@shopgate/engage/core/hooks';
|
|
|
9
5
|
import { receivePageConfigV2 } from '@shopgate/engage/page/action-creators';
|
|
10
6
|
import { PAGE_PREVIEW_SLUG } from '@shopgate/engage/page/constants';
|
|
11
7
|
import { ALLOWED_PAGE_PREVIEW_ORIGINS, CONSIDER_CONTAINER_MARGINS_ON_SCROLL_DEFAULT } from "./constants";
|
|
12
|
-
import { getScrollContainer } from "./helpers";
|
|
8
|
+
import { getScrollContainer, isAllowedOrigin, getReferrerOrigin } from "./helpers";
|
|
13
9
|
import { WidgetsPreviewContext } from "./WidgetsPreviewContext";
|
|
14
10
|
import { dispatchWidgetPreviewEvent, useWidgetPreviewEvent } from "./events";
|
|
15
11
|
|
|
@@ -30,22 +26,26 @@ import { dispatchWidgetPreviewEvent, useWidgetPreviewEvent } from "./events";
|
|
|
30
26
|
/**
|
|
31
27
|
* Hook for postMessage communication when your component is inside an iframe.
|
|
32
28
|
*
|
|
33
|
-
* Listens on window for "message" events
|
|
34
|
-
*
|
|
29
|
+
* Listens on window for "message" events, and only calls onMessage(data, rawEvent) if the origin
|
|
30
|
+
* of the event is covered by parentOrigins and the message actually came from window.parent.
|
|
35
31
|
*
|
|
36
32
|
* @param {function(MessageData, any): void} onMessage
|
|
37
33
|
* Callback invoked when a trusted message arrives. Receives data and the
|
|
38
34
|
* raw event (so you can inspect origin, source, etc.).
|
|
39
35
|
* @param {string[]} parentOrigins
|
|
40
|
-
* Array of allowed parent origin
|
|
41
|
-
* ['https://a.example.com','https
|
|
36
|
+
* Array of allowed parent origin patterns (e.g.
|
|
37
|
+
* ['https://a.example.com','https://*.example.com']). See ALLOWED_PAGE_PREVIEW_ORIGINS for
|
|
38
|
+
* details about the supported wildcard syntax.
|
|
39
|
+
* @param {boolean} [enabled]
|
|
40
|
+
* Whether the message listener is supposed to be attached.
|
|
42
41
|
* @returns {IframeMessengerResult}
|
|
43
42
|
* An object with a single method:
|
|
44
43
|
* • sendToParent(data, [targetOrigin]): void
|
|
45
44
|
* – Posts data up to window.parent. By default it uses the most recently
|
|
46
|
-
* seen origin (from an incoming message)
|
|
45
|
+
* seen origin (from an incoming message), otherwise the origin of the embedding document.
|
|
46
|
+
* Nothing is sent when neither of them is covered by parentOrigins.
|
|
47
47
|
*/
|
|
48
|
-
function useIframeMessenger(onMessage, parentOrigins) {
|
|
48
|
+
function useIframeMessenger(onMessage, parentOrigins, enabled = true) {
|
|
49
49
|
// Keep a ref to the latest onMessage callback so the listener always has it.
|
|
50
50
|
const onMessageRef = useRef(onMessage);
|
|
51
51
|
useEffect(() => {
|
|
@@ -59,15 +59,15 @@ function useIframeMessenger(onMessage, parentOrigins) {
|
|
|
59
59
|
* Send a message up to the parent window.
|
|
60
60
|
* @param {MessageData} data - The data object to post.
|
|
61
61
|
* @param {string} [targetOrigin]
|
|
62
|
-
* Optional override for the origin to post to. Must be
|
|
63
|
-
*
|
|
64
|
-
* or parentOrigins[0], or "*" if array is empty.
|
|
62
|
+
* Optional override for the origin to post to. Must be covered by parentOrigins. If omitted,
|
|
63
|
+
* the last seen origin (lastOriginRef) or the origin of the embedding document is used.
|
|
65
64
|
*/
|
|
66
65
|
const sendToParent = useCallback((data, targetOrigin) => {
|
|
67
|
-
// Determine which origin to use: explicit, then last seen, then
|
|
68
|
-
|
|
66
|
+
// Determine which origin to use: explicit, then last seen, then the embedding document.
|
|
67
|
+
// Patterns are no valid postMessage targets, so an unresolved origin aborts the send.
|
|
68
|
+
const originToUse = [targetOrigin, lastOriginRef.current, getReferrerOrigin()].find(origin => isAllowedOrigin(origin, parentOrigins));
|
|
69
69
|
if (!originToUse) {
|
|
70
|
-
logger.warn('useIframeMessenger: no targetOrigin available. ' + 'Provide parentOrigins or pass targetOrigin.');
|
|
70
|
+
logger.warn('useIframeMessenger: no allowed targetOrigin available. ' + 'Provide parentOrigins or pass an allowed targetOrigin.');
|
|
71
71
|
return;
|
|
72
72
|
}
|
|
73
73
|
window.parent.postMessage(data, originToUse);
|
|
@@ -75,13 +75,17 @@ function useIframeMessenger(onMessage, parentOrigins) {
|
|
|
75
75
|
|
|
76
76
|
// Attach / detach the "message" listener.
|
|
77
77
|
useEffect(() => {
|
|
78
|
+
if (!enabled) {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
78
82
|
/**
|
|
79
83
|
* Handler for incoming postMessage events.
|
|
80
84
|
* @param {any} rawEvent – The original MessageEvent object.
|
|
81
85
|
*/
|
|
82
86
|
function handler(rawEvent) {
|
|
83
|
-
// Only proceed if the origin is
|
|
84
|
-
if (!
|
|
87
|
+
// Only proceed if the origin is covered by our whitelist.
|
|
88
|
+
if (!isAllowedOrigin(rawEvent.origin, parentOrigins)) return;
|
|
85
89
|
// Ensure the message actually came from window.parent.
|
|
86
90
|
if (rawEvent.source !== window.parent) return;
|
|
87
91
|
|
|
@@ -95,7 +99,7 @@ function useIframeMessenger(onMessage, parentOrigins) {
|
|
|
95
99
|
return () => {
|
|
96
100
|
window.removeEventListener('message', handler);
|
|
97
101
|
};
|
|
98
|
-
}, [parentOrigins, sendToParent]);
|
|
102
|
+
}, [enabled, parentOrigins, sendToParent]);
|
|
99
103
|
return {
|
|
100
104
|
sendToParent
|
|
101
105
|
};
|
|
@@ -194,7 +198,7 @@ export const usePreviewIframeCommunication = (isActive = false) => {
|
|
|
194
198
|
});
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
|
-
}, ALLOWED_PAGE_PREVIEW_ORIGINS);
|
|
201
|
+
}, ALLOWED_PAGE_PREVIEW_ORIGINS, isActive);
|
|
198
202
|
useWidgetPreviewEvent('widget-clicked', e => {
|
|
199
203
|
if (!isActive) {
|
|
200
204
|
return;
|