From 903713e7053a86b8b7cf5e550030320256d2baf4 Mon Sep 17 00:00:00 2001 From: Jared Donovan Date: Wed, 4 Jul 2018 06:02:46 +1000 Subject: [PATCH 1/4] WIP - Mixed content error for Issue #543 (#661) * Set trust proxy option on Express app. * Fix replacement of filePath for full screen and embed sketches. * Use const rather than let because is never reassigned. --- server/server.js | 2 ++ server/utils/previewGeneration.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/server/server.js b/server/server.js index d186ff4e..446326c5 100644 --- a/server/server.js +++ b/server/server.js @@ -45,6 +45,8 @@ if (process.env.NODE_ENV === 'development') { corsOriginsWhitelist.push(/localhost/); } +app.set('trust proxy', true); + // Enable Cross-Origin Resource Sharing (CORS) for all origins const corsMiddleware = cors({ credentials: true, diff --git a/server/utils/previewGeneration.js b/server/utils/previewGeneration.js index 61d1e5f5..a8f46e8d 100644 --- a/server/utils/previewGeneration.js +++ b/server/utils/previewGeneration.js @@ -29,7 +29,8 @@ function resolveLinksInString(content, files, projectId) { while (resolvedFilePath.startsWith('/')) { resolvedFilePath = resolvedFilePath.substr(1); } - newContent = newContent.replace(filePath, `/sketches/${projectId}/assets/${resolvedFilePath}`); + const replacement = `/sketches/${projectId}/assets/${resolvedFilePath}`; + newContent = newContent.replace(filePath, replacement); } } } From 617f00653c941ed1d4a15d3f9ea033a882abb145 Mon Sep 17 00:00:00 2001 From: Liang Tang <1074461480@qq.com> Date: Tue, 31 Jul 2018 00:20:57 +0800 Subject: [PATCH 2/4] Improve current console (#656) * init v2 * make replay work * fix a failing scenary of react-frame * fix some bugs * delete/comment some files * remove * fix some bugs && remove more comments * remove unnecessary lines * minor tweak * fix some bugs * try to hook iframe using webpack * update * changes according to cassie * minor tweak * fix lint * extract sass * add icons * update webpack config * update webpack configuration * update * tweak * fix a small bug --- client/images/console-debug-dark.svg | 1 + client/images/console-debug-light.svg | 1 + client/images/console-error-dark.svg | 1 + client/images/console-error-light.svg | 1 + client/images/console-info-dark.svg | 1 + client/images/console-info-light.svg | 1 + client/images/console-warn-dark.svg | 1 + client/images/console-warn-light.svg | 1 + client/modules/IDE/components/Console.jsx | 72 ++++++++++++-- client/modules/IDE/components/Editor.jsx | 2 +- .../modules/IDE/components/PreviewFrame.jsx | 97 +++++++++---------- client/modules/IDE/pages/IDEView.jsx | 2 + client/styles/abstracts/_variables.scss | 21 +++- client/styles/components/_console-feed.scss | 47 +++++++++ client/styles/components/_console.scss | 57 +++++++---- client/styles/components/_preview-frame.scss | 1 + client/utils/consoleUtils.js | 36 ------- client/utils/previewEntry.js | 21 ++++ package.json | 9 +- server/server.js | 2 +- webpack.config.babel.js | 2 +- webpack.config.dev.js | 54 ++++++++++- webpack.config.prod.js | 49 +++++++++- 23 files changed, 348 insertions(+), 132 deletions(-) create mode 100644 client/images/console-debug-dark.svg create mode 100644 client/images/console-debug-light.svg create mode 100644 client/images/console-error-dark.svg create mode 100644 client/images/console-error-light.svg create mode 100644 client/images/console-info-dark.svg create mode 100644 client/images/console-info-light.svg create mode 100644 client/images/console-warn-dark.svg create mode 100644 client/images/console-warn-light.svg create mode 100644 client/styles/components/_console-feed.scss create mode 100644 client/utils/previewEntry.js diff --git a/client/images/console-debug-dark.svg b/client/images/console-debug-dark.svg new file mode 100644 index 00000000..58aebc9d --- /dev/null +++ b/client/images/console-debug-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-debug-light.svg b/client/images/console-debug-light.svg new file mode 100644 index 00000000..65a0b0a8 --- /dev/null +++ b/client/images/console-debug-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-error-dark.svg b/client/images/console-error-dark.svg new file mode 100644 index 00000000..56caaebc --- /dev/null +++ b/client/images/console-error-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-error-light.svg b/client/images/console-error-light.svg new file mode 100644 index 00000000..7ddff356 --- /dev/null +++ b/client/images/console-error-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-info-dark.svg b/client/images/console-info-dark.svg new file mode 100644 index 00000000..37e75260 --- /dev/null +++ b/client/images/console-info-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-info-light.svg b/client/images/console-info-light.svg new file mode 100644 index 00000000..f239a82f --- /dev/null +++ b/client/images/console-info-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-warn-dark.svg b/client/images/console-warn-dark.svg new file mode 100644 index 00000000..bcfcb475 --- /dev/null +++ b/client/images/console-warn-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/images/console-warn-light.svg b/client/images/console-warn-light.svg new file mode 100644 index 00000000..83a70db4 --- /dev/null +++ b/client/images/console-warn-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/modules/IDE/components/Console.jsx b/client/modules/IDE/components/Console.jsx index fdb4e092..4f394f13 100644 --- a/client/modules/IDE/components/Console.jsx +++ b/client/modules/IDE/components/Console.jsx @@ -2,13 +2,63 @@ import PropTypes from 'prop-types'; import React from 'react'; import InlineSVG from 'react-inlinesvg'; import classNames from 'classnames'; +import { Console as ConsoleFeed } from 'console-feed'; +import { CONSOLE_FEED_WITHOUT_ICONS, CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_CONTRAST_STYLES } from '../../../styles/components/_console-feed.scss'; +import warnLightUrl from '../../../images/console-warn-light.svg'; +import warnDarkUrl from '../../../images/console-warn-dark.svg'; +import errorLightUrl from '../../../images/console-error-light.svg'; +import errorDarkUrl from '../../../images/console-error-dark.svg'; +import debugLightUrl from '../../../images/console-debug-light.svg'; +import debugDarkUrl from '../../../images/console-debug-dark.svg'; +import infoLightUrl from '../../../images/console-info-light.svg'; +import infoDarkUrl from '../../../images/console-info-dark.svg'; const upArrowUrl = require('../../../images/up-arrow.svg'); const downArrowUrl = require('../../../images/down-arrow.svg'); class Console extends React.Component { - componentDidUpdate() { + componentDidUpdate(prevProps) { this.consoleMessages.scrollTop = this.consoleMessages.scrollHeight; + if (this.props.theme !== prevProps.theme) { + this.props.clearConsole(); + this.props.dispatchConsoleEvent(this.props.consoleEvents); + } + } + + getConsoleFeedStyle(theme, times) { + const style = {}; + const CONSOLE_FEED_LIGHT_ICONS = { + LOG_WARN_ICON: `url(${warnLightUrl})`, + LOG_ERROR_ICON: `url(${errorLightUrl})`, + LOG_DEBUG_ICON: `url(${debugLightUrl})`, + LOG_INFO_ICON: `url(${infoLightUrl})` + }; + const CONSOLE_FEED_DARK_ICONS = { + LOG_WARN_ICON: `url(${warnDarkUrl})`, + LOG_ERROR_ICON: `url(${errorDarkUrl})`, + LOG_DEBUG_ICON: `url(${debugDarkUrl})`, + LOG_INFO_ICON: `url(${infoDarkUrl})` + }; + if (times > 1) { + Object.assign(style, CONSOLE_FEED_WITHOUT_ICONS); + } + switch (theme) { + case 'light': + return Object.assign(CONSOLE_FEED_LIGHT_STYLES, CONSOLE_FEED_LIGHT_ICONS, style); + case 'dark': + return Object.assign(CONSOLE_FEED_DARK_STYLES, CONSOLE_FEED_DARK_ICONS, style); + case 'contrast': + return Object.assign(CONSOLE_FEED_CONTRAST_STYLES, CONSOLE_FEED_DARK_ICONS, style); + default: + return ''; + } + } + + formatData(args) { + if (!Array.isArray(args)) { + return Array.of(args); + } + return args; } render() { @@ -39,17 +89,25 @@ class Console extends React.Component {
{ this.consoleMessages = element; }} className="preview-console__messages"> {this.props.consoleEvents.map((consoleEvent) => { - const { arguments: args, method } = consoleEvent; + const { arguments: args, method, times } = consoleEvent; + const { theme } = this.props; + Object.assign(consoleEvent, { data: this.formatData(args) }); if (Object.keys(args).length === 0) { return ( -
+
undefined
); } return ( -
- {Object.keys(args).map(key => {args[key]})} +
+ { times > 1 && +
{times}
+ } +
); })} @@ -67,7 +125,9 @@ Console.propTypes = { isExpanded: PropTypes.bool.isRequired, collapseConsole: PropTypes.func.isRequired, expandConsole: PropTypes.func.isRequired, - clearConsole: PropTypes.func.isRequired + clearConsole: PropTypes.func.isRequired, + dispatchConsoleEvent: PropTypes.func.isRequired, + theme: PropTypes.string.isRequired }; Console.defaultProps = { diff --git a/client/modules/IDE/components/Editor.jsx b/client/modules/IDE/components/Editor.jsx index b36a6332..74bd6ceb 100644 --- a/client/modules/IDE/components/Editor.jsx +++ b/client/modules/IDE/components/Editor.jsx @@ -116,8 +116,8 @@ class Editor extends React.Component { this.props.setUnsavedChanges(true); this.props.updateFileContent(this.props.file.name, this._cm.getValue()); if (this.props.autorefresh && this.props.isPlaying) { - this.props.startRefreshSketch(); this.props.clearConsole(); + this.props.startRefreshSketch(); } }, 400)); diff --git a/client/modules/IDE/components/PreviewFrame.jsx b/client/modules/IDE/components/PreviewFrame.jsx index ade57b97..507bdfb7 100644 --- a/client/modules/IDE/components/PreviewFrame.jsx +++ b/client/modules/IDE/components/PreviewFrame.jsx @@ -2,10 +2,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import ReactDOM from 'react-dom'; // import escapeStringRegexp from 'escape-string-regexp'; +import { isEqual } from 'lodash'; import srcDoc from 'srcdoc-polyfill'; - import loopProtect from 'loop-protect'; -import loopProtectScript from 'loop-protect/dist/loop-protect.min'; import { JSHINT } from 'jshint'; import decomment from 'decomment'; import { getBlobUrl } from '../actions/files'; @@ -18,38 +17,20 @@ import { EXTERNAL_LINK_REGEX, NOT_EXTERNAL_LINK_REGEX } from '../../../../server/utils/fileUtils'; -import { hijackConsole, hijackConsoleErrorsScript, startTag, getAllScriptOffsets } +import { hijackConsoleErrorsScript, startTag, getAllScriptOffsets } from '../../../utils/consoleUtils'; - class PreviewFrame extends React.Component { - componentDidMount() { - if (this.props.isPlaying) { - this.renderFrameContents(); - } + constructor(props) { + super(props); + this.handleConsoleEvent = this.handleConsoleEvent.bind(this); + } - window.addEventListener('message', (messageEvent) => { - console.log(messageEvent); - messageEvent.data.forEach((message) => { - const args = message.arguments; - Object.keys(args).forEach((key) => { - if (args[key].includes('Exiting potential infinite loop')) { - this.props.stopSketch(); - this.props.expandConsole(); - } - }); - }); - this.props.dispatchConsoleEvent(messageEvent.data); - }); + componentDidMount() { + window.addEventListener('message', this.handleConsoleEvent); } componentDidUpdate(prevProps) { - // if sketch starts or stops playing, want to rerender - if (this.props.isPlaying !== prevProps.isPlaying) { - this.renderSketch(); - return; - } - // if the user explicitly clicks on the play button if (this.props.isPlaying && this.props.previewIsRefreshing) { this.renderSketch(); @@ -86,12 +67,43 @@ class PreviewFrame extends React.Component { } componentWillUnmount() { + window.removeEventListener('message', this.handleConsoleEvent); ReactDOM.unmountComponentAtNode(this.iframeElement.contentDocument.body); } - clearPreview() { - const doc = this.iframeElement; - doc.srcDoc = ''; + handleConsoleEvent(messageEvent) { + if (Array.isArray(messageEvent.data)) { + messageEvent.data.every((message, index, arr) => { + const { arguments: args } = message; + let hasInfiniteLoop = false; + Object.keys(args).forEach((key) => { + if (typeof args[key] === 'string' && args[key].includes('Exiting potential infinite loop')) { + this.props.stopSketch(); + this.props.expandConsole(); + hasInfiniteLoop = true; + } + }); + if (hasInfiniteLoop) { + return false; + } + if (index === arr.length - 1) { + Object.assign(message, { times: 1 }); + return false; + } + const cur = Object.assign(message, { times: 1 }); + const nextIndex = index + 1; + while (isEqual(cur.arguments, arr[nextIndex].arguments) && cur.method === arr[nextIndex].method) { + cur.times += 1; + arr.splice(nextIndex, 1); + if (nextIndex === arr.length) { + return false; + } + } + return true; + }); + + this.props.dispatchConsoleEvent(messageEvent.data); + } } addLoopProtect(sketchDoc) { @@ -121,9 +133,7 @@ class PreviewFrame extends React.Component { injectLocalFiles() { const htmlFile = this.props.htmlFile.content; let scriptOffs = []; - const resolvedFiles = this.resolveJSAndCSSLinks(this.props.files); - const parser = new DOMParser(); const sketchDoc = parser.parseFromString(htmlFile, 'text/html'); @@ -138,10 +148,6 @@ class PreviewFrame extends React.Component { this.resolveScripts(sketchDoc, resolvedFiles); this.resolveStyles(sketchDoc, resolvedFiles); - const scriptsToInject = [ - loopProtectScript, - hijackConsole - ]; const accessiblelib = sketchDoc.createElement('script'); accessiblelib.setAttribute( 'src', @@ -156,7 +162,6 @@ class PreviewFrame extends React.Component { const textSection = sketchDoc.createElement('section'); textSection.setAttribute('id', 'textOutput-content'); sketchDoc.getElementById('accessible-outputs').appendChild(textSection); - this.iframeElement.focus(); } if (this.props.gridOutput) { sketchDoc.body.appendChild(accessibleOutputs); @@ -164,7 +169,6 @@ class PreviewFrame extends React.Component { const gridSection = sketchDoc.createElement('section'); gridSection.setAttribute('id', 'gridOutput-content'); sketchDoc.getElementById('accessible-outputs').appendChild(gridSection); - this.iframeElement.focus(); } if (this.props.soundOutput) { sketchDoc.body.appendChild(accessibleOutputs); @@ -174,11 +178,9 @@ class PreviewFrame extends React.Component { sketchDoc.getElementById('accessible-outputs').appendChild(soundSection); } - scriptsToInject.forEach((scriptToInject) => { - const script = sketchDoc.createElement('script'); - script.text = scriptToInject; - sketchDoc.head.appendChild(script); - }); + const previewScripts = sketchDoc.createElement('script'); + previewScripts.src = '/previewScripts.js'; + sketchDoc.head.appendChild(previewScripts); const sketchDocString = `\n${sketchDoc.documentElement.outerHTML}`; scriptOffs = getAllScriptOffsets(sketchDocString); @@ -320,15 +322,6 @@ class PreviewFrame extends React.Component { } } - renderFrameContents() { - const doc = this.iframeElement.contentDocument; - if (doc.readyState === 'complete') { - this.renderSketch(); - } else { - setTimeout(this.renderFrameContents, 0); - } - } - render() { return (