From fbd50b3f3fd111b43937d95013d1fdff5adda013 Mon Sep 17 00:00:00 2001 From: Quang Lam Date: Sun, 10 May 2020 15:09:58 +0700 Subject: [PATCH] Allow access to Node.js & Electron APIs in JS code injection (#245) Co-authored-by: linonetwo --- public/libs/preferences.js | 1 + public/preload/view.js | 29 +++++-- src/components/dialog-code-injection/index.js | 79 +++++++++++++------ src/components/dialog-preferences/index.js | 5 +- src/state/dialog-code-injection/actions.js | 4 + src/state/dialog-code-injection/reducers.js | 2 + 6 files changed, 89 insertions(+), 31 deletions(-) diff --git a/public/libs/preferences.js b/public/libs/preferences.js index 420c3d89..3f20b36c 100755 --- a/public/libs/preferences.js +++ b/public/libs/preferences.js @@ -25,6 +25,7 @@ const getDefaultPauseNotificationsByScheduleTo = () => { }; const defaultPreferences = { + allowNodeInJsCodeInjection: false, allowPrerelease: Boolean(semver.prerelease(app.getVersion())), askForDownloadPath: true, attachToMenubar: false, diff --git a/public/preload/view.js b/public/preload/view.js index 744aaf50..0df9c26e 100644 --- a/public/preload/view.js +++ b/public/preload/view.js @@ -48,15 +48,32 @@ const handleLoaded = (event) => { }); const jsCodeInjection = ipcRenderer.sendSync('get-preference', 'jsCodeInjection'); + const allowNodeInJsCodeInjection = ipcRenderer.sendSync('get-preference', 'allowNodeInJsCodeInjection'); const cssCodeInjection = ipcRenderer.sendSync('get-preference', 'cssCodeInjection'); if (jsCodeInjection && jsCodeInjection.trim().length > 0) { - try { - const node = document.createElement('script'); - node.innerHTML = jsCodeInjection; - document.body.appendChild(node); - } catch (err) { - console.log(err); // eslint-disable-line no-console + if (allowNodeInJsCodeInjection) { + try { + // eslint-disable-next-line no-new-func + Function( + 'require', + `"use strict";${jsCodeInjection}`, + )(require); + } catch (err) { + /* eslint-disable no-console */ + console.log(err); + /* eslint-enable no-console */ + } + } else { + try { + const node = document.createElement('script'); + node.innerHTML = jsCodeInjection; + document.body.appendChild(node); + } catch (err) { + /* eslint-disable no-console */ + console.log(err); + /* eslint-enable no-console */ + } } } diff --git a/src/components/dialog-code-injection/index.js b/src/components/dialog-code-injection/index.js index d9bbc94f..c3bdb235 100644 --- a/src/components/dialog-code-injection/index.js +++ b/src/components/dialog-code-injection/index.js @@ -2,6 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import Button from '@material-ui/core/Button'; +import FormControlLabel from '@material-ui/core/FormControlLabel'; +import Switch from '@material-ui/core/Switch'; import AceEditor from 'react-ace'; @@ -29,6 +31,10 @@ const styles = (theme) => ({ actions: { borderTop: `1px solid ${theme.palette.divider}`, padding: theme.spacing(2), + display: 'flex', + }, + actionsLeft: { + flex: 1, }, button: { float: 'right', @@ -36,44 +42,68 @@ const styles = (theme) => ({ }, }); -const getMode = () => { - const codeInjectionType = window.require('electron').remote.getGlobal('codeInjectionType'); +const getMode = (codeInjectionType) => { if (codeInjectionType === 'css') return 'css'; if (codeInjectionType === 'js') return 'javascript'; return ''; }; const CodeInjection = ({ + allowNodeInJsCodeInjection, classes, code, onSave, onUpdateForm, shouldUseDarkColors, -}) => ( -
-
- onUpdateForm({ code: value })} - /> +}) => { + const codeInjectionType = window.require('electron').remote.getGlobal('codeInjectionType'); + return ( +
+
+ onUpdateForm({ code: value })} + /> +
+
+
+ {codeInjectionType === 'js' && ( + onUpdateForm({ allowNodeInJsCodeInjection: e.target.checked })} + color="primary" + /> + )} + label="Allow access to Node.JS & Electron APIs" + /> + )} +
+
+ + +
+
-
- - -
-
-); + ); +}; + +CodeInjection.defaultProps = { + allowNodeInJsCodeInjection: false, +}; CodeInjection.propTypes = { + allowNodeInJsCodeInjection: PropTypes.bool, classes: PropTypes.object.isRequired, code: PropTypes.string.isRequired, onSave: PropTypes.func.isRequired, @@ -83,6 +113,7 @@ CodeInjection.propTypes = { const mapStateToProps = (state) => ({ code: state.dialogCodeInjection.form.code || '', + allowNodeInJsCodeInjection: state.dialogCodeInjection.form.allowNodeInJsCodeInjection, shouldUseDarkColors: state.general.shouldUseDarkColors, }); diff --git a/src/components/dialog-preferences/index.js b/src/components/dialog-preferences/index.js index 28753391..d5b1272a 100644 --- a/src/components/dialog-preferences/index.js +++ b/src/components/dialog-preferences/index.js @@ -170,6 +170,7 @@ const getUpdaterDesc = (status, info) => { }; const Preferences = ({ + allowNodeInJsCodeInjection, allowPrerelease, askForDownloadPath, attachToMenubar, @@ -1018,7 +1019,7 @@ const Preferences = ({ requestShowCodeInjectionWindow('js')}> - + @@ -1233,6 +1234,7 @@ Preferences.defaultProps = { }; Preferences.propTypes = { + allowNodeInJsCodeInjection: PropTypes.bool.isRequired, allowPrerelease: PropTypes.bool.isRequired, askForDownloadPath: PropTypes.bool.isRequired, attachToMenubar: PropTypes.bool.isRequired, @@ -1275,6 +1277,7 @@ Preferences.propTypes = { }; const mapStateToProps = (state) => ({ + allowNodeInJsCodeInjection: state.preferences.allowNodeInJsCodeInjection, allowPrerelease: state.preferences.allowPrerelease, askForDownloadPath: state.preferences.askForDownloadPath, attachToMenubar: state.preferences.attachToMenubar, diff --git a/src/state/dialog-code-injection/actions.js b/src/state/dialog-code-injection/actions.js index c35c819c..96277fee 100644 --- a/src/state/dialog-code-injection/actions.js +++ b/src/state/dialog-code-injection/actions.js @@ -16,7 +16,11 @@ export const save = () => (dispatch, getState) => { const { remote } = window.require('electron'); const codeInjectionType = remote.getGlobal('codeInjectionType'); + requestSetPreference(`${codeInjectionType}CodeInjection`, form.code); + if (codeInjectionType === 'js' && typeof form.allowNodeInJsCodeInjection === 'boolean') { + requestSetPreference('allowNodeInJsCodeInjection', form.allowNodeInJsCodeInjection); + } requestShowRequireRestartDialog(); diff --git a/src/state/dialog-code-injection/reducers.js b/src/state/dialog-code-injection/reducers.js index 55aac404..46e5d8a3 100644 --- a/src/state/dialog-code-injection/reducers.js +++ b/src/state/dialog-code-injection/reducers.js @@ -10,6 +10,8 @@ const form = (state = {}, action) => { const codeInjectionType = window.require('electron').remote.getGlobal('codeInjectionType'); return { code: getPreference(`${codeInjectionType}CodeInjection`), + // allowNodeInJsCodeInjection is only used for js injection + allowNodeInJsCodeInjection: codeInjectionType === 'js' ? getPreference('allowNodeInJsCodeInjection') : false, }; } case UPDATE_CODE_INJECTION_FORM: return { ...state, ...action.changes };