@shopgate/engage 7.32.0-beta.13 → 7.32.0-beta.15

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 (72) hide show
  1. package/admin-preview/constants/index.d.ts +9 -1
  2. package/admin-preview/constants/index.d.ts.map +1 -1
  3. package/admin-preview/constants/index.js +10 -2
  4. package/admin-preview/helpers/_internal/origins.d.ts +20 -0
  5. package/admin-preview/helpers/_internal/origins.d.ts.map +1 -0
  6. package/admin-preview/helpers/_internal/origins.js +72 -0
  7. package/admin-preview/helpers/index.d.ts +13 -0
  8. package/admin-preview/helpers/index.d.ts.map +1 -1
  9. package/admin-preview/helpers/index.js +17 -1
  10. package/admin-preview/hooks/index.d.ts +9 -7
  11. package/admin-preview/hooks/index.d.ts.map +1 -1
  12. package/admin-preview/hooks/index.js +21 -18
  13. package/admin-preview/initialization/awaitInitialFrontendSettings.d.ts +4 -3
  14. package/admin-preview/initialization/awaitInitialFrontendSettings.d.ts.map +1 -1
  15. package/admin-preview/initialization/awaitInitialFrontendSettings.js +13 -9
  16. package/filter/components/FilterItem/index.js +9 -2
  17. package/filter/components/FilterPageContent/components/ApplyButton/index.d.ts +21 -0
  18. package/filter/components/FilterPageContent/components/ApplyButton/index.d.ts.map +1 -0
  19. package/filter/components/FilterPageContent/components/ApplyButton/index.js +25 -52
  20. package/filter/components/FilterPageContent/components/FilterButtonBar/index.d.ts +8 -0
  21. package/filter/components/FilterPageContent/components/FilterButtonBar/index.d.ts.map +1 -0
  22. package/filter/components/FilterPageContent/components/FilterButtonBar/index.js +63 -0
  23. package/filter/components/FilterPageContent/components/ResetButton/index.d.ts +17 -0
  24. package/filter/components/FilterPageContent/components/ResetButton/index.d.ts.map +1 -0
  25. package/filter/components/FilterPageContent/components/ResetButton/index.js +24 -46
  26. package/filter/components/FilterPageContent/components/Selector/components/Selected/index.d.ts +19 -0
  27. package/filter/components/FilterPageContent/components/Selector/components/Selected/index.d.ts.map +1 -0
  28. package/filter/components/FilterPageContent/components/Selector/components/Selected/index.js +25 -46
  29. package/filter/components/FilterPageContent/components/Selector/components/Toggle/index.d.ts +14 -0
  30. package/filter/components/FilterPageContent/components/Selector/components/Toggle/index.d.ts.map +1 -0
  31. package/filter/components/FilterPageContent/components/Selector/components/Toggle/index.js +13 -46
  32. package/filter/components/FilterPageContent/components/Selector/components/ValueCheckbox/index.d.ts +25 -0
  33. package/filter/components/FilterPageContent/components/Selector/components/ValueCheckbox/index.d.ts.map +1 -0
  34. package/filter/components/FilterPageContent/components/Selector/components/ValueCheckbox/index.js +77 -0
  35. package/filter/components/FilterPageContent/components/Selector/index.d.ts +43 -0
  36. package/filter/components/FilterPageContent/components/Selector/index.d.ts.map +1 -0
  37. package/filter/components/FilterPageContent/components/Selector/index.js +52 -40
  38. package/filter/components/FilterPageContent/index.d.ts +16 -0
  39. package/filter/components/FilterPageContent/index.d.ts.map +1 -0
  40. package/filter/components/FilterPageContent/index.js +51 -57
  41. package/filter/components/PriceSlider/components/Label/index.js +69 -94
  42. package/filter/components/PriceSlider/index.js +43 -33
  43. package/filter/helpers/buildFilterParamsForFetchFiltersRequest.d.ts +8 -0
  44. package/filter/helpers/buildFilterParamsForFetchFiltersRequest.d.ts.map +1 -0
  45. package/filter/helpers/buildInitialFilters.d.ts +7 -0
  46. package/filter/helpers/buildInitialFilters.d.ts.map +1 -0
  47. package/filter/helpers/buildUpdatedFilters.d.ts +7 -0
  48. package/filter/helpers/buildUpdatedFilters.d.ts.map +1 -0
  49. package/filter/helpers/index.d.ts +5 -0
  50. package/filter/helpers/index.d.ts.map +1 -0
  51. package/filter/helpers/translateFilterLabel.d.ts +8 -0
  52. package/filter/helpers/translateFilterLabel.d.ts.map +1 -0
  53. package/filter/providers/FilterPageProvider.context.d.ts +157 -0
  54. package/filter/providers/FilterPageProvider.context.d.ts.map +1 -0
  55. package/filter/providers/FilterPageProvider.d.ts +24 -0
  56. package/filter/providers/FilterPageProvider.d.ts.map +1 -0
  57. package/filter/providers/FilterPageProvider.js +77 -132
  58. package/locations/components/GlobalLocationSwitcher/GlobalLocationSwitcherBar.js +4 -2
  59. package/package.json +7 -7
  60. package/page/components/Widgets/hooks.d.ts.map +1 -1
  61. package/page/components/Widgets/hooks.js +5 -1
  62. package/product/components/FilterBar/components/Content/components/FilterButton/index.js +38 -46
  63. package/product/components/FilterBar/components/Content/components/FilterChips/components/FilterChip/index.d.ts +38 -0
  64. package/product/components/FilterBar/components/Content/components/FilterChips/components/FilterChip/index.d.ts.map +1 -0
  65. package/product/components/FilterBar/components/Content/components/FilterChips/components/FilterChip/index.js +88 -0
  66. package/product/components/FilterBar/components/Content/components/FilterChips/index.js +54 -35
  67. package/product/components/FilterBar/components/Content/components/Sort/index.js +1 -1
  68. package/product/components/FilterBar/components/Content/index.js +7 -3
  69. package/product/components/FilterBar/index.js +11 -22
  70. package/product/components/ProductFilters/index.js +2 -6
  71. package/filter/components/FilterPageContent/components/Selector/components/ValueButton/index.js +0 -69
  72. package/product/components/FilterBar/components/Content/components/FilterChips/connector.js +0 -22
@@ -1,6 +1,14 @@
1
1
  export declare const PAGE_PREVIEW_PATTERN = "/shopgate-internal-page-preview";
2
2
  /**
3
- * List of allowed origins for cms page preview iFrame communication.
3
+ * List of allowed origin patterns for admin preview iFrame communication. Both directions are
4
+ * validated against these patterns: incoming messages are only processed when their origin matches,
5
+ * and outgoing messages are only posted to a matching origin - either the origin of the last
6
+ * accepted incoming message, or the origin of the embedding document.
7
+ *
8
+ * A "*" acts as a wildcard for one or more domain labels, so "https://*.shopgate.com" matches
9
+ * "https://app.shopgate.com" as well as "https://next.us.admin.shopgate.com", but neither
10
+ * "https://shopgate.com" nor "https://evil.shopgate.com.attacker.example". Every other character
11
+ * is matched literally, which means that scheme and port always have to match exactly.
4
12
  */
5
13
  export declare const ALLOWED_ADMIN_PREVIEW_ORIGINS: string[];
6
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/constants/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AAEtE;;GAEG;AACH,eAAO,MAAM,6BAA6B,UASzC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/constants/index.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,oCAAoC,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,eAAO,MAAM,6BAA6B,UAMzC,CAAC"}
@@ -1,6 +1,14 @@
1
1
  export const PAGE_PREVIEW_PATTERN = '/shopgate-internal-page-preview';
2
2
 
3
3
  /**
4
- * List of allowed origins for cms page preview iFrame communication.
4
+ * List of allowed origin patterns for admin preview iFrame communication. Both directions are
5
+ * validated against these patterns: incoming messages are only processed when their origin matches,
6
+ * and outgoing messages are only posted to a matching origin - either the origin of the last
7
+ * accepted incoming message, or the origin of the embedding document.
8
+ *
9
+ * A "*" acts as a wildcard for one or more domain labels, so "https://*.shopgate.com" matches
10
+ * "https://app.shopgate.com" as well as "https://next.us.admin.shopgate.com", but neither
11
+ * "https://shopgate.com" nor "https://evil.shopgate.com.attacker.example". Every other character
12
+ * is matched literally, which means that scheme and port always have to match exactly.
5
13
  */
6
- export const ALLOWED_ADMIN_PREVIEW_ORIGINS = ['https://next.admin.shopgatedev.com', 'https://next.admin.shopgatepg.com', 'https://next.admin.shopgate.com', 'https://next.us.admin.shopgate.com', 'https://admin-mono.shopgatedev.com', 'https://admin-mono.shopgatepg.com', 'https://admin-mono.shopgate.com', 'http://localhost:1337'];
14
+ export const ALLOWED_ADMIN_PREVIEW_ORIGINS = ['https://*.shopgate.com', 'https://*.shopgatedev.com', 'https://*.shopgatepg.com', 'http://localhost.localdev.cc', 'http://localhost:1337'];
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Internal to the admin-preview package - do not import this module from anywhere else. Consume
3
+ * the isAllowedOrigin / getReferrerOrigin helpers from @shopgate/engage/admin-preview/helpers
4
+ * instead.
5
+ */
6
+ /**
7
+ * Checks if an origin is covered by a list of allowed origin patterns.
8
+ * @param origin The origin to check e.g. "https://app.shopgate.com".
9
+ * @param patterns The allowed origin patterns.
10
+ * @returns Whether the origin is allowed.
11
+ */
12
+ export declare const matchesAllowedOrigin: (origin: string | null | undefined, patterns?: string[]) => origin is string;
13
+ /**
14
+ * Determines the origin of the document that embeds the current page. Since allowed origins can be
15
+ * patterns, and patterns are no valid postMessage targets, this is the only way to address a parent
16
+ * that has not sent a message yet.
17
+ * @returns The referrer origin, or null when it can't be determined.
18
+ */
19
+ export declare const readReferrerOrigin: () => string | null;
20
+ //# sourceMappingURL=origins.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../../../../admin-preview/helpers/_internal/origins.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,WAAU,MAAM,EAAO,KACtB,MAAM,IAAI,MAwBZ,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,QAAO,MAAM,GAAG,IAM9C,CAAC"}
@@ -0,0 +1,72 @@
1
+ import "core-js/modules/es.array.includes.js";
2
+ import "core-js/modules/es.string.replace.js";
3
+ import "core-js/modules/web.url.js";
4
+ import "core-js/modules/web.url.to-json.js";
5
+ import "core-js/modules/web.url-search-params.js";
6
+ /**
7
+ * Internal to the admin-preview package - do not import this module from anywhere else. Consume
8
+ * the isAllowedOrigin / getReferrerOrigin helpers from @shopgate/engage/admin-preview/helpers
9
+ * instead.
10
+ */
11
+
12
+ /**
13
+ * Regular expression source that a "*" within an allowed origin pattern is replaced with. It
14
+ * matches one or more domain labels, but never a dot at the very end, so that the suffix of the
15
+ * pattern stays anchored to the end of the origin.
16
+ */
17
+ const ORIGIN_WILDCARD_SOURCE = '(?:[a-zA-Z0-9-]+\\.)*[a-zA-Z0-9-]+';
18
+
19
+ // Cache for regular expressions that were already created from an origin pattern.
20
+ const originPatternCache = new Map();
21
+
22
+ /**
23
+ * Converts an allowed origin pattern into an anchored regular expression. Every character except
24
+ * the "*" wildcard is matched literally.
25
+ * @param pattern The origin pattern e.g. "https://*.shopgate.com".
26
+ * @returns The regular expression for the pattern.
27
+ */
28
+ const createOriginRegExp = pattern => {
29
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
+ return new RegExp(`^${escaped.split('\\*').join(ORIGIN_WILDCARD_SOURCE)}$`);
31
+ };
32
+
33
+ /**
34
+ * Checks if an origin is covered by a list of allowed origin patterns.
35
+ * @param origin The origin to check e.g. "https://app.shopgate.com".
36
+ * @param patterns The allowed origin patterns.
37
+ * @returns Whether the origin is allowed.
38
+ */
39
+ export const matchesAllowedOrigin = (origin, patterns = []) => {
40
+ // Opaque origins are serialized as "null" and must never be trusted.
41
+ if (typeof origin !== 'string' || origin === '' || origin === 'null') {
42
+ return false;
43
+ }
44
+ return patterns.some(pattern => {
45
+ if (typeof pattern !== 'string' || pattern === '') {
46
+ return false;
47
+ }
48
+ if (!pattern.includes('*')) {
49
+ return pattern === origin;
50
+ }
51
+ let regExp = originPatternCache.get(pattern);
52
+ if (!regExp) {
53
+ regExp = createOriginRegExp(pattern);
54
+ originPatternCache.set(pattern, regExp);
55
+ }
56
+ return regExp.test(origin);
57
+ });
58
+ };
59
+
60
+ /**
61
+ * Determines the origin of the document that embeds the current page. Since allowed origins can be
62
+ * patterns, and patterns are no valid postMessage targets, this is the only way to address a parent
63
+ * that has not sent a message yet.
64
+ * @returns The referrer origin, or null when it can't be determined.
65
+ */
66
+ export const readReferrerOrigin = () => {
67
+ try {
68
+ return new URL(document.referrer).origin;
69
+ } catch (e) {
70
+ return null;
71
+ }
72
+ };
@@ -20,4 +20,17 @@ export declare const isFrontendSettingsAdminPreviewActive: () => boolean;
20
20
  * @returns True if the app must not navigate, false otherwise.
21
21
  */
22
22
  export declare const isNavigationBlocked: () => boolean;
23
+ /**
24
+ * Checks if an origin is covered by a list of allowed origin patterns. See
25
+ * ALLOWED_ADMIN_PREVIEW_ORIGINS for details about the supported wildcard syntax.
26
+ * @param origin The origin to check e.g. "https://app.shopgate.com".
27
+ * @param patterns The allowed origin patterns.
28
+ * @returns Whether the origin is allowed.
29
+ */
30
+ export declare const isAllowedOrigin: (origin: string | null | undefined, patterns?: string[]) => origin is string;
31
+ /**
32
+ * Determines the origin of the document that embeds the current page.
33
+ * @returns The referrer origin, or null when it can't be determined.
34
+ */
35
+ export declare const getReferrerOrigin: () => string | null;
23
36
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/helpers/index.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAClB,CAAC;AAEvB;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAO,OAClB,CAAC;AAE3B;;;GAGG;AACH,eAAO,MAAM,oCAAoC,QAAO,OAClB,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAO,OACX,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/helpers/index.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAAO,OAClB,CAAC;AAEvB;;;GAGG;AACH,eAAO,MAAM,wBAAwB,QAAO,OAClB,CAAC;AAE3B;;;GAGG;AACH,eAAO,MAAM,oCAAoC,QAAO,OAClB,CAAC;AAEvC;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAO,OACX,CAAC;AAE7B;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,WAAU,MAAM,EAAO,KACtB,MAAM,IAAI,MAAgD,CAAC;AAE9D;;;GAGG;AACH,eAAO,MAAM,iBAAiB,QAAO,MAAM,GAAG,IAA4B,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import { detectAdminPreview, detectFrontendSettingsAdminPreview, detectPageAdminPreview } from "./_internal/detection";
2
+ import { matchesAllowedOrigin, readReferrerOrigin } from "./_internal/origins";
2
3
 
3
4
  /**
4
5
  * Checks if the app is currently in admin preview mode (pages or frontend settings).
@@ -24,4 +25,19 @@ export const isFrontendSettingsAdminPreviewActive = () => detectFrontendSettings
24
25
  * Frontend settings preview is not affected - it previews styling on the regular app.
25
26
  * @returns True if the app must not navigate, false otherwise.
26
27
  */
27
- export const isNavigationBlocked = () => isPageAdminPreviewActive();
28
+ export const isNavigationBlocked = () => isPageAdminPreviewActive();
29
+
30
+ /**
31
+ * Checks if an origin is covered by a list of allowed origin patterns. See
32
+ * ALLOWED_ADMIN_PREVIEW_ORIGINS for details about the supported wildcard syntax.
33
+ * @param origin The origin to check e.g. "https://app.shopgate.com".
34
+ * @param patterns The allowed origin patterns.
35
+ * @returns Whether the origin is allowed.
36
+ */
37
+ export const isAllowedOrigin = (origin, patterns = []) => matchesAllowedOrigin(origin, patterns);
38
+
39
+ /**
40
+ * Determines the origin of the document that embeds the current page.
41
+ * @returns The referrer origin, or null when it can't be determined.
42
+ */
43
+ export const getReferrerOrigin = () => readReferrerOrigin();
@@ -13,24 +13,26 @@ export interface IframeMessengerResult<TMessage extends MessageData = MessageDat
13
13
  /**
14
14
  * Send data up to window.parent.
15
15
  * If targetOrigin is omitted, uses the most recently seen origin
16
- * (from an incoming message). If none was seen yet, falls back to
17
- * the origin derived from document.referrer, then parentOrigins[0], then "*".
16
+ * (from an incoming message), otherwise the origin derived from document.referrer.
17
+ * Nothing is sent when neither of them is covered by parentOrigins.
18
18
  */
19
19
  sendToParent: (data: TMessage, targetOrigin?: string) => void;
20
20
  }
21
21
  /**
22
22
  * Hook for postMessage communication when your component is inside an iframe.
23
23
  *
24
- * Listens on window for "message" events from any origin in parentOrigins,
25
- * and only calls onMessage(data, rawEvent) if both origin and source match.
24
+ * Listens on window for "message" events, and only calls onMessage(data, rawEvent) if the origin
25
+ * of the event is covered by parentOrigins and the message came from window.parent.
26
26
  *
27
27
  * @param onMessage Callback invoked when a trusted message arrives.
28
28
  * Receives data and the raw event so consumers can inspect origin, source,
29
29
  * and other metadata when needed.
30
- * @param parentOrigins Array of allowed parent origin strings, e.g.
31
- * ['https://a.example.com', 'https://b.example.com'].
30
+ * @param parentOrigins Array of allowed parent origin patterns, e.g.
31
+ * ['https://a.example.com', 'https://*.example.com']. See ALLOWED_ADMIN_PREVIEW_ORIGINS for
32
+ * details about the supported wildcard syntax.
33
+ * @param enabled Whether the message listener is supposed to be attached.
32
34
  * @returns An object with a single method:
33
35
  * sendToParent(data, [targetOrigin]) to post data to the parent window.
34
36
  */
35
- export declare function useIframeMessenger<TMessage extends MessageData = MessageData>(onMessage: OnIframeMessage<TMessage>, parentOrigins: string[]): IframeMessengerResult<TMessage>;
37
+ export declare function useIframeMessenger<TMessage extends MessageData = MessageData>(onMessage: OnIframeMessage<TMessage>, parentOrigins: string[], enabled?: boolean): IframeMessengerResult<TMessage>;
36
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/hooks/index.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI,CACxE,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,KAC7B,IAAI,CAAC;AAEV,MAAM,WAAW,qBAAqB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW;IAC/E;;;;;OAKG;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,EAC3E,SAAS,EAAE,eAAe,CAAC,QAAQ,CAAC,EACpC,aAAa,EAAE,MAAM,EAAE,GACtB,qBAAqB,CAAC,QAAQ,CAAC,CAmEjC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../admin-preview/hooks/index.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;OAEG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,MAAM,eAAe,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI,CACxE,IAAI,EAAE,QAAQ,EACd,QAAQ,EAAE,YAAY,CAAC,QAAQ,CAAC,KAC7B,IAAI,CAAC;AAEV,MAAM,WAAW,qBAAqB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW;IAC/E;;;;;OAKG;IACH,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,EAC3E,SAAS,EAAE,eAAe,CAAC,QAAQ,CAAC,EACpC,aAAa,EAAE,MAAM,EAAE,EACvB,OAAO,UAAO,GACb,qBAAqB,CAAC,QAAQ,CAAC,CAkEjC"}
@@ -1,24 +1,23 @@
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 { useCallback, useEffect, useRef } from 'react';
6
2
  import { logger } from '@shopgate/engage/core/helpers';
3
+ import { getReferrerOrigin, isAllowedOrigin } from "../helpers";
7
4
  /**
8
5
  * Hook for postMessage communication when your component is inside an iframe.
9
6
  *
10
- * Listens on window for "message" events from any origin in parentOrigins,
11
- * and only calls onMessage(data, rawEvent) if both origin and source match.
7
+ * Listens on window for "message" events, and only calls onMessage(data, rawEvent) if the origin
8
+ * of the event is covered by parentOrigins and the message came from window.parent.
12
9
  *
13
10
  * @param onMessage Callback invoked when a trusted message arrives.
14
11
  * Receives data and the raw event so consumers can inspect origin, source,
15
12
  * and other metadata when needed.
16
- * @param parentOrigins Array of allowed parent origin strings, e.g.
17
- * ['https://a.example.com', 'https://b.example.com'].
13
+ * @param parentOrigins Array of allowed parent origin patterns, e.g.
14
+ * ['https://a.example.com', 'https://*.example.com']. See ALLOWED_ADMIN_PREVIEW_ORIGINS for
15
+ * details about the supported wildcard syntax.
16
+ * @param enabled Whether the message listener is supposed to be attached.
18
17
  * @returns An object with a single method:
19
18
  * sendToParent(data, [targetOrigin]) to post data to the parent window.
20
19
  */
21
- export function useIframeMessenger(onMessage, parentOrigins) {
20
+ export function useIframeMessenger(onMessage, parentOrigins, enabled = true) {
22
21
  // Keep a ref to the latest onMessage callback so the listener always has it.
23
22
  const onMessageRef = useRef(onMessage);
24
23
  useEffect(() => {
@@ -31,15 +30,15 @@ export function useIframeMessenger(onMessage, parentOrigins) {
31
30
  /**
32
31
  * Send a message up to the parent window.
33
32
  * @param data - The data object to post.
34
- * @param targetOrigin Optional override for the origin to post to.
35
- * If omitted, uses the last seen origin, then referrer origin,
36
- * then parentOrigins[0], then "*".
33
+ * @param targetOrigin Optional override for the origin to post to. Must be covered by
34
+ * parentOrigins. If omitted, uses the last seen origin, then the referrer origin.
37
35
  */
38
36
  const sendToParent = useCallback((data, targetOrigin) => {
39
- // Determine which origin to use: explicit, then last seen, then first, then "*".
40
- const originToUse = typeof targetOrigin === 'string' ? targetOrigin : lastOriginRef.current ?? (document.referrer ? new URL(document.referrer).origin : undefined) ?? parentOrigins[0] ?? '*';
37
+ // Determine which origin to use: explicit, then last seen, then the embedding document.
38
+ // Patterns are no valid postMessage targets, so an unresolved origin aborts the send.
39
+ const originToUse = [targetOrigin, lastOriginRef.current, getReferrerOrigin()].find(origin => isAllowedOrigin(origin, parentOrigins));
41
40
  if (!originToUse) {
42
- logger.warn('useIframeMessenger: no targetOrigin available. ' + 'Provide parentOrigins or pass targetOrigin.');
41
+ logger.warn('useIframeMessenger: no allowed targetOrigin available. ' + 'Provide parentOrigins or pass an allowed targetOrigin.');
43
42
  return;
44
43
  }
45
44
  window.parent.postMessage(data, originToUse);
@@ -47,13 +46,17 @@ export function useIframeMessenger(onMessage, parentOrigins) {
47
46
 
48
47
  // Attach / detach the "message" listener.
49
48
  useEffect(() => {
49
+ if (!enabled) {
50
+ return undefined;
51
+ }
52
+
50
53
  /**
51
54
  * Handler for incoming postMessage events.
52
55
  * @param rawEvent The original MessageEvent object.
53
56
  */
54
57
  function handler(rawEvent) {
55
- // Only proceed if the origin is in our whitelist.
56
- if (!parentOrigins.includes(rawEvent.origin)) return;
58
+ // Only proceed if the origin is covered by our whitelist.
59
+ if (!isAllowedOrigin(rawEvent.origin, parentOrigins)) return;
57
60
  // Ensure the message actually came from window.parent.
58
61
  if (rawEvent.source !== window.parent) return;
59
62
 
@@ -67,7 +70,7 @@ export function useIframeMessenger(onMessage, parentOrigins) {
67
70
  return () => {
68
71
  window.removeEventListener('message', handler);
69
72
  };
70
- }, [parentOrigins, sendToParent]);
73
+ }, [enabled, parentOrigins]);
71
74
  return {
72
75
  sendToParent
73
76
  };
@@ -8,9 +8,10 @@ interface PreviewStore {
8
8
  * listener only mounts once React committed, so everything the admin sent before that is dropped
9
9
  * and the styling arrives as a visible restyle.
10
10
  *
11
- * The handshake goes to every allowed origin rather than to a single guessed one. Nothing has been
12
- * received at this point, so there is no known parent origin, and the referrer is not guaranteed to
13
- * survive the admin's referrer policy. The browser drops the copies whose target does not match.
11
+ * The handshake goes to the origin of the embedding document. Nothing has been received at this
12
+ * point, so that referrer is the only concrete parent origin available - the allow list holds
13
+ * patterns, and a pattern is no valid postMessage target. An admin that strips its referrer
14
+ * therefore gets no ready message, and the app falls back to the timeout below.
14
15
  *
15
16
  * Resolves when the payload arrived or when REQUEST_TIMEOUT elapsed, and never rejects - an admin
16
17
  * that stays silent has to fall back to the unstyled app, never to an iframe that never renders.
@@ -1 +1 @@
1
- {"version":3,"file":"awaitInitialFrontendSettings.d.ts","sourceRoot":"","sources":["../../../admin-preview/initialization/awaitInitialFrontendSettings.ts"],"names":[],"mappings":"AAmBA,UAAU,YAAY;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAqE3E,CAAC"}
1
+ {"version":3,"file":"awaitInitialFrontendSettings.d.ts","sourceRoot":"","sources":["../../../admin-preview/initialization/awaitInitialFrontendSettings.ts"],"names":[],"mappings":"AAuBA,UAAU,YAAY;IACpB,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,4BAA4B,GAAI,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAyE3E,CAAC"}
@@ -1,7 +1,6 @@
1
- import "core-js/modules/es.array.includes.js";
2
1
  import { withScope, captureMessage, Severity as SentrySeverity } from '@sentry/browser';
3
2
  import { receiveAppSettings } from '@shopgate/engage/settings/action-creators/appSettings';
4
- import { isFrontendSettingsAdminPreviewActive } from "../helpers";
3
+ import { getReferrerOrigin, isAllowedOrigin, isFrontendSettingsAdminPreviewActive } from "../helpers";
5
4
  import { ALLOWED_ADMIN_PREVIEW_ORIGINS } from "../constants";
6
5
  import { getOrCreateStyleTag, serializeStyling } from "../components/FrontendSettingsPreviewBridge/helpers";
7
6
  import { setInitialFrontendSettings } from "./_internal/initialFrontendSettings";
@@ -13,9 +12,10 @@ const REQUEST_TIMEOUT = 3000;
13
12
  * listener only mounts once React committed, so everything the admin sent before that is dropped
14
13
  * and the styling arrives as a visible restyle.
15
14
  *
16
- * The handshake goes to every allowed origin rather than to a single guessed one. Nothing has been
17
- * received at this point, so there is no known parent origin, and the referrer is not guaranteed to
18
- * survive the admin's referrer policy. The browser drops the copies whose target does not match.
15
+ * The handshake goes to the origin of the embedding document. Nothing has been received at this
16
+ * point, so that referrer is the only concrete parent origin available - the allow list holds
17
+ * patterns, and a pattern is no valid postMessage target. An admin that strips its referrer
18
+ * therefore gets no ready message, and the app falls back to the timeout below.
19
19
  *
20
20
  * Resolves when the payload arrived or when REQUEST_TIMEOUT elapsed, and never rejects - an admin
21
21
  * that stays silent has to fall back to the unstyled app, never to an iframe that never renders.
@@ -51,7 +51,7 @@ export const awaitInitialFrontendSettings = store => new Promise(resolve => {
51
51
  * @param event The message event.
52
52
  */
53
53
  function handleMessage(event) {
54
- if (!ALLOWED_ADMIN_PREVIEW_ORIGINS.includes(event.origin)) return;
54
+ if (!isAllowedOrigin(event.origin, ALLOWED_ADMIN_PREVIEW_ORIGINS)) return;
55
55
  if (event.source !== window.parent) return;
56
56
  const {
57
57
  data
@@ -76,9 +76,13 @@ export const awaitInitialFrontendSettings = store => new Promise(resolve => {
76
76
  });
77
77
  settle();
78
78
  }, REQUEST_TIMEOUT);
79
- ALLOWED_ADMIN_PREVIEW_ORIGINS.forEach(origin => {
79
+
80
+ // Allowed origins can be patterns, which are no valid postMessage targets, so the ready
81
+ // message goes to the document that embedded us - as long as it is an allowed origin.
82
+ const parentOrigin = getReferrerOrigin();
83
+ if (isAllowedOrigin(parentOrigin, ALLOWED_ADMIN_PREVIEW_ORIGINS)) {
80
84
  window.parent.postMessage({
81
85
  type: 'frontendSettingsPreviewReady'
82
- }, origin);
83
- });
86
+ }, parentOrigin);
87
+ }
84
88
  });
@@ -5,8 +5,15 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  const useStyles = makeStyles()(theme => ({
6
6
  item: {
7
7
  background: theme.palette.background.surface,
8
- marginTop: 4,
9
- borderTop: `1px solid ${theme.components.separatorLine.borderColor}`
8
+ position: 'relative',
9
+ '&::before': {
10
+ content: '""',
11
+ position: 'absolute',
12
+ top: 0,
13
+ left: theme.spacing(2),
14
+ right: theme.spacing(2),
15
+ borderTop: `1px solid ${theme.components.separatorLine.borderColor}`
16
+ }
10
17
  }
11
18
  }));
12
19
 
@@ -0,0 +1,21 @@
1
+ export interface FilterApplyButtonProps {
2
+ /**
3
+ * Click handler for the button.
4
+ */
5
+ onClick: () => void;
6
+ /**
7
+ * Widget settings injected by `withWidgetSettings`, forwarded to the portal.
8
+ */
9
+ widgetSettings?: Record<string, unknown>;
10
+ /**
11
+ * Class name for the button.
12
+ */
13
+ className?: string;
14
+ /**
15
+ * Whether the button is disabled.
16
+ */
17
+ disabled?: boolean;
18
+ }
19
+ declare const _default: JSX;
20
+ export default _default;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../filter/components/FilterPageContent/components/ApplyButton/index.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;;AAgCD,wBAAiG"}
@@ -1,64 +1,37 @@
1
- import React, { memo } from 'react';
2
- import PropTypes from 'prop-types';
1
+ import { memo } from 'react';
3
2
  import { I18n, SurroundPortals } from '@shopgate/engage/components';
4
3
  import { Button } from '@shopgate/engage/components/v2';
5
- import { makeStyles } from '@shopgate/engage/styles';
6
4
  import { PORTAL_FILTER_APPLY_BUTTON } from '@shopgate/engage/filter/constants';
7
5
  import { withWidgetSettings } from '@shopgate/engage/core';
8
6
  import { jsx as _jsx } from "react/jsx-runtime";
9
- const useStyles = makeStyles()(theme => ({
10
- wrapper: {
11
- display: 'flex',
12
- marginRight: theme.spacing(2)
13
- }
14
- }));
15
-
16
7
  /**
17
- * The filter apply button component.
18
- * @param {Object} props The component props
19
- * @param {boolean} props.disabled Whether the button is disabled
20
- * @param {Function} props.onClick Click handler for the button
21
- * @returns {JSX.Element}
8
+ * The filter apply button, rendered as "View Results" in the filter button bar.
9
+ * @returns The rendered component.
22
10
  */
23
11
  const FilterApplyButton = ({
24
- disabled,
12
+ disabled = false,
25
13
  onClick,
14
+ className,
26
15
  widgetSettings
27
- }) => {
28
- const {
29
- classes
30
- } = useStyles();
31
- const {
32
- buttonTextColor,
33
- buttonTextColorDisabled
34
- } = widgetSettings;
35
- const buttonColor = !disabled ? buttonTextColor : buttonTextColorDisabled;
36
- return /*#__PURE__*/_jsx(SurroundPortals, {
37
- portalName: PORTAL_FILTER_APPLY_BUTTON,
38
- portalProps: {
39
- disabled,
40
- onClick,
41
- widgetSettings
42
- },
43
- children: /*#__PURE__*/_jsx("div", {
44
- className: classes.wrapper,
45
- children: /*#__PURE__*/_jsx(Button, {
46
- variant: "link",
47
- color: "secondary",
48
- onClick: onClick,
49
- disabled: disabled,
50
- testId: "applyFilterButton",
51
- children: /*#__PURE__*/_jsx(I18n.Text, {
52
- string: "filter.apply",
53
- style: {
54
- color: buttonColor
55
- }
56
- })
57
- })
16
+ }) => /*#__PURE__*/_jsx(SurroundPortals, {
17
+ portalName: PORTAL_FILTER_APPLY_BUTTON,
18
+ portalProps: {
19
+ disabled,
20
+ onClick,
21
+ widgetSettings
22
+ },
23
+ children: /*#__PURE__*/_jsx(Button, {
24
+ className: className,
25
+ variant: "contained",
26
+ color: "primary",
27
+ onClick: onClick,
28
+ disabled: disabled,
29
+ fullWidth: true,
30
+ size: "small",
31
+ testId: "applyFilterButton",
32
+ children: /*#__PURE__*/_jsx(I18n.Text, {
33
+ string: "filter.view_results"
58
34
  })
59
- });
60
- };
61
- FilterApplyButton.defaultProps = {
62
- disabled: false
63
- };
35
+ })
36
+ });
64
37
  export default withWidgetSettings(/*#__PURE__*/memo(FilterApplyButton), '@shopgate/engage/components/AppBar');
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The filter button bar renders the "View Results" and "Clear all" buttons in a sticky bar at the
3
+ * bottom of the screen. It's rendered into the app footer via a portal.
4
+ * @returns The rendered component, or `null` when the app footer is not available.
5
+ */
6
+ declare const FilterButtonBar: () => import("react").ReactPortal | null;
7
+ export default FilterButtonBar;
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../filter/components/FilterPageContent/components/FilterButtonBar/index.tsx"],"names":[],"mappings":"AA0BA;;;;GAIG;AACH,QAAA,MAAM,eAAe,0CAkCpB,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { createPortal } from 'react-dom';
2
+ import { ResponsiveContainer } from '@shopgate/engage/components';
3
+ import { makeStyles } from '@shopgate/engage/styles';
4
+ import { useFilterPage } from '@shopgate/engage/filter/hooks';
5
+ import ApplyButton from "../ApplyButton";
6
+ import ResetButton from "../ResetButton";
7
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
+ const useStyles = makeStyles()(theme => ({
9
+ bar: {
10
+ display: 'flex',
11
+ gap: theme.spacing(1.5),
12
+ padding: theme.spacing(1.5, 2),
13
+ background: theme.palette.background.surface,
14
+ boxShadow: '0 -4px 5px -2px rgba(0, 0, 0, 0.1)',
15
+ position: 'relative',
16
+ zIndex: 2,
17
+ paddingBottom: `calc(${theme.spacing(1.5)}px + ${theme.layout.safeArea.bottom})`
18
+ },
19
+ apply: {
20
+ flex: 2
21
+ },
22
+ reset: {
23
+ flex: 1
24
+ }
25
+ }));
26
+
27
+ /**
28
+ * The filter button bar renders the "View Results" and "Clear all" buttons in a sticky bar at the
29
+ * bottom of the screen. It's rendered into the app footer via a portal.
30
+ * @returns The rendered component, or `null` when the app footer is not available.
31
+ */
32
+ const FilterButtonBar = () => {
33
+ const {
34
+ classes
35
+ } = useStyles();
36
+ const {
37
+ hasChanged,
38
+ resetPossible,
39
+ applyFilters,
40
+ resetAllFilters
41
+ } = useFilterPage();
42
+ const domElement = typeof document !== 'undefined' ? document.getElementById('AppFooter') : null;
43
+ if (!domElement) {
44
+ return null;
45
+ }
46
+ return /*#__PURE__*/createPortal(/*#__PURE__*/_jsx(ResponsiveContainer, {
47
+ appAlways: true,
48
+ breakpoint: "<sm",
49
+ children: /*#__PURE__*/_jsxs("div", {
50
+ className: classes.bar,
51
+ children: [/*#__PURE__*/_jsx(ApplyButton, {
52
+ className: classes.apply,
53
+ disabled: !hasChanged,
54
+ onClick: applyFilters
55
+ }), /*#__PURE__*/_jsx(ResetButton, {
56
+ className: classes.reset,
57
+ disabled: !resetPossible,
58
+ onClick: resetAllFilters
59
+ })]
60
+ })
61
+ }), domElement);
62
+ };
63
+ export default FilterButtonBar;
@@ -0,0 +1,17 @@
1
+ export interface FilterResetButtonProps {
2
+ /**
3
+ * Click handler for the button.
4
+ */
5
+ onClick: () => void;
6
+ /**
7
+ * Class name for the button.
8
+ */
9
+ className?: string;
10
+ /**
11
+ * Whether the button is disabled.
12
+ */
13
+ disabled?: boolean;
14
+ }
15
+ declare const _default: import("react").MemoExoticComponent<({ disabled, onClick, className }: FilterResetButtonProps) => import("react").JSX.Element>;
16
+ export default _default;
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../filter/components/FilterPageContent/components/ResetButton/index.tsx"],"names":[],"mappings":"AAKA,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;+FAMoE,sBAAsB;AAsB3F,wBAAuC"}