gwchq-textjam 0.2.30 → 0.3.0

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 (2) hide show
  1. package/dist/index.js +56 -115
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -369284,7 +369284,6 @@ const node_html_parser_1 = __webpack_require__(36192);
369284
369284
  const classnames_1 = __importDefault(__webpack_require__(46942));
369285
369285
  const react_router_dom_1 = __webpack_require__(92648);
369286
369286
  const EditorSlice_1 = __webpack_require__(68512);
369287
- const externalLinkHelper_1 = __webpack_require__(31615);
369288
369287
  const open_in_new_tab_svg_1 = __importDefault(__webpack_require__(86936));
369289
369288
  const stores_1 = __webpack_require__(32132);
369290
369289
  const ProjectTypes_1 = __webpack_require__(27130);
@@ -369370,29 +369369,22 @@ function HtmlRunner() {
369370
369369
  };
369371
369370
  }
369372
369371
  }, [isPreviewMode]);
369373
- const showModal = () => {
369374
- dispatch((0, EditorSlice_1.showErrorModal)());
369375
- eventListener();
369376
- };
369377
- const { externalLink, setExternalLink, handleAllowedExternalLink, handleRegularExternalLink, handleExternalLinkError, } = (0, externalLinkHelper_1.useExternalLinkState)(showModal);
369378
- const eventListener = () => {
369379
- window.addEventListener("message", (event) => {
369380
- if (typeof event.data?.msg === "string") {
369381
- if (event.data?.msg === "ERROR: External link") {
369382
- handleExternalLinkError();
369383
- }
369384
- else if (event.data?.msg === "Allowed external link") {
369385
- handleAllowedExternalLink(event.data.payload.linkTo);
369386
- }
369387
- else {
369388
- handleRegularExternalLink(event.data.payload.linkTo);
369389
- }
369390
- }
369391
- });
369392
- };
369372
+ (0, react_1.useEffect)(() => {
369373
+ const handleReloadMessage = (event) => {
369374
+ if (event.data?.msg !== "RELOAD")
369375
+ return;
369376
+ const nextPage = event.data?.payload?.linkTo;
369377
+ if (typeof nextPage !== "string")
369378
+ return;
369379
+ dispatch((0, EditorSlice_1.setPage)(nextPage));
369380
+ dispatch((0, EditorSlice_1.triggerCodeRun)());
369381
+ };
369382
+ window.addEventListener("message", handleReloadMessage);
369383
+ return () => window.removeEventListener("message", handleReloadMessage);
369384
+ }, [dispatch]);
369393
369385
  const iframeReload = () => {
369394
369386
  const iframe = output.current?.contentDocument;
369395
- const filePath = (0, helpers_1.getFilenameFromIFrame)(iframe) ?? externalLink;
369387
+ const filePath = (0, helpers_1.getFilenameFromIFrame)(iframe);
369396
369388
  if (runningFilePath !== filePath) {
369397
369389
  setRunningFilePath(filePath);
369398
369390
  }
@@ -369402,9 +369394,11 @@ function HtmlRunner() {
369402
369394
  linkElement.addEventListener("click", (e) => {
369403
369395
  const href = linkElement.getAttribute("href");
369404
369396
  const target = linkElement.getAttribute("target");
369405
- // block in-iframe navigation for links with target="_blank" and .html href
369397
+ if (!href || target !== "_blank" || (0, helpers_1.isExternalUrl)(href))
369398
+ return;
369399
+ // block in-iframe navigation for internal html links with target="_blank"
369406
369400
  // and open them in a preview tab
369407
- if (target === "_blank" && href?.includes(".html")) {
369401
+ if (href?.includes(".html")) {
369408
369402
  e.preventDefault();
369409
369403
  e.stopImmediatePropagation();
369410
369404
  openPreview(`/${href}`);
@@ -369412,10 +369406,8 @@ function HtmlRunner() {
369412
369406
  }, true);
369413
369407
  });
369414
369408
  }
369415
- setExternalLink(null);
369416
369409
  };
369417
369410
  (0, react_1.useEffect)(() => {
369418
- eventListener();
369419
369411
  dispatch((0, EditorSlice_1.loadingRunner)(EditorTypes_1.RunnerType.HTML));
369420
369412
  dispatch((0, EditorSlice_1.setLoadedRunner)(EditorTypes_1.RunnerType.HTML));
369421
369413
  }, []);
@@ -369440,39 +369432,31 @@ function HtmlRunner() {
369440
369432
  const fileToRun = page ?? defaultPreviewFilePath;
369441
369433
  setRunningFilePath(fileToRun);
369442
369434
  dispatch((0, EditorSlice_1.setError)(null));
369443
- if (!externalLink) {
369444
- const entryPoint = (0, helpers_1.getEntryPoint)(projectComponents, fileToRun);
369445
- if (!entryPoint) {
369446
- dispatch((0, EditorSlice_1.setError)(fileToRun === helpers_1.DEFAULT_ENTRY_FILE_PATH && !isPreviewMode
369447
- ? {
369448
- type: Errors_1.ErrorType.ENTRY_FILE_NOT_FOUND,
369449
- details: {
369450
- fileName: helpers_1.DEFAULT_ENTRY_FILE_NAME,
369451
- },
369452
- }
369453
- : {
369454
- type: Errors_1.ErrorType.PAGE_NOT_FOUND,
369455
- }));
369456
- dispatch((0, EditorSlice_1.codeRunHandled)());
369457
- return;
369458
- }
369459
- const indexPage = (0, node_html_parser_1.parse)(entryPoint.content);
369460
- const body = indexPage.querySelector("body") || indexPage;
369461
- const htmlRoot = indexPage.querySelector("html") ?? indexPage;
369462
- body.insertAdjacentHTML("afterbegin", scripts_1.disableLocalStorageScript);
369463
- htmlRoot.insertAdjacentHTML("afterbegin", scripts_1.consoleOverrideScript);
369464
- const { content } = await (0, fileParsers_1.resolveAndRewriteHtmlImports)(indexPage.toString(), projectComponents, entryPoint.path, page ?? entryPoint.path);
369465
- if (output.current) {
369466
- output.current.srcdoc = content;
369467
- }
369468
- if (codeRunTriggered) {
369469
- dispatch((0, EditorSlice_1.codeRunHandled)());
369470
- }
369435
+ const entryPoint = (0, helpers_1.getEntryPoint)(projectComponents, fileToRun);
369436
+ if (!entryPoint) {
369437
+ dispatch((0, EditorSlice_1.setError)(fileToRun === helpers_1.DEFAULT_ENTRY_FILE_PATH && !isPreviewMode
369438
+ ? {
369439
+ type: Errors_1.ErrorType.ENTRY_FILE_NOT_FOUND,
369440
+ details: {
369441
+ fileName: helpers_1.DEFAULT_ENTRY_FILE_NAME,
369442
+ },
369443
+ }
369444
+ : {
369445
+ type: Errors_1.ErrorType.PAGE_NOT_FOUND,
369446
+ }));
369447
+ dispatch((0, EditorSlice_1.codeRunHandled)());
369448
+ return;
369471
369449
  }
369472
- else {
369473
- if (output.current) {
369474
- output.current.src = externalLink;
369475
- }
369450
+ const indexPage = (0, node_html_parser_1.parse)(entryPoint.content);
369451
+ const body = indexPage.querySelector("body") || indexPage;
369452
+ const htmlRoot = indexPage.querySelector("html") ?? indexPage;
369453
+ body.insertAdjacentHTML("afterbegin", scripts_1.disableLocalStorageScript);
369454
+ htmlRoot.insertAdjacentHTML("afterbegin", scripts_1.consoleOverrideScript);
369455
+ const { content } = await (0, fileParsers_1.resolveAndRewriteHtmlImports)(indexPage.toString(), projectComponents, entryPoint.path, page ?? entryPoint.path);
369456
+ if (output.current) {
369457
+ output.current.srcdoc = content;
369458
+ }
369459
+ if (codeRunTriggered) {
369476
369460
  dispatch((0, EditorSlice_1.codeRunHandled)());
369477
369461
  }
369478
369462
  };
@@ -369512,7 +369496,7 @@ function HtmlRunner() {
369512
369496
  const iframeClasses = (0, classnames_1.default)(styles_module_scss_1.default.iframe, {
369513
369497
  [styles_module_scss_1.default.codeHasBeenRun]: codeHasBeenRun,
369514
369498
  });
369515
- return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.htmlrunnerContainer, children: [(0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Preview", icon: preview_svg_1.default, readOnly: readOnly, extraTabContent: codeHasBeenRun && openInNewTabLink, tabPanelClassName: styles_module_scss_1.default.previewHtml, children: error ? ((0, jsx_runtime_1.jsx)("div", { className: iframeClasses, children: (0, jsx_runtime_1.jsx)(NotFoundPage_1.NotFoundPage, {}) })) : ((0, jsx_runtime_1.jsx)("iframe", { className: iframeClasses, sandbox: "allow-scripts allow-same-origin allow-modals allow-popups", referrerPolicy: "strict-origin-when-cross-origin", allow: "\n accelerometer 'none';\n camera 'none';\n encrypted-media;\n fullscreen;\n picture-in-picture;\n geolocation 'none';\n gyroscope 'none';\n magnetometer 'none';\n microphone 'none';\n midi 'none';\n payment 'none';\n usb 'none';\n ", id: "output-frame", title: "HTML Output Preview", ref: output, onLoad: iframeReload })) }), !isPreviewMode && ((0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Console", icon: console_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(HtmlConsole_1.default, { consoleLogs: consoleLogs }) }) }))] }));
369499
+ return ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_scss_1.default.htmlrunnerContainer, children: [(0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Preview", icon: preview_svg_1.default, readOnly: readOnly, extraTabContent: codeHasBeenRun && openInNewTabLink, tabPanelClassName: styles_module_scss_1.default.previewHtml, children: error ? ((0, jsx_runtime_1.jsx)("div", { className: iframeClasses, children: (0, jsx_runtime_1.jsx)(NotFoundPage_1.NotFoundPage, {}) })) : ((0, jsx_runtime_1.jsx)("iframe", { className: iframeClasses, sandbox: "allow-scripts allow-same-origin allow-modals allow-popups allow-popups-to-escape-sandbox", referrerPolicy: "strict-origin-when-cross-origin", allow: "\n accelerometer 'none';\n camera 'none';\n encrypted-media;\n fullscreen;\n picture-in-picture;\n geolocation 'none';\n gyroscope 'none';\n magnetometer 'none';\n microphone 'none';\n midi 'none';\n payment 'none';\n usb 'none';\n ", id: "output-frame", title: "HTML Output Preview", ref: output, onLoad: iframeReload })) }), !isPreviewMode && ((0, jsx_runtime_1.jsx)(ResizableWithHandle_1.default, { "data-testid": "proj-console-container", handleDirection: "top", defaultHeight: "50%", className: styles_module_scss_1.default.resizeContainer, handleClassName: styles_module_scss_1.default.resizeHandleContainer, children: (0, jsx_runtime_1.jsx)(OutputTabPanel_1.OutputTabPanel, { title: "Console", icon: console_svg_1.default, readOnly: readOnly, children: (0, jsx_runtime_1.jsx)(HtmlConsole_1.default, { consoleLogs: consoleLogs }) }) }))] }));
369516
369500
  }
369517
369501
  exports["default"] = HtmlRunner;
369518
369502
 
@@ -369591,6 +369575,7 @@ const node_html_parser_1 = __webpack_require__(36192);
369591
369575
  const ProjectTypes_1 = __webpack_require__(27130);
369592
369576
  const binaryStore_1 = __webpack_require__(5060);
369593
369577
  const projectHelpers_1 = __webpack_require__(2610);
369578
+ const helpers_1 = __webpack_require__(1108);
369594
369579
  /** Normalizes CSS paths
369595
369580
  * Treats paths without leading / or ./ or ../ as relative to current file
369596
369581
  * E.g. 'image.png' will be treated as './image.png'
@@ -369644,9 +369629,7 @@ const getMimeType = (filePath, fallback = "application/octet-stream") => {
369644
369629
  };
369645
369630
  /** Check if a URL is external, blob, or data */
369646
369631
  const isExternalOrDataUrl = (url) => {
369647
- return (url.startsWith("blob:") ||
369648
- /^(https?:)?\/\//.test(url) ||
369649
- url.startsWith("data:"));
369632
+ return (url.startsWith("blob:") || (0, helpers_1.isExternalUrl)(url) || url.startsWith("data:"));
369650
369633
  };
369651
369634
  async function replaceAsync(input, regex, replacer) {
369652
369635
  let result = "";
@@ -369823,7 +369806,12 @@ async function rewriteSources(indexPage, components, baseFilePath, propName, blo
369823
369806
  const nodes = indexPage.querySelectorAll(`[${propName}]`);
369824
369807
  for (const node of nodes) {
369825
369808
  const raw = node.getAttribute(propName);
369826
- if (!raw || isExternalOrDataUrl(raw))
369809
+ if (!raw)
369810
+ continue;
369811
+ // For external links with target="_blank", add noopener and noreferrer for security
369812
+ if ((0, helpers_1.isExternalUrl)(raw) && node.getAttribute("target") === "_blank")
369813
+ node.setAttribute("rel", "noopener noreferrer");
369814
+ if (isExternalOrDataUrl(raw))
369827
369815
  continue;
369828
369816
  if ((0, projectHelpers_1.parseFileName)(raw).extension === ProjectTypes_1.ProjectFileExtension.HTML &&
369829
369817
  propName === "href") {
@@ -369876,7 +369864,7 @@ async function resolveAndRewriteHtmlImports(html, projectComponents, baseFilePat
369876
369864
 
369877
369865
 
369878
369866
  Object.defineProperty(exports, "__esModule", ({ value: true }));
369879
- exports.getFilenameFromIFrame = exports.getEntryPoint = exports.DEFAULT_ENTRY_FILE_PATH = exports.DEFAULT_ENTRY_FILE_NAME = void 0;
369867
+ exports.isExternalUrl = exports.getFilenameFromIFrame = exports.getEntryPoint = exports.DEFAULT_ENTRY_FILE_PATH = exports.DEFAULT_ENTRY_FILE_NAME = void 0;
369880
369868
  const ProjectTypes_1 = __webpack_require__(27130);
369881
369869
  exports.DEFAULT_ENTRY_FILE_NAME = "index.html";
369882
369870
  exports.DEFAULT_ENTRY_FILE_PATH = `/${exports.DEFAULT_ENTRY_FILE_NAME}`;
@@ -369887,6 +369875,10 @@ const getEntryPoint = (components, filePath) => {
369887
369875
  exports.getEntryPoint = getEntryPoint;
369888
369876
  const getFilenameFromIFrame = (iframe) => iframe?.querySelector("meta[filename]")?.getAttribute("filename") ?? null;
369889
369877
  exports.getFilenameFromIFrame = getFilenameFromIFrame;
369878
+ const isExternalUrl = (url) => {
369879
+ return /^(https?:)?\/\//.test(url);
369880
+ };
369881
+ exports.isExternalUrl = isExternalUrl;
369890
369882
 
369891
369883
 
369892
369884
  /***/ }),
@@ -376667,57 +376659,6 @@ const downloadProjectFile = async (file, fileName) => {
376667
376659
  exports.downloadProjectFile = downloadProjectFile;
376668
376660
 
376669
376661
 
376670
- /***/ }),
376671
-
376672
- /***/ 31615:
376673
- /***/ ((__unused_webpack_module, exports, __webpack_require__) => {
376674
-
376675
-
376676
- Object.defineProperty(exports, "__esModule", ({ value: true }));
376677
- exports.matchingRegexes = exports.allowedInternalLinks = exports.allowedExternalLinks = exports.useExternalLinkState = void 0;
376678
- const react_1 = __webpack_require__(51649);
376679
- const react_redux_1 = __webpack_require__(14062);
376680
- const EditorSlice_1 = __webpack_require__(68512);
376681
- const Errors_1 = __webpack_require__(20339);
376682
- const domain = "https://rpf.io/";
376683
- const host = window?.location?.origin || "http://localhost:3011";
376684
- const rpfDomain = new RegExp(`^${domain}`);
376685
- const hostDomain = new RegExp(`^${host}`);
376686
- const allowedInternalLinks = [new RegExp(`^#[a-zA-Z0-9]+`)];
376687
- exports.allowedInternalLinks = allowedInternalLinks;
376688
- const allowedExternalLinks = [rpfDomain, hostDomain];
376689
- exports.allowedExternalLinks = allowedExternalLinks;
376690
- const useExternalLinkState = (showModal) => {
376691
- const dispatch = (0, react_redux_1.useDispatch)();
376692
- const [externalLink, setExternalLink] = (0, react_1.useState)(null);
376693
- const handleAllowedExternalLink = (linkTo) => {
376694
- setExternalLink(linkTo);
376695
- dispatch((0, EditorSlice_1.triggerCodeRun)());
376696
- };
376697
- const handleRegularExternalLink = (linkTo) => {
376698
- setExternalLink(null);
376699
- dispatch((0, EditorSlice_1.setPage)(linkTo));
376700
- dispatch((0, EditorSlice_1.triggerCodeRun)());
376701
- };
376702
- const handleExternalLinkError = () => {
376703
- dispatch((0, EditorSlice_1.setError)({ type: Errors_1.ErrorType.EXTERNAL_LINK }));
376704
- showModal();
376705
- };
376706
- return {
376707
- externalLink,
376708
- setExternalLink,
376709
- handleAllowedExternalLink,
376710
- handleRegularExternalLink,
376711
- handleExternalLinkError,
376712
- };
376713
- };
376714
- exports.useExternalLinkState = useExternalLinkState;
376715
- const matchingRegexes = (regexArray, testString) => {
376716
- return regexArray.some((reg) => reg.test(testString));
376717
- };
376718
- exports.matchingRegexes = matchingRegexes;
376719
-
376720
-
376721
376662
  /***/ }),
376722
376663
 
376723
376664
  /***/ 65404:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gwchq-textjam",
3
3
  "description": "Embeddable React editor used in Raspberry Pi text-based projects.",
4
- "version": "0.2.30",
4
+ "version": "0.3.0",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/GirlsFirst/gwchq-textjam",
7
7
  "author": "Girls Who Code HQ",