mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
269 lines
7.8 KiB
JavaScript
269 lines
7.8 KiB
JavaScript
'use strict';
|
|
|
|
const { ipcRenderer } = require('electron');
|
|
|
|
const apiEnums = require('../enums/api.js');
|
|
const proxyCmds = apiEnums.proxyCmds;
|
|
|
|
/**
|
|
* Creates and returns a proxy (in renderer process) that will use IPC
|
|
* with main process where "real" instance is created.
|
|
*
|
|
* The constructor is executed synchronously, so take care to not block
|
|
* processes.
|
|
*
|
|
* All method calls will be sent over IPC to main process, evaulated and
|
|
* result returned back to main process. Method calls return a promise.
|
|
*
|
|
* Special method calls: "addEventListener" and "removeEventListener" allow
|
|
* attaching/detaching to events.
|
|
*
|
|
* Getters (e.g., x.y) will return a promise that gets fullfilled
|
|
* when ipc returns value.
|
|
*
|
|
* Setters are synchronously executed (so take care).
|
|
*
|
|
* Note: The "real" instance should implement a destroy method (e.g., close) that
|
|
* should be used to destroy the instance held in main process, otherwise a
|
|
* memory leak will occur - as renderer can not know when instance is no longer
|
|
* used. Would like to incorporate: https://github.com/EvolveLabs/electron-weak
|
|
*
|
|
* @param {Class} ApiClass reference to prototype/class constructor.
|
|
* @return {object} proxy for ApiClass.
|
|
*/
|
|
function createProxy(ApiClass) {
|
|
return new Proxy(ApiClass, constructorHandler);
|
|
}
|
|
|
|
let id = 1;
|
|
function uniqueId() {
|
|
return id++;
|
|
}
|
|
|
|
let constructorHandler = {
|
|
construct: function(target, argumentsList) {
|
|
var arg = {
|
|
className: target.name,
|
|
constructorArgsArray: argumentsList
|
|
};
|
|
|
|
var objId = ipcRenderer.sendSync(proxyCmds.createObject, arg);
|
|
|
|
if (!objId) {
|
|
throw new Error('can not create obj: ' + target.name);
|
|
}
|
|
|
|
var ProxyClass = new target();
|
|
ProxyClass._objId = objId;
|
|
ProxyClass._callbacks = new WeakMap();
|
|
|
|
let instanceHandler = {
|
|
get: instanceGetHandler,
|
|
set: instanceSetHandler
|
|
}
|
|
|
|
// work like to incorporate something like https://github.com/EvolveLabs/electron-weak
|
|
// here to tell when object is destroyed so we can ipc main process to
|
|
// loss ref to liveObj.
|
|
|
|
return new Proxy(ProxyClass, instanceHandler);
|
|
},
|
|
|
|
// static getter and method handler
|
|
get: staticGetHandler
|
|
}
|
|
|
|
function instanceGetHandler(target, name) {
|
|
// all methods and getters we support should be on the prototype
|
|
let prototype = Object.getPrototypeOf(target);
|
|
let desc = Object.getOwnPropertyDescriptor(prototype, name);
|
|
|
|
// does this have a "getter"
|
|
if (desc && desc.get) {
|
|
return getHandler(target, name, false);
|
|
}
|
|
// does this have a method
|
|
if (desc && typeof desc.value === 'function') {
|
|
if (name === 'addEventListener') {
|
|
return addEventHandler(target);
|
|
}
|
|
|
|
if (name === 'removeEventListener') {
|
|
return removeEventHandler(target);
|
|
}
|
|
|
|
return methodHandler(target, name, false);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function addEventHandler(target) {
|
|
return function(eventName, callback) {
|
|
var callbackId = eventName + uniqueId();
|
|
var args = {
|
|
callbackId: callbackId,
|
|
objId: target._objId,
|
|
eventName: eventName
|
|
};
|
|
ipcRenderer.send(proxyCmds.addEvent, args);
|
|
|
|
let callbackFunc = function(arg) {
|
|
if (arg.callbackId === callbackId) {
|
|
callback(args.result);
|
|
}
|
|
}
|
|
|
|
ipcRenderer.on(proxyCmds.eventCallback, callbackFunc);
|
|
|
|
target._callbacks.set(callback, {
|
|
callbackId: callbackId,
|
|
callbackbackFunc: callbackFunc
|
|
});
|
|
|
|
}
|
|
}
|
|
|
|
function removeEventHandler(target) {
|
|
return function(eventName, callback) {
|
|
if (target._callbacks && target._callbacks.has(callback)) {
|
|
let callbackObj = target._callbacks.get(callback);
|
|
|
|
let args = {
|
|
eventName: eventName,
|
|
callbackId: callbackObj.callbackId,
|
|
objId: target._objId
|
|
}
|
|
|
|
ipcRenderer.removeListener(proxyCmds.eventCallback,
|
|
callbackObj.callbackbackFunc);
|
|
|
|
ipcRenderer.send(proxyCmds.removeEvent, args);
|
|
|
|
target._callbacks.delete(callback);
|
|
}
|
|
}
|
|
}
|
|
|
|
function methodHandler(target, methodName, isStatic) {
|
|
return function(...argPassedToMethod) {
|
|
return new Promise(function(resolve, reject) {
|
|
var invokeId = methodName + uniqueId();
|
|
var args = {
|
|
invokeId: invokeId,
|
|
objId: target._objId,
|
|
methodName: methodName,
|
|
arguments: argPassedToMethod,
|
|
isStatic: isStatic,
|
|
className: target.name
|
|
}
|
|
|
|
if (!isStatic) {
|
|
args.objId = target._objId;
|
|
}
|
|
|
|
ipcRenderer.on(proxyCmds.invokeResult, resultCallback);
|
|
ipcRenderer.send(proxyCmds.invokeMethod, args);
|
|
|
|
function removeEventListener() {
|
|
ipcRenderer.removeListener(proxyCmds.invokeResult,
|
|
resultCallback);
|
|
}
|
|
function resultCallback(arg) {
|
|
if (arg.invokeId === invokeId) {
|
|
window.clearTimeout(timer);
|
|
removeEventListener();
|
|
if (arg.error) {
|
|
reject('method called failed: ' + arg.error);
|
|
} else {
|
|
resolve(arg.returnValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// timeout in case we never hear anything back from main process
|
|
let timer = setTimeout(function() {
|
|
removeEventListener();
|
|
reject('timeout_no_reponse');
|
|
}, 5000);
|
|
});
|
|
}
|
|
}
|
|
|
|
function getHandler(target, property, isStatic) {
|
|
return new Promise(function(resolve, reject) {
|
|
var getterId = property + uniqueId();
|
|
var args = {
|
|
getterId: getterId,
|
|
getterProperty: property,
|
|
isStatic: isStatic,
|
|
className: target.name
|
|
}
|
|
|
|
if (!isStatic) {
|
|
args.objId = target._objId;
|
|
}
|
|
|
|
ipcRenderer.on(proxyCmds.getResult, resultCallback);
|
|
ipcRenderer.send(proxyCmds.get, args);
|
|
|
|
function removeEventListener() {
|
|
ipcRenderer.removeListener(proxyCmds.getResult,
|
|
resultCallback);
|
|
}
|
|
function resultCallback(arg) {
|
|
if (arg.getterId === getterId) {
|
|
window.clearTimeout(timer);
|
|
removeEventListener();
|
|
if (arg.error) {
|
|
reject('getter called failed: ' + arg.error);
|
|
} else {
|
|
resolve(arg.returnValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
// timeout in case we never hear anything back from main process
|
|
let timer = setTimeout(function() {
|
|
removeEventListener();
|
|
reject('timeout_no_reponse');
|
|
}, 5000);
|
|
});
|
|
}
|
|
|
|
function instanceSetHandler(target, property, value) {
|
|
let prototype = Object.getPrototypeOf(target);
|
|
let desc = Object.getOwnPropertyDescriptor(prototype, property);
|
|
if (desc && desc.set) {
|
|
var args = {
|
|
objId: target._objId,
|
|
setterProperty: property,
|
|
setterValue: value
|
|
}
|
|
|
|
ipcRenderer.sendSync(proxyCmds.set, args);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function staticGetHandler(target, name) {
|
|
// all methods and getters we support should be on the prototype
|
|
let desc = Object.getOwnPropertyDescriptor(target, name);
|
|
|
|
// does this have a static "getter"
|
|
if (desc && desc.get) {
|
|
return getHandler(target, name, true);
|
|
}
|
|
// does this have a static method
|
|
if (desc && typeof desc.value === 'function') {
|
|
return methodHandler(target, name, true);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
|
|
module.exports = createProxy
|