Skip to content

Instantly share code, notes, and snippets.

@anaisbetts
Created December 22, 2015 17:06
Show Gist options
  • Save anaisbetts/066118920dd99a197a44 to your computer and use it in GitHub Desktop.
Save anaisbetts/066118920dd99a197a44 to your computer and use it in GitHub Desktop.
ExecuteJavaScript returning a result
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Electron boilerplate</title>
<link rel="stylesheet" href="index.css">
<script>
window.foo = {};
window.foo.bar = {
baz: function(a,b) {
return a + b + 5;
},
bazAsync: function(a,b) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(a + b + 5);
}, 1000);
})
}
};
require('./execute-js-func').init();
</script>
</head>
<body>
<div class="container">
<header>
<h1>Electron boilerplate</h1>
</header>
<section class="main"></section>
<footer></footer>
</div>
</body>
</html>
const electron = require('electron');
const app = electron.app;
const d = require('debug')('index');
import {getRemoteMethod} from './execute-js-func';
// report crashes to the Electron project
require('crash-reporter').start();
// adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
// prevent window being garbage collected
let mainWindow;
function onClosed() {
// dereference the window
// for multiple windows store them in an array
mainWindow = null;
}
function createMainWindow() {
const win = new electron.BrowserWindow({
width: 600,
height: 400
});
win.loadURL(`file://${__dirname}/example.html`);
win.on('closed', onClosed);
d("Create!");
win.webContents.on('did-finish-load', async function() {
d("Loaded!");
try {
let m = getRemoteMethod(win, 'foo.bar.baz');
let result = await m(5, 7);
d(result);
m = getRemoteMethod(win, 'foo.bar.bazAsync');
result = await m(5, 7);
d(result);
} catch (e) {
console.log(e.message);
console.log(e.stack);
}
});
return win;
}
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (!mainWindow) {
mainWindow = createMainWindow();
}
});
app.on('ready', () => {
mainWindow = createMainWindow();
});
import uuid from 'node-uuid';
import {Observable, Disposable} from 'rx';
const requestChannel = 'execute-javascript-request';
const responseChannel = 'execute-javascript-response';
let isBrowser = (process.type === 'browser');
let ipc = require('electron')[isBrowser ? 'ipcMain' : 'ipcRenderer'];
const d = require(isBrowser ? 'debug' : 'debug/browser')('execute-js-func');
function listenToIpc(channel) {
return Observable.create((subj) => {
let listener = (event, args) => {
d(`Got an event for ${channel}: ${JSON.stringify(args)}`);
subj.onNext(args);
};
d(`Setting up listener! ${channel}`);
ipc.on(channel, listener);
return Disposable.create(() =>
ipc.removeListener(channel, listener));
});
}
function getSendMethod(windowOrWebView) {
return ('webContents' in windowOrWebView) ?
(...a) => {
d(`webContents send: ${JSON.stringify(a)}`);
windowOrWebView.webContents.send(...a);
} :
(...a) => {
d(`webView send: ${JSON.stringify(a)}`);
windowOrWebView.send(...a);
};
}
function listenerForId(id) {
return listenToIpc(responseChannel)
.where((receive) => receive.id === id)
.take(1)
.flatMap((receive) => {
if (receive.error) {
let e = new Error(receive.error.message);
e.stack = receive.error.stack;
return Observable.throw(e);
}
return Observable.return(receive.result);
})
.timeout(5 * 1000);
}
export function getRemoteMethod(windowOrWebView, pathToObject) {
return function(...args) {
return executeJavaScriptMethod(windowOrWebView, pathToObject, ...args);
};
}
export function remoteEval(windowOrWebView, str) {
let send = getSendMethod(windowOrWebView);
let toSend = { id: uuid.v4(), eval: str };
let ret = listenerForId(toSend.id).toPromise();
d(`Sending: ${JSON.stringify(toSend)}`);
send(requestChannel, toSend);
return ret;
}
export function executeJavaScriptMethod(windowOrWebView, pathToObject, ...args) {
let send = getSendMethod(windowOrWebView);
if (!pathToObject.match(/^[a-zA-Z0-9\.]+$/)) {
return Promise.reject(new Error('pathToObject must be of the form foo.bar.baz'));
}
let toSend = { args, id: uuid.v4(), path: pathToObject };
let ret = listenerForId(toSend.id).toPromise();
d(`Sending: ${JSON.stringify(toSend)}`);
send(requestChannel, toSend);
return ret;
}
function objectAndParentGivenPath(path) {
let obj = window;
let parent = obj;
for (let part of path.split('.')) {
parent = obj;
obj = obj[part];
}
d(`parent: ${parent}, obj: ${obj}`);
if (typeof(parent) !== 'object') {
throw new Error(`Couldn't access part of the object window.${path}`);
}
return [parent, obj];
}
async function evalRemoteMethod(path, args) {
let [parent, obj] = objectAndParentGivenPath(path);
let result = obj;
if (typeof(obj) === 'function') {
d("obj is function!");
let res = obj.apply(parent, args);
result = res;
if (typeof(res) === 'object' && 'then' in res) {
d("result is Promise!");
result = await res;
}
}
return result;
}
export function init() {
let listener = async function(e, receive) {
d(`Got Message! ${JSON.stringify(receive)}`);
try {
if (receive.eval) {
receive.result = eval(receive.eval);
} else {
receive.result = await evalRemoteMethod(receive.path, receive.args);
}
d(`Replying! ${JSON.stringify(receive)}`);
e.sender.send(responseChannel, receive);
} catch(err) {
receive.error = {
message: err.message,
stack: err.stack
};
d(`Failed! ${JSON.stringify(receive)}`);
e.sender.send(responseChannel, receive);
}
};
d("Set up listener!");
ipc.on('execute-javascript-request', listener);
return Disposable.create(() => ipc.removeListener('execute-javascript-request', listener));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment