@pushwoosh/dumb-components 1.1.171 → 1.1.173

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 (31) hide show
  1. package/NativeRichMediaPreview/NativeRichMediaPreview.js +7 -12
  2. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/NativeRichMediaContentImg.d.ts +3 -0
  3. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/NativeRichMediaContentImg.js +21 -0
  4. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/index.d.ts +1 -0
  5. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/index.js +1 -0
  6. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/styles.d.ts +1 -0
  7. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/styles.js +5 -0
  8. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/types.d.ts +5 -0
  9. package/NativeRichMediaPreview/components/NativeRichMediaContentImg/types.js +1 -0
  10. package/NativeRichMediaPreview/views/NativeRichMediaCarouselView/NativeRichMediaCarouselView.js +9 -11
  11. package/NativeRichMediaPreview/views/NativeRichMediaCarouselView/styles.d.ts +0 -1
  12. package/NativeRichMediaPreview/views/NativeRichMediaCarouselView/styles.js +3 -7
  13. package/NativeRichMediaPreview/views/NativeRichMediaModalView/NativeRichMediaModalView.js +2 -3
  14. package/NativeRichMediaPreview/views/NativeRichMediaSheetView/NativeRichMediaSheetView.js +2 -3
  15. package/NativeRichMediaPreview/views/shared/styles.d.ts +0 -3
  16. package/NativeRichMediaPreview/views/shared/styles.js +2 -8
  17. package/RichmediaPreview/RichmediaPreview.d.ts +4 -2
  18. package/RichmediaPreview/RichmediaPreview.js +6 -15
  19. package/RichmediaPreview/styles.d.ts +2 -1
  20. package/RichmediaPreview/styles.js +2 -1
  21. package/SandboxedHtmlFrame/SandboxedHtmlFrame.d.ts +16 -0
  22. package/SandboxedHtmlFrame/SandboxedHtmlFrame.js +55 -0
  23. package/SandboxedHtmlFrame/__tests__/SandboxedHtmlFrame.test.d.ts +1 -0
  24. package/SandboxedHtmlFrame/__tests__/SandboxedHtmlFrame.test.js +57 -0
  25. package/SandboxedHtmlFrame/index.d.ts +2 -0
  26. package/SandboxedHtmlFrame/index.js +1 -0
  27. package/SandboxedHtmlFrame/types.d.ts +33 -0
  28. package/SandboxedHtmlFrame/types.js +1 -0
  29. package/index.d.ts +1 -0
  30. package/index.js +1 -0
  31. package/package.json +1 -1
@@ -11,7 +11,6 @@ import { parseNativeRichMediaConfig } from './parseNativeRichMediaConfig';
11
11
  import { resolveLocalization } from './resolveLocalization';
12
12
  import { BackdropLayer, ContentFlow, PreviewHost, ScrollLayer, SpinnerLayer } from './styles';
13
13
  import { NativeRichMediaBannerView, NativeRichMediaCarouselView, NativeRichMediaFullscreenView, NativeRichMediaInvalidView, NativeRichMediaModalView, NativeRichMediaPipView, NativeRichMediaScratchcardView, NativeRichMediaSheetView, NativeRichMediaSpinwheelView, NativeRichMediaStoriesView, NativeRichMediaVideoView } from './views';
14
- import { GradientOverlay } from './views/shared/styles';
15
14
  function dimsScreen(config) {
16
15
  if (!config) {
17
16
  return false;
@@ -50,16 +49,12 @@ function justifyFor(config) {
50
49
  return 'center';
51
50
  }
52
51
  }
53
- function renderCoverBackdrop(url, backgroundColor, overlayTopOffset) {
54
- return _jsxs(_Fragment, {
55
- children: [_jsx(NativeRichMediaCoverImg, {
56
- url: url,
57
- position: "absolute",
58
- inset: true,
59
- backgroundColor: backgroundColor
60
- }), _jsx(GradientOverlay, {
61
- "$topOffset": overlayTopOffset
62
- })]
52
+ function renderCoverBackdrop(url, backgroundColor) {
53
+ return _jsx(NativeRichMediaCoverImg, {
54
+ url: url,
55
+ position: "absolute",
56
+ inset: true,
57
+ backgroundColor: backgroundColor
63
58
  });
64
59
  }
65
60
  function renderBackdrop(config, activeIndex) {
@@ -72,7 +67,7 @@ function renderBackdrop(config, activeIndex) {
72
67
  {
73
68
  var _items$clampIndex;
74
69
  const items = config.stories.items;
75
- return renderCoverBackdrop((_items$clampIndex = items[clampIndex(activeIndex, items.length)]) === null || _items$clampIndex === void 0 ? void 0 : _items$clampIndex.image, '#000', '55%');
70
+ return renderCoverBackdrop((_items$clampIndex = items[clampIndex(activeIndex, items.length)]) === null || _items$clampIndex === void 0 ? void 0 : _items$clampIndex.image, '#000');
76
71
  }
77
72
  default:
78
73
  return null;
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { NativeRichMediaContentImgProps } from './types';
3
+ export declare function NativeRichMediaContentImg(props: NativeRichMediaContentImgProps): ReactElement;
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Color } from '@pushwoosh/kit-constants';
3
+ import { Horizontal } from '@pushwoosh/kit-helpers';
4
+ import { Img } from './styles';
5
+ export function NativeRichMediaContentImg(props) {
6
+ const {
7
+ url,
8
+ margin,
9
+ borderRadius = 18
10
+ } = props;
11
+ return _jsx(Horizontal, {
12
+ "$margin": margin,
13
+ "$borderRadius": borderRadius,
14
+ "$bgColor": Color.FROZEN,
15
+ "$overflow": "hidden",
16
+ children: _jsx(Img, {
17
+ src: url,
18
+ alt: ""
19
+ })
20
+ });
21
+ }
@@ -0,0 +1 @@
1
+ export { NativeRichMediaContentImg } from './NativeRichMediaContentImg';
@@ -0,0 +1 @@
1
+ export { NativeRichMediaContentImg } from './NativeRichMediaContentImg';
@@ -0,0 +1 @@
1
+ export declare const Img: import("styled-components").StyledComponent<"img", any, {}, never>;
@@ -0,0 +1,5 @@
1
+ import styled from 'styled-components';
2
+ export const Img = styled.img.withConfig({
3
+ displayName: "Img",
4
+ componentId: "sc-1q27duj-0"
5
+ })(["display:block;width:100%;height:auto;"]);
@@ -0,0 +1,5 @@
1
+ export type NativeRichMediaContentImgProps = {
2
+ url?: string;
3
+ margin?: string;
4
+ borderRadius?: number;
5
+ };
@@ -1,5 +1,5 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { Card, Dot, Dots, GradientOverlay, TextBlock } from './styles';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, Dot, Dots, TextBlock } from './styles';
3
3
  import { NativeRichMediaCloseChip } from '../../components/NativeRichMediaCloseChip';
4
4
  import { NativeRichMediaCoverImg } from '../../components/NativeRichMediaCoverImg';
5
5
  import { NativeRichMediaText } from '../../components/NativeRichMediaText';
@@ -20,15 +20,13 @@ export function NativeRichMediaCarouselView(props) {
20
20
  url: item.image,
21
21
  position: "absolute",
22
22
  inset: true
23
- }), (title.text || message.text) && _jsxs(_Fragment, {
24
- children: [_jsx(GradientOverlay, {}), _jsxs(TextBlock, {
25
- children: [title.text && _jsx(NativeRichMediaTitle, {
26
- "$color": title.color,
27
- children: title.text
28
- }), message.text && _jsx(NativeRichMediaText, {
29
- "$color": message.color,
30
- children: message.text
31
- })]
23
+ }), (title.text || message.text) && _jsxs(TextBlock, {
24
+ children: [title.text && _jsx(NativeRichMediaTitle, {
25
+ "$color": title.color,
26
+ children: title.text
27
+ }), message.text && _jsx(NativeRichMediaText, {
28
+ "$color": message.color,
29
+ children: message.text
32
30
  })]
33
31
  }), items.length > 1 && _jsx(Dots, {
34
32
  children: items.map((_, i) => _jsx(Dot
@@ -1,5 +1,4 @@
1
1
  export declare const Card: import("styled-components").StyledComponent<"div", any, {}, never>;
2
- export declare const GradientOverlay: import("styled-components").StyledComponent<"div", any, {}, never>;
3
2
  export declare const TextBlock: import("styled-components").StyledComponent<"div", any, {}, never>;
4
3
  export declare const Dots: import("styled-components").StyledComponent<"div", any, {}, never>;
5
4
  export declare const Dot: import("styled-components").StyledComponent<"div", any, {
@@ -3,21 +3,17 @@ export const Card = styled.div.withConfig({
3
3
  displayName: "Card",
4
4
  componentId: "sc-15b2cux-0"
5
5
  })(["position:relative;margin:0 12px;height:461px;border-radius:24px;background:#000;overflow:hidden;box-shadow:0 16px 28px rgba(0,0,0,0.28);"]);
6
- export const GradientOverlay = styled.div.withConfig({
7
- displayName: "GradientOverlay",
8
- componentId: "sc-15b2cux-1"
9
- })(["position:absolute;left:0;right:0;bottom:0;height:140px;background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,0.75));"]);
10
6
  export const TextBlock = styled.div.withConfig({
11
7
  displayName: "TextBlock",
12
- componentId: "sc-15b2cux-2"
8
+ componentId: "sc-15b2cux-1"
13
9
  })(["position:absolute;left:18px;right:18px;bottom:36px;"]);
14
10
  export const Dots = styled.div.withConfig({
15
11
  displayName: "Dots",
16
- componentId: "sc-15b2cux-3"
12
+ componentId: "sc-15b2cux-2"
17
13
  })(["position:absolute;left:0;right:0;bottom:12px;text-align:center;font-size:0;"]);
18
14
  export const Dot = styled.div.withConfig({
19
15
  displayName: "Dot",
20
- componentId: "sc-15b2cux-4"
16
+ componentId: "sc-15b2cux-3"
21
17
  })(["display:inline-block;width:7px;height:7px;border-radius:50%;margin:0 4.5px;background:", ";"], ({
22
18
  $active
23
19
  }) => $active ? '#FFF' : 'rgba(255,255,255,0.4)');
@@ -3,7 +3,7 @@ import { Vertical } from '@pushwoosh/kit-helpers';
3
3
  import { Card } from './styles';
4
4
  import { NativeRichMediaButtonList } from '../../components/NativeRichMediaButtonList';
5
5
  import { NativeRichMediaCloseChip } from '../../components/NativeRichMediaCloseChip';
6
- import { NativeRichMediaCoverImg } from '../../components/NativeRichMediaCoverImg';
6
+ import { NativeRichMediaContentImg } from '../../components/NativeRichMediaContentImg';
7
7
  import { NativeRichMediaText } from '../../components/NativeRichMediaText';
8
8
  import { NativeRichMediaTitle } from '../../components/NativeRichMediaTitle';
9
9
  import { asButtonList, asTextValue } from '../../helpers';
@@ -17,10 +17,9 @@ export function NativeRichMediaModalView(props) {
17
17
  const message = asTextValue(block.message);
18
18
  return _jsxs(Card, {
19
19
  "$background": block.background,
20
- children: [block.image && _jsx(NativeRichMediaCoverImg, {
20
+ children: [block.image && _jsx(NativeRichMediaContentImg, {
21
21
  url: block.image,
22
22
  margin: "14px 14px 0",
23
- height: 179,
24
23
  borderRadius: 18
25
24
  }), (title.text || message.text) && _jsxs(Vertical, {
26
25
  "$padding": (block.image ? '20px' : '30px') + ' 24px 0',
@@ -3,7 +3,7 @@ import { Vertical } from '@pushwoosh/kit-helpers';
3
3
  import { Handle, Sheet } from './styles';
4
4
  import { NativeRichMediaButtonList } from '../../components/NativeRichMediaButtonList';
5
5
  import { NativeRichMediaCloseChip } from '../../components/NativeRichMediaCloseChip';
6
- import { NativeRichMediaCoverImg } from '../../components/NativeRichMediaCoverImg';
6
+ import { NativeRichMediaContentImg } from '../../components/NativeRichMediaContentImg';
7
7
  import { NativeRichMediaText } from '../../components/NativeRichMediaText';
8
8
  import { NativeRichMediaTitle } from '../../components/NativeRichMediaTitle';
9
9
  import { asButtonList, asTextValue } from '../../helpers';
@@ -17,10 +17,9 @@ export function NativeRichMediaSheetView(props) {
17
17
  const message = asTextValue(block.message);
18
18
  return _jsxs(Sheet, {
19
19
  "$background": block.background,
20
- children: [_jsx(Handle, {}), block.image && _jsx(NativeRichMediaCoverImg, {
20
+ children: [_jsx(Handle, {}), block.image && _jsx(NativeRichMediaContentImg, {
21
21
  url: block.image,
22
22
  margin: "26px 14px 0",
23
- height: 190,
24
23
  borderRadius: 18
25
24
  }), (title.text || message.text) && _jsxs(Vertical, {
26
25
  "$padding": (block.image ? '16px' : '20px') + ' 24px 0',
@@ -1,6 +1,3 @@
1
- export declare const GradientOverlay: import("styled-components").StyledComponent<"div", any, {
2
- $topOffset?: string;
3
- }, never>;
4
1
  export declare const TextBlock: import("styled-components").StyledComponent<"div", any, {}, never>;
5
2
  export declare const Card: import("styled-components").StyledComponent<"div", any, {
6
3
  $background: string;
@@ -1,18 +1,12 @@
1
1
  import styled from 'styled-components';
2
2
  import { EDGE_INSET } from '../../helpers';
3
- export const GradientOverlay = styled.div.withConfig({
4
- displayName: "GradientOverlay",
5
- componentId: "sc-x5bn03-0"
6
- })(["position:absolute;left:0;right:0;bottom:0;top:", ";background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,0.75));"], ({
7
- $topOffset
8
- }) => $topOffset ?? '45%');
9
3
  export const TextBlock = styled.div.withConfig({
10
4
  displayName: "TextBlock",
11
- componentId: "sc-x5bn03-1"
5
+ componentId: "sc-x5bn03-0"
12
6
  })(["margin:0 ", "px ", "px;"], EDGE_INSET, EDGE_INSET);
13
7
  export const Card = styled.div.withConfig({
14
8
  displayName: "Card",
15
- componentId: "sc-x5bn03-2"
9
+ componentId: "sc-x5bn03-1"
16
10
  })(["position:relative;margin:0 12px;border-radius:24px;box-shadow:0 16px 28px rgba(0,0,0,0.28);padding:", ";background:", ";"], ({
17
11
  $padding
18
12
  }) => $padding, ({
@@ -1,8 +1,10 @@
1
1
  import { type ReactElement } from 'react';
2
2
  import type { RichmediaPreviewProps } from './types';
3
3
  /**
4
- * Renders a rich-media (in-app) template in a sandboxed iframe, resolving params and
5
- * localization via {@link useRichmediaHtml}.
4
+ * Renders a rich-media (in-app) template in a fully isolated sandboxed iframe, resolving
5
+ * params and localization via {@link useRichmediaHtml}. Template scripts run in an opaque
6
+ * origin (via {@link SandboxedHtmlFrame}) and cannot reach the control-panel session,
7
+ * cookies, storage or parent DOM.
6
8
  *
7
9
  * @example
8
10
  * <RichmediaPreview name="Promo" content={html} params={params} language="en" />
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from 'react';
3
2
  import { Color } from '@pushwoosh/kit-constants';
4
3
  import { Spinner } from '../Spinner';
5
4
  import { useRichmediaHtml } from './hooks';
6
5
  import { RichMediaPreviewContainer, RichMediaPreviewIframe } from './styles';
7
6
  /**
8
- * Renders a rich-media (in-app) template in a sandboxed iframe, resolving params and
9
- * localization via {@link useRichmediaHtml}.
7
+ * Renders a rich-media (in-app) template in a fully isolated sandboxed iframe, resolving
8
+ * params and localization via {@link useRichmediaHtml}. Template scripts run in an opaque
9
+ * origin (via {@link SandboxedHtmlFrame}) and cannot reach the control-panel session,
10
+ * cookies, storage or parent DOM.
10
11
  *
11
12
  * @example
12
13
  * <RichmediaPreview name="Promo" content={html} params={params} language="en" />
@@ -23,7 +24,6 @@ export function RichmediaPreview(props) {
23
24
  height = 480,
24
25
  isLoading = false
25
26
  } = props;
26
- const $ref = useRef(null);
27
27
  const richmediaHtml = useRichmediaHtml({
28
28
  content,
29
29
  params,
@@ -31,15 +31,6 @@ export function RichmediaPreview(props) {
31
31
  defaultLanguage,
32
32
  baseUrl
33
33
  });
34
- useEffect(() => {
35
- var _$ref$current;
36
- if (!($ref !== null && $ref !== void 0 && (_$ref$current = $ref.current) !== null && _$ref$current !== void 0 && (_$ref$current = _$ref$current.contentWindow) !== null && _$ref$current !== void 0 && _$ref$current.document)) {
37
- return;
38
- }
39
- $ref.current.contentWindow.document.open('text/html', 'replace');
40
- $ref.current.contentWindow.document.write(richmediaHtml);
41
- $ref.current.contentWindow.document.close();
42
- }, [$ref, richmediaHtml]);
43
34
  return _jsx(RichMediaPreviewContainer, {
44
35
  width: width,
45
36
  height: height,
@@ -49,9 +40,9 @@ export function RichmediaPreview(props) {
49
40
  size: "medium",
50
41
  color: Color.LOCKED
51
42
  }) : _jsx(RichMediaPreviewIframe, {
52
- ref: $ref,
43
+ mode: "isolated",
53
44
  title: name,
54
- sandbox: "allow-same-origin allow-scripts"
45
+ html: richmediaHtml
55
46
  })
56
47
  });
57
48
  }
@@ -1,3 +1,4 @@
1
+ import { SandboxedHtmlFrame } from '../SandboxedHtmlFrame';
1
2
  export declare const RichMediaPreviewContainer: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
2
3
  $alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
3
4
  $alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
@@ -14,4 +15,4 @@ export declare const RichMediaPreviewContainer: import("styled-components").Styl
14
15
  height: string | number;
15
16
  $isLoading?: boolean;
16
17
  }, "$gridAutoFlow">;
17
- export declare const RichMediaPreviewIframe: import("styled-components").StyledComponent<"iframe", any, {}, never>;
18
+ export declare const RichMediaPreviewIframe: import("styled-components").StyledComponent<typeof SandboxedHtmlFrame, any, {}, never>;
@@ -1,6 +1,7 @@
1
1
  import styled from 'styled-components';
2
2
  import { Color, ShapeRadius } from '@pushwoosh/kit-constants';
3
3
  import { Vertical, toCssValue } from '@pushwoosh/kit-helpers';
4
+ import { SandboxedHtmlFrame } from '../SandboxedHtmlFrame';
4
5
  export const RichMediaPreviewContainer = styled(Vertical).withConfig({
5
6
  displayName: "RichMediaPreviewContainer",
6
7
  componentId: "sc-1ci6l2b-0"
@@ -11,7 +12,7 @@ export const RichMediaPreviewContainer = styled(Vertical).withConfig({
11
12
  }) => toCssValue(height), ShapeRadius.SECTION, ({
12
13
  $isLoading
13
14
  }) => $isLoading ? Color.FROZEN : Color.CLEAR);
14
- export const RichMediaPreviewIframe = styled.iframe.withConfig({
15
+ export const RichMediaPreviewIframe = styled(SandboxedHtmlFrame).withConfig({
15
16
  displayName: "RichMediaPreviewIframe",
16
17
  componentId: "sc-1ci6l2b-1"
17
18
  })(["position:absolute;left:-50%;top:-50%;width:200%;height:200%;transform:scale(0.51);"]);
@@ -0,0 +1,16 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { SandboxedHtmlFrameProps } from './types';
3
+ /**
4
+ * Renders untrusted HTML inside a sandboxed iframe. The `mode` fixes the security
5
+ * boundary so scripts and same-origin access can never be granted together:
6
+ *
7
+ * - `isolated` — `sandbox="allow-scripts"`; scripts run in an opaque origin and cannot
8
+ * touch the embedding page. The parent gets no DOM access.
9
+ * - `inspectable` — `sandbox="allow-same-origin"`; scripts are disabled, but the parent
10
+ * keeps read access to the frame document via `onReady`.
11
+ *
12
+ * @example
13
+ * <SandboxedHtmlFrame mode="isolated" html={html} title="Preview" />
14
+ * <SandboxedHtmlFrame mode="inspectable" html={html} title="Stats" onReady={decorate} />
15
+ */
16
+ export declare function SandboxedHtmlFrame(props: SandboxedHtmlFrameProps): ReactElement;
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ const SANDBOX = {
4
+ isolated: 'allow-scripts',
5
+ inspectable: 'allow-same-origin'
6
+ };
7
+ /**
8
+ * Renders untrusted HTML inside a sandboxed iframe. The `mode` fixes the security
9
+ * boundary so scripts and same-origin access can never be granted together:
10
+ *
11
+ * - `isolated` — `sandbox="allow-scripts"`; scripts run in an opaque origin and cannot
12
+ * touch the embedding page. The parent gets no DOM access.
13
+ * - `inspectable` — `sandbox="allow-same-origin"`; scripts are disabled, but the parent
14
+ * keeps read access to the frame document via `onReady`.
15
+ *
16
+ * @example
17
+ * <SandboxedHtmlFrame mode="isolated" html={html} title="Preview" />
18
+ * <SandboxedHtmlFrame mode="inspectable" html={html} title="Stats" onReady={decorate} />
19
+ */
20
+ export function SandboxedHtmlFrame(props) {
21
+ const {
22
+ mode,
23
+ html,
24
+ title,
25
+ className
26
+ } = props;
27
+ const onReady = mode === 'inspectable' ? props.onReady : undefined;
28
+ const $ref = useRef(null);
29
+ const [isReady, setIsReady] = useState(false);
30
+ useEffect(() => {
31
+ setIsReady(false);
32
+ }, [html]);
33
+ const handleLoad = useCallback(() => {
34
+ setIsReady(true);
35
+ }, []);
36
+ useEffect(() => {
37
+ var _$ref$current;
38
+ if (!isReady || !onReady) {
39
+ return undefined;
40
+ }
41
+ const frameDocument = (_$ref$current = $ref.current) === null || _$ref$current === void 0 ? void 0 : _$ref$current.contentDocument;
42
+ if (!frameDocument) {
43
+ return undefined;
44
+ }
45
+ return onReady(frameDocument);
46
+ }, [isReady, onReady]);
47
+ return _jsx("iframe", {
48
+ ref: $ref,
49
+ className: className,
50
+ title: title,
51
+ srcDoc: html,
52
+ sandbox: SANDBOX[mode],
53
+ onLoad: handleLoad
54
+ });
55
+ }
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from '@testing-library/react';
3
+ import { SandboxedHtmlFrame } from '../SandboxedHtmlFrame';
4
+ const HTML = '<p>content</p>';
5
+ function getFrame(container) {
6
+ const frame = container.querySelector('iframe');
7
+ if (!frame) {
8
+ throw new Error('iframe not rendered');
9
+ }
10
+ return frame;
11
+ }
12
+ describe('SandboxedHtmlFrame', () => {
13
+ it('isolates scripts in an opaque origin without same-origin access', () => {
14
+ const {
15
+ container
16
+ } = render(_jsx(SandboxedHtmlFrame, {
17
+ mode: "isolated",
18
+ title: "preview",
19
+ html: HTML
20
+ }));
21
+ const frame = getFrame(container);
22
+ expect(frame.getAttribute('sandbox')).toBe('allow-scripts');
23
+ expect(frame.getAttribute('srcdoc')).toBe(HTML);
24
+ expect(frame).toHaveAttribute('title', 'preview');
25
+ });
26
+ it('grants same-origin access but disables scripts in inspectable mode', () => {
27
+ const {
28
+ container
29
+ } = render(_jsx(SandboxedHtmlFrame, {
30
+ mode: "inspectable",
31
+ title: "stats",
32
+ html: HTML
33
+ }));
34
+ const frame = getFrame(container);
35
+ expect(frame.getAttribute('sandbox')).toBe('allow-same-origin');
36
+ expect(frame.getAttribute('sandbox')).not.toContain('allow-scripts');
37
+ });
38
+ it('never emits allow-scripts together with allow-same-origin', () => {
39
+ const isolated = render(_jsx(SandboxedHtmlFrame, {
40
+ mode: "isolated",
41
+ title: "a",
42
+ html: HTML
43
+ }));
44
+ const inspectable = render(_jsx(SandboxedHtmlFrame, {
45
+ mode: "inspectable",
46
+ title: "b",
47
+ html: HTML
48
+ }));
49
+ [isolated, inspectable].forEach(({
50
+ container
51
+ }) => {
52
+ const sandbox = getFrame(container).getAttribute('sandbox') ?? '';
53
+ const hasBoth = sandbox.includes('allow-scripts') && sandbox.includes('allow-same-origin');
54
+ expect(hasBoth).toBe(false);
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,2 @@
1
+ export { SandboxedHtmlFrame } from './SandboxedHtmlFrame';
2
+ export type { SandboxedHtmlFrameProps, IsolatedSandboxedHtmlFrameProps, InspectableSandboxedHtmlFrameProps, } from './types';
@@ -0,0 +1 @@
1
+ export { SandboxedHtmlFrame } from './SandboxedHtmlFrame';
@@ -0,0 +1,33 @@
1
+ type SandboxedHtmlFrameBaseProps = {
2
+ /** Untrusted HTML rendered inside the iframe via `srcDoc`. */
3
+ html: string;
4
+ /** Accessible title of the iframe. */
5
+ title: string;
6
+ /** Forwarded to the iframe element so consumers can size it via `styled()`. */
7
+ className?: string;
8
+ };
9
+ /**
10
+ * Fully isolated rendering: scripts run inside an opaque origin and cannot reach the
11
+ * embedding page (cookies, storage, parent DOM). Use for pure visual previews of
12
+ * untrusted content.
13
+ */
14
+ export type IsolatedSandboxedHtmlFrameProps = SandboxedHtmlFrameBaseProps & {
15
+ mode: 'isolated';
16
+ onReady?: never;
17
+ };
18
+ /**
19
+ * Inspectable rendering: scripts are disabled entirely, but the embedding page keeps
20
+ * same-origin read access to the frame document. Use when the parent must measure or
21
+ * decorate the rendered DOM (e.g. an interaction heatmap).
22
+ */
23
+ export type InspectableSandboxedHtmlFrameProps = SandboxedHtmlFrameBaseProps & {
24
+ mode: 'inspectable';
25
+ /**
26
+ * Called with the frame document once it has loaded, and again whenever the callback
27
+ * identity changes. May return a cleanup run before the next call and on unmount.
28
+ */
29
+ onReady?: (document: Document) => void | (() => void);
30
+ };
31
+ /** Props of {@link SandboxedHtmlFrame}. */
32
+ export type SandboxedHtmlFrameProps = IsolatedSandboxedHtmlFrameProps | InspectableSandboxedHtmlFrameProps;
33
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/index.d.ts CHANGED
@@ -43,6 +43,7 @@ export { CodeEditor } from './CodeEditor';
43
43
  export { EnhancedInput, SearchInput } from './EnhancedInput';
44
44
  export { Chip, type ChipProps, type ChipVariant } from './Chip';
45
45
  export { RichmediaPreview, useRichmediaHtml } from './RichmediaPreview';
46
+ export { SandboxedHtmlFrame, type SandboxedHtmlFrameProps, type IsolatedSandboxedHtmlFrameProps, type InspectableSandboxedHtmlFrameProps, } from './SandboxedHtmlFrame';
46
47
  export { NativeRichMediaPreview, parseNativeRichMediaConfig, NATIVE_RICH_MEDIA_DISPLAY_TYPES } from './NativeRichMediaPreview';
47
48
  export type { NativeRichMediaConfig, NativeRichMediaDisplayType, NativeRichMediaText, NativeRichMediaBorder, NativeRichMediaCover, NativeRichMediaAction, NativeRichMediaButton, NativeRichMediaReward, NativeRichMediaModalBlock, NativeRichMediaSheetBlock, NativeRichMediaCarouselBlock, NativeRichMediaStoriesBlock, NativeRichMediaBannerBlock, NativeRichMediaFullscreenBlock, NativeRichMediaVideoBlock, NativeRichMediaPipBlock, NativeRichMediaScratchcardBlock, NativeRichMediaSpinwheelBlock, NativeRichMediaPreviewProps, NativeRichMediaLocalization, } from './NativeRichMediaPreview';
48
49
  export { RichMessageEditor, RichMessageViewer, type DynamicContent, } from './RichMessageEditor';
package/index.js CHANGED
@@ -43,6 +43,7 @@ export { CodeEditor } from './CodeEditor';
43
43
  export { EnhancedInput, SearchInput } from './EnhancedInput';
44
44
  export { Chip } from './Chip';
45
45
  export { RichmediaPreview, useRichmediaHtml } from './RichmediaPreview';
46
+ export { SandboxedHtmlFrame } from './SandboxedHtmlFrame';
46
47
  export { NativeRichMediaPreview, parseNativeRichMediaConfig, NATIVE_RICH_MEDIA_DISPLAY_TYPES } from './NativeRichMediaPreview';
47
48
  export { RichMessageEditor, RichMessageViewer } from './RichMessageEditor';
48
49
  export { FrequencyCappingEdit, FrequencyCappingReadonly, FrequencyCappingInApps } from './FrequencyCapping';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "1.1.171",
3
+ "version": "1.1.173",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",