Allow access to Node.js & Electron APIs in JS code injection (#245)

Co-authored-by: linonetwo <linonetwo012@gmail.com>
This commit is contained in:
Quang Lam 2020-05-10 15:09:58 +07:00 committed by GitHub
parent 0de23b6c25
commit fbd50b3f3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 31 deletions

View file

@ -25,6 +25,7 @@ const getDefaultPauseNotificationsByScheduleTo = () => {
};
const defaultPreferences = {
allowNodeInJsCodeInjection: false,
allowPrerelease: Boolean(semver.prerelease(app.getVersion())),
askForDownloadPath: true,
attachToMenubar: false,

View file

@ -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 */
}
}
}

View file

@ -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,
}) => (
<div className={classes.root}>
<div className={classes.flexGrow}>
<AceEditor
mode={getMode()}
theme={shouldUseDarkColors ? 'monokai' : 'github'}
height="100%"
width="100%"
name="codeEditor"
value={code}
onChange={(value) => onUpdateForm({ code: value })}
/>
}) => {
const codeInjectionType = window.require('electron').remote.getGlobal('codeInjectionType');
return (
<div className={classes.root}>
<div className={classes.flexGrow}>
<AceEditor
mode={getMode(codeInjectionType)}
theme={shouldUseDarkColors ? 'monokai' : 'github'}
height="100%"
width="100%"
name="codeEditor"
value={code}
onChange={(value) => onUpdateForm({ code: value })}
/>
</div>
<div className={classes.actions}>
<div className={classes.actionsLeft}>
{codeInjectionType === 'js' && (
<FormControlLabel
control={(
<Switch
checked={allowNodeInJsCodeInjection}
onChange={(e) => onUpdateForm({ allowNodeInJsCodeInjection: e.target.checked })}
color="primary"
/>
)}
label="Allow access to Node.JS & Electron APIs"
/>
)}
</div>
<div className={classes.actionsRight}>
<Button color="primary" variant="contained" disableElevation className={classes.button} onClick={onSave}>
Save
</Button>
<Button variant="contained" disableElevation className={classes.button} onClick={() => window.require('electron').remote.getCurrentWindow().close()}>
Cancel
</Button>
</div>
</div>
</div>
<div className={classes.actions}>
<Button color="primary" variant="contained" disableElevation className={classes.button} onClick={onSave}>
Save
</Button>
<Button variant="contained" disableElevation className={classes.button} onClick={() => window.require('electron').remote.getCurrentWindow().close()}>
Cancel
</Button>
</div>
</div>
);
);
};
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,
});

View file

@ -170,6 +170,7 @@ const getUpdaterDesc = (status, info) => {
};
const Preferences = ({
allowNodeInJsCodeInjection,
allowPrerelease,
askForDownloadPath,
attachToMenubar,
@ -1018,7 +1019,7 @@ const Preferences = ({
</ListItem>
<Divider />
<ListItem button onClick={() => requestShowCodeInjectionWindow('js')}>
<ListItemText primary="JS Code Injection" secondary={jsCodeInjection ? 'Set' : 'Not set'} />
<ListItemText primary="JS Code Injection" secondary={jsCodeInjection ? `Set ${allowNodeInJsCodeInjection ? ' (with access to Node.JS & Electron APIs)' : ''}` : 'Not set'} />
<ChevronRightIcon color="action" />
</ListItem>
<Divider />
@ -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,

View file

@ -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();

View file

@ -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 };