@capillarytech/creatives-library 8.0.250-alpha.0 → 8.0.250-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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.250-alpha.0",
4
+ "version": "8.0.250-alpha.1",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -15,13 +15,13 @@
15
15
  * - Filters dangerous protocols (javascript:, data:, vbscript:)
16
16
  * - Variant-aware sanitization (EMAIL vs INAPP with different security levels)
17
17
  * - Iframe sandbox uses "allow-scripts" only (no allow-same-origin for security)
18
- * - Content delivered via blob URLs to isolate iframe from parent origin
18
+ * - Content delivered via srcdoc attribute for CSP compliance (replaces blob URLs)
19
19
  *
20
20
  * Previous Security Issues (FIXED):
21
21
  * - Removed insecure manual sanitizeJavaScript function that only escaped </script> tags
22
22
  * - Replaced with robust DOMPurify-based sanitization for full XSS protection
23
23
  * - Removed "allow-same-origin" from iframe sandbox to prevent same-origin access
24
- * - Implemented blob URL delivery to maintain isolation while allowing script execution
24
+ * - Replaced blob URLs with srcdoc to comply with CSP frame-src directives
25
25
  */
26
26
 
27
27
  import React, { useMemo, useEffect, useRef } from 'react';
@@ -103,34 +103,25 @@ const PreviewPane = ({
103
103
  return sanitizationResult.sanitized;
104
104
  }, [contentValue, variant]);
105
105
 
106
- // Create blob URL for secure iframe content delivery
107
- // This isolates the iframe content from the parent origin while allowing script execution
108
- const blobUrlRef = useRef(null);
109
- const blobUrl = useMemo(() => {
110
- // Clean up previous blob URL to prevent memory leaks
111
- if (blobUrlRef.current) {
112
- URL.revokeObjectURL(blobUrlRef.current);
113
- blobUrlRef.current = null;
106
+ // Use srcdoc instead of blob URLs for CSP compliance
107
+ // srcdoc allows embedding HTML directly in iframe without violating frame-src CSP directives
108
+ // This is more secure and CSP-compliant than blob URLs
109
+ const iframeContent = useMemo(() => {
110
+ if (!combinedContent) {
111
+ return null;
114
112
  }
115
113
 
116
- // Create new blob URL if we have content
117
- if (combinedContent) {
118
- const blob = new Blob([combinedContent], { type: 'text/html' });
119
- blobUrlRef.current = URL.createObjectURL(blob);
120
- return blobUrlRef.current;
114
+ // srcdoc has a size limit (~2MB in some browsers), but for HTML preview content this should be sufficient
115
+ // If content is too large, we'll fall back to about:blank and log a warning
116
+ const MAX_SRCDOC_SIZE = 2000000; // 2MB limit
117
+ if (combinedContent.length > MAX_SRCDOC_SIZE) {
118
+ console.warn('PreviewPane: Content too large for srcdoc, using about:blank');
119
+ return null;
121
120
  }
122
121
 
123
- return null;
122
+ return combinedContent;
124
123
  }, [combinedContent]);
125
124
 
126
- // Cleanup blob URL on unmount
127
- useEffect(() => () => {
128
- if (blobUrlRef.current) {
129
- URL.revokeObjectURL(blobUrlRef.current);
130
- blobUrlRef.current = null;
131
- }
132
- }, []);
133
-
134
125
  // Generate CSS classes based on view mode
135
126
  const getIframeClasses = () => {
136
127
  const baseClass = 'preview-pane__iframe';
@@ -159,7 +150,8 @@ const PreviewPane = ({
159
150
 
160
151
  const renderDeviceFrame = () => (
161
152
  <iframe
162
- src={blobUrl || 'about:blank'}
153
+ src={iframeContent ? undefined : 'about:blank'}
154
+ srcDoc={iframeContent || undefined}
163
155
  title={intl.formatMessage(messages.htmlPreview)}
164
156
  className={getIframeClasses()}
165
157
  style={getPreviewStyles()}
@@ -210,7 +202,7 @@ const PreviewPane = ({
210
202
  </CapRow>
211
203
 
212
204
  <CapRow className="preview-pane__content">
213
- {blobUrl ? renderDeviceFrame() : renderEmptyState()}
205
+ {iframeContent ? renderDeviceFrame() : renderEmptyState()}
214
206
  </CapRow>
215
207
  </div>
216
208
  );
@@ -14,7 +14,6 @@ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
14
14
  // Cap UI Library
15
15
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
16
16
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
17
- import CapTab from '@capillarytech/cap-ui-library/CapTab';
18
17
  import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
19
18
 
20
19
  // Messages
@@ -23,6 +22,7 @@ import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
23
22
 
24
23
  // Styles
25
24
  import './_validationTabs.scss';
25
+ import {StyledCapTab} from '../../../../v2Containers/MobilePushNew/style';
26
26
 
27
27
  // Constants for issue sources
28
28
  const ISSUE_SOURCES = {
@@ -346,11 +346,11 @@ const ValidationTabs = ({
346
346
  return (
347
347
  <div className={`validation-tabs ${className || ''}`}>
348
348
  <CapRow className="validation-tabs__header">
349
- <CapTab
349
+ <StyledCapTab
350
+ className="validation-tabs__tabs"
350
351
  activeKey={activeKey || (tabPanes[0]?.key)}
351
352
  onChange={setActiveKey}
352
353
  panes={tabPanes}
353
- className="validation-tabs__tabs"
354
354
  />
355
355
  <CapRow className="validation-tabs__actions">
356
356
  <CapTooltip title={intl.formatMessage(messages.closePanel)}>
@@ -883,8 +883,18 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
883
883
  const msgString = JSON.stringify(msg);
884
884
  const langId = formData[this.state.currentTab - 1].activeTab;
885
885
  const langIndex = formData[this.state.currentTab - 1].selectedLanguages.indexOf(langId);
886
- const win = document.getElementById(`edmeditor${(langIndex + 1) > 1 ? (langIndex + 1) : ''}`).contentWindow;
887
- win.postMessage(msgString, '*');
886
+ const elementToSelect = document.getElementById(`edmeditor${(langIndex + 1) > 1 ? (langIndex + 1) : ''}`);
887
+ if (elementToSelect) {
888
+ try {
889
+ const win = elementToSelect.contentWindow;
890
+ if (win) {
891
+ win.postMessage(msgString, '*');
892
+ }
893
+ } catch (error) {
894
+ // Handle cross-origin frame access errors
895
+ console.warn('Failed to access iframe contentWindow (cross-origin restriction):', error);
896
+ }
897
+ }
888
898
  } else if (data === "unsubscribe" && this.state.editorInstanse) {
889
899
  const anchor = `<a href='{{${data}}}'>${data}</a>`;
890
900
  this.state.editorInstanse.insertHtml(`${anchor}`);