Refactor IPC

This commit is contained in:
Samuel Elliott 2018-03-21 20:52:42 +00:00
parent 8a9c8edf39
commit aa933d9a09
No known key found for this signature in database
GPG Key ID: 8420C7CDE43DC4D6
12 changed files with 300 additions and 126 deletions

View File

@ -35,7 +35,7 @@ export default new class {
* Init css editor
*/
init() {
ClientIPC.on('bd-get-scss', () => this.sendToEditor('set-scss', { scss: this.scss }));
ClientIPC.on('bd-get-scss', () => this.scss, true);
ClientIPC.on('bd-update-scss', (e, scss) => this.updateScss(scss));
ClientIPC.on('bd-save-csseditor-bounds', (e, bounds) => this.saveEditorBounds(bounds));
@ -49,7 +49,7 @@ export default new class {
this.sendToEditor('set-liveupdate', event.value);
});
ClientIPC.on('bd-get-liveupdate', () => this.sendToEditor('set-liveupdate', this.liveupdate.value));
ClientIPC.on('bd-get-liveupdate', () => this.liveupdate.value, true);
ClientIPC.on('bd-set-liveupdate', (e, value) => this.liveupdate.value = value);
this.watchfilessetting = Settings.getSetting('css', 'default', 'watch-files');
@ -73,7 +73,7 @@ export default new class {
*/
async updateScss(scss, sendSource) {
if (sendSource)
this.sendToEditor('set-scss', { scss });
this.sendToEditor('set-scss', scss);
if (!scss) {
this._scss = this.css = '';
@ -137,7 +137,7 @@ export default new class {
* @param {any} data
*/
async sendToEditor(channel, data) {
return await ClientIPC.send('sendToCssEditor', { channel, data });
return ClientIPC.sendToCssEditor(channel, data);
}
/**
@ -168,7 +168,7 @@ export default new class {
*/
setState(scss, css, files, err) {
this._scss = scss;
this.sendToEditor('set-scss', { scss });
this.sendToEditor('set-scss', scss);
this.css = css;
this.files = files;
this.error = err;

View File

@ -8,7 +8,7 @@
* LICENSE file in the root directory of this source tree.
*/
import { ClientIPC } from 'bdipc';
import { ClientIPC } from 'common';
export default class {
@ -42,4 +42,5 @@ export default class {
throw err;
}
}
}

View File

@ -9,7 +9,7 @@
*/
import sparkplug from 'sparkplug';
import { ClientIPC } from 'bdipc';
import { ClientIPC } from 'common';
import Module from './module';
import Events from './events';

View File

@ -20,7 +20,7 @@ import { SettingsSet, SettingsCategory, Setting, SettingsScheme } from 'structs'
import { BdMenuItems, Modals, DOM, Reflection } from 'ui';
import DiscordApi from './discordapi';
import { ReactComponents } from './reactcomponents';
import { MonkeyPatch } from './patcher';
import { Patcher, MonkeyPatch } from './patcher';
export default class PluginApi {

View File

@ -8,51 +8,153 @@
* LICENSE file in the root directory of this source tree.
*/
const { ipcRenderer, ipcMain } = require('electron');
import { ipcRenderer } from 'electron';
export class ClientIPC {
static on(channel, cb) {
ipcRenderer.on(channel, (event, message) => cb(event, message));
const callbacks = new WeakMap();
export default new class ClientIPC {
constructor() {
this.on('ping', () => 'pong', true);
}
static async send(channel, message) {
/**
* Adds an IPC event listener.
* @param {String} channel The channel to listen on
* @param {Function} callback A function that will be called when a message is received
* @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
* @return {Promise}
*/
on(channel, callback, reply) {
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
const __eid = Date.now().toString();
ipcRenderer.send(channel, Object.assign(message ? message : {}, { __eid }));
const boundCallback = async (event, args) => {
const ipcevent = new BDIpcEvent(event, args);
try {
const r = callback(ipcevent, ipcevent.message);
if (reply) ipcevent.reply(await r);
} catch (err) {
console.error('Error in IPC callback:', err);
if (reply) ipcevent.reject(err);
}
};
callbacks.set(callback, boundCallback);
ipcRenderer.on(channel, boundCallback);
}
off(channel, callback) {
ipcRenderer.removeListener(channel, callbacks.get(callback));
}
/**
* Sends a message to the main process and returns a promise that is resolved when the main process replies.
* @param {String} channel The channel to send a message to
* @param {Any} message Data to send to the main process
* @param {Boolean} error Whether to mark the message as an error
* @return {Promise}
*/
async send(channel, message, error) {
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
const eid = 'bd-' + Date.now().toString();
ipcRenderer.send(channel, { eid, message, error });
return new Promise((resolve, reject) => {
ipcRenderer.once(__eid, (event, arg) => {
if (arg.err) {
return reject(arg.err);
}
resolve(arg);
ipcRenderer.once(eid, (event, arg) => {
if (arg.error) reject(arg.message);
else resolve(arg.message);
});
});
}
/**
* Sends a message to the Discord window and returns a promise that is resolved when it replies.
* @param {String} channel The channel to send a message to
* @param {Any} message Data to send to the renderer process
* @return {Promise}
*/
sendToDiscord(channel, message) {
return this.send('bd-sendToDiscord', {
channel, message
});
}
/**
* Sends a message to the CSS editor window and returns a promise that is resolved when it replies.
* @param {String} channel The channel to send a message to
* @param {Any} message Data to send to the CSS editor window
* @return {Promise}
*/
sendToCssEditor(channel, message) {
return this.send('bd-sendToCssEditor', {
channel, message
});
}
ping() {
return this.send('ping');
}
getConfig() {
return this.send('getConfig');
}
showOpenDialog(options) {
return this.send('native-open', options);
}
compileSass(options) {
return this.send('compileSass', options);
}
dba(command) {
return this.send('dba', command);
}
}
/**
* An IPC event.
*/
class BDIpcEvent {
constructor(event, args) {
this.args = args;
this.ipcEvent = event;
this.replied = false;
}
bindings() {
this.send = this.send.bind(this);
this.reply = this.reply.bind(this);
}
send(message) {
this.ipcEvent.sender.send(this.args.__eid, message);
/**
* Sends a message back to the message's sender.
* @param {Any} message Data to send to this message's sender
*/
get send() { return this.reply }
reply(message, error) {
if (this.replied)
throw {message: 'This message has already been replied to.'};
this.replied = true;
return ClientIPC.send(this.eid, message, error);
}
reply(message) {
this.send(message);
reject(err) {
return this.reply(err, true);
}
}
export class CoreIPC {
static on(channel, cb) {
ipcMain.on(channel, (event, args) => cb(new BDIpcEvent(event, args)));
get message() {
return this.args.message;
}
get error() {
return this.args.error;
}
get eid() {
return this.args.eid;
}
}

View File

@ -1,4 +1,4 @@
export { ClientIPC } from './bdipc';
export { default as ClientIPC } from './bdipc';
export * from './utils';
export { ClientLogger } from './logger';
export { default as AsyncEventEmitter } from './async-eventemitter';

View File

@ -21,7 +21,7 @@ export const logLevels = {
'info': 'info'
};
export class Logger {
export default class Logger {
constructor(file) {
this.logs = [];

View File

@ -60,48 +60,48 @@ class Comms {
}
initListeners() {
BDIpc.on('bd-getConfig', o => o.reply(this.bd.config.config));
BDIpc.on('ping', () => 'pong', true);
BDIpc.on('bd-sendToDiscord', event => this.bd.windowUtils.send(event.args.channel, event.args.message));
BDIpc.on('bd-getConfig', () => this.bd.config.config, true);
BDIpc.on('bd-openCssEditor', o => this.bd.csseditor.openEditor(o));
// BDIpc.on('bd-setScss', o => this.bd.csseditor.setSCSS(o.args.scss));
BDIpc.on('bd-sendToCssEditor', o => this.bd.csseditor.send(o.args.channel, o.args.data));
BDIpc.on('bd-sendToDiscord', (event, m) => this.sendToDiscord(m.channel, m.message), true);
BDIpc.on('bd-native-open', o => {
dialog.showOpenDialog(BrowserWindow.fromWebContents(o.ipcEvent.sender), o.args, filenames => {
o.reply(filenames);
BDIpc.on('bd-openCssEditor', (event, options) => this.bd.csseditor.openEditor(options), true);
BDIpc.on('bd-sendToCssEditor', (event, m) => this.sendToCssEditor(m.channel, m.message), true);
BDIpc.on('bd-native-open', (event, options) => {
dialog.showOpenDialog(BrowserWindow.fromWebContents(event.ipcEvent.sender), options, filenames => {
event.reply(filenames);
});
});
BDIpc.on('bd-compileSass', o => {
if (!o.args.path && !o.args.data) return o.reply('');
if (typeof o.args.path === 'string' && typeof o.args.data === 'string') {
o.args.data = `${o.args.data} @import '${o.args.path.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}';`;
o.args.path = undefined;
BDIpc.on('bd-compileSass', (event, options) => {
if (typeof options.path === 'string' && typeof options.data === 'string') {
options.data = `${options.data} @import '${options.path.replace(/\\/g, '\\\\').replace(/'/g, '\\\'')}';`;
options.path = undefined;
}
sass.render(o.args, (err, result) => {
if (err) return o.reply({ err });
else o.reply(result);
sass.render(options, (err, result) => {
if (err) event.reject(err);
else event.reply(result);
});
});
BDIpc.on('bd-dba', o => {
(async () => {
try {
o.reply(await this.bd.dbInstance.exec(o.args));
} catch (err) {
o.reply({ err });
}
})();
});
BDIpc.on('bd-dba', (event, options) => this.bd.dbInstance.exec(options), true);
}
async send(channel, message) {
BDIpc.send(channel, message);
}
async sendToDiscord(channel, message) {
return this.bd.windowUtils.send(channel, message);
}
async sendToCssEditor(channel, message) {
return this.bd.csseditor.send(channel, message);
}
}
class BetterDiscord {
@ -156,7 +156,7 @@ class BetterDiscord {
const windows = BrowserWindow.getAllWindows();
for (let window of windows) {
BetterDiscord.ignite(window);
if (window) BetterDiscord.ignite(window);
}
if (windows.length === 1 && windows[0].webContents.getURL().includes('discordapp.com')) {

View File

@ -8,36 +8,114 @@
* LICENSE file in the root directory of this source tree.
*/
const { ipcMain } = require('electron');
const { Module } = require('./modulebase');
const { ipcMain } = require('electron');
const callbacks = new WeakMap();
/**
* The IPC module used in the main process.
*/
class BDIpc {
/**
* Adds an IPC event listener.
* @param {String} channel The channel to listen on
* @param {Function} callback A function that will be called when a message is received
* @param {Boolean} reply Whether to automatically reply to the message with the callback's return value
* @return {Promise}
*/
static on(channel, callback, reply) {
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
const boundCallback = async (event, args) => {
const ipcevent = new BDIpcEvent(event, args);
try {
const r = callback(ipcevent, ipcevent.message);
if (reply) ipcevent.reply(await r);
} catch (err) {
console.error('Error in IPC callback:', err);
if (reply) ipcevent.reject(err);
}
};
callbacks.set(callback, boundCallback);
ipcMain.on(channel, boundCallback);
}
static off(channel, callback) {
ipcMain.removeListener(channel, callbacks.get(callback));
}
/**
* Sends a message to the main process and returns a promise that is resolved when the main process replies.
* @param {BrowserWindow} window The window to send a message to
* @param {String} channel The channel to send a message to
* @param {Any} message Data to send to the main process
* @param {Boolean} error Whether to mark the message as an error
* @return {Promise}
*/
static send(window, channel, message, error) {
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
const eid = 'bd-' + Date.now().toString();
window.send(channel, { eid, message, error });
return new Promise((resolve, reject) => {
ipcMain.once(eid, (event, arg) => {
if (arg.error) reject(arg.message);
else resolve(arg.message);
});
});
}
static ping(window) {
return this.send(window, 'ping');
}
}
class BDIpcEvent extends Module {
constructor(event, args) {
super(args);
this.ipcEvent = event;
this.replied = false;
}
bindings() {
this.send = this.send.bind(this);
this.reply = this.reply.bind(this);
}
send(message) {
this.ipcEvent.sender.send(this.args.__eid, message);
/**
* Sends a message back to the message's sender.
* @param {Any} message Data to send to this message's sender
*/
reply(message, error) {
if (this.replied)
throw {message: 'This message has already been replied to.'};
this.replied = true;
return BDIpc.send(this.ipcEvent.sender, this.eid, message, error);
}
reply(message) {
this.send(message);
reject(err) {
return this.reply(err, true);
}
}
class BDIpc {
static on(channel, cb) {
ipcMain.on(channel, (event, args) => cb(new BDIpcEvent(event, args)));
get message() {
return this.args.message;
}
get error() {
return this.args.error;
}
get eid() {
return this.args.eid;
}
}
module.exports = { BDIpc };

View File

@ -13,6 +13,7 @@ const { BrowserWindow } = require('electron');
const { Module } = require('./modulebase');
const { WindowUtils } = require('./utils');
const { BDIpc } = require('./bdipc');
class CSSEditor extends Module {
@ -23,46 +24,40 @@ class CSSEditor extends Module {
}
/**
* Opens an editor and replies to an IPC event.
* @param {BDIpcEvent} event
* Opens an editor.
* @return {Promise}
*/
openEditor(o) {
if (this.editor) {
if (this.editor.isFocused()) return;
openEditor(options) {
return new Promise((resolve, reject) => {
if (this.editor) {
if (this.editor.isFocused()) return;
this.editor.focus();
this.editor.flashFrame(true);
o.reply(true);
return;
}
this.editor.focus();
this.editor.flashFrame(true);
return resolve(true);
}
const options = Object.assign({}, this.options, o.message);
options = Object.assign({}, this.options, options);
this.editor = new BrowserWindow(options);
this.editor.loadURL('about:blank');
this.editor.setSheetOffset(33);
this.editorUtils = new WindowUtils({ window: this.editor });
this.editor = new BrowserWindow(options);
this.editor.loadURL('about:blank');
this.editor.setSheetOffset(33);
this.editorUtils = new WindowUtils({ window: this.editor });
this.editor.on('close', () => {
this.bd.windowUtils.send('bd-save-csseditor-bounds', this.editor.getBounds());
this.editor = null;
});
this.editor.on('close', () => {
this.bd.windowUtils.send('bd-save-csseditor-bounds', this.editor.getBounds());
this.editor = null;
});
this.editor.once('ready-to-show', () => {
this.editor.show();
});
this.editor.once('ready-to-show', () => {
this.editor.show();
});
this.editor.webContents.on('did-finish-load', () => {
this.editorUtils.injectScript(path.join(this.editorPath, 'csseditor.js'));
o.reply(true);
});
}
/**
* Sets the SCSS in the editor.
*/
setSCSS(scss) {
this.send('set-scss', scss);
this.editor.webContents.on('did-finish-load', () => {
this.editorUtils.injectScript(path.join(this.editorPath, 'csseditor.js'));
resolve(true);
});
})
}
/**
@ -71,8 +66,8 @@ class CSSEditor extends Module {
* @param {Any} data
*/
send(channel, data) {
if (!this.editor) return;
this.editor.webContents.send(channel, data);
if (!this.editor) throw {message: 'The CSS editor is not open.'};
return BDIpc.send(this.editor, channel, data);
}
/**

View File

@ -14,6 +14,7 @@ const path = require('path');
const fs = require('fs');
const { Module } = require('./modulebase');
const { BDIpc } = require('./bdipc');
class Utils {
static async tryParseJson(jsonString) {
@ -177,7 +178,7 @@ class WindowUtils extends Module {
}
executeJavascript(script) {
this.webContents.executeJavaScript(script);
return this.webContents.executeJavaScript(script);
}
injectScript(fpath, variable) {
@ -185,13 +186,15 @@ class WindowUtils extends Module {
}
static injectScript(window, fpath, variable) {
// console.log(`Injecting: ${fpath}`);
window = window.webContents || window;
if (!window) return;
// console.log(`Injecting: ${fpath} to`, window);
const escaped_path = fpath.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
const escaped_variable = variable ? variable.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : null;
if (variable) window.executeJavaScript(`window["${escaped_variable}"] = require("${escaped_path}");`);
else window.executeJavaScript(`require("${escaped_path}");`);
if (variable) return window.executeJavaScript(`window["${escaped_variable}"] = require("${escaped_path}");`);
else return window.executeJavaScript(`require("${escaped_path}");`);
}
on(event, callback) {
@ -199,8 +202,7 @@ class WindowUtils extends Module {
}
send(channel, message) {
channel = channel.startsWith('bd-') ? channel : `bd-${channel}`;
this.webContents.send(channel, message);
return BDIpc.send(this.window, channel, message);
}
}

View File

@ -33,7 +33,7 @@
</template>
<script>
import BDIpc from './BDIpc';
import ClientIPC from 'bdipc';
import { remote } from 'electron';
@ -131,42 +131,38 @@
}
},
created() {
BDIpc.on('set-scss', (_, data) => {
if (data.error) {
console.log(data.error);
return;
}
console.log(data);
this.setScss(data.scss);
});
ClientIPC.on('set-scss', (_, scss) => this.setScss(scss));
BDIpc.on('scss-error', (_, err) => {
ClientIPC.on('scss-error', (_, err) => {
this.error = err;
this.$forceUpdate();
if (err)
console.error('SCSS parse error:', err);
});
BDIpc.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
ClientIPC.on('set-liveupdate', (e, liveUpdate) => this.liveUpdate = liveUpdate);
},
mounted() {
this.codemirror.on('keyup', this.cmOnKeyUp);
BDIpc.sendToDiscord('get-scss');
BDIpc.sendToDiscord('get-liveupdate');
(async () => {
this.setScss(await ClientIPC.sendToDiscord('get-scss'));
this.liveUpdate = await ClientIPC.sendToDiscord('get-liveupdate');
})();
},
watch: {
liveUpdate(liveUpdate) {
BDIpc.sendToDiscord('set-liveupdate', liveUpdate);
ClientIPC.sendToDiscord('set-liveupdate', liveUpdate);
}
},
methods: {
save() {
const scss = this.codemirror.getValue();
BDIpc.sendToDiscord('save-scss', scss);
ClientIPC.sendToDiscord('save-scss', scss);
},
update() {
const scss = this.codemirror.getValue();
BDIpc.sendToDiscord('update-scss', scss);
ClientIPC.sendToDiscord('update-scss', scss);
},
toggleaot() {
this.alwaysOnTop = !this.alwaysOnTop;
@ -180,7 +176,7 @@
this.codemirror.setValue(scss || '');
},
cmOnChange(value) {
if(this.liveUpdate) BDIpc.sendToDiscord('update-scss', value);
if(this.liveUpdate) ClientIPC.sendToDiscord('update-scss', value);
},
cmOnKeyUp(editor, event) {
if (event.ctrlKey) return;