You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

189 lines
5.6 KiB

import { IpcMain } from 'electron';
import { debug } from 'electron-log';
import createLndRpc, * as LND from '@radar/lnrpc';
import { DefaultsKey, ipcChannels, withDefaults } from '../../src/shared';
import { LndNode } from '../../src/shared/types';
/**
* mapping of node name <-> LnRpc to cache these objects. The createLndRpc function
* reads from disk, so this gives us a small bit of performance improvement
*/
let rpcCache: {
[key: string]: LND.LnRpc;
} = {};
/**
* Helper function to lookup a node by name in the cache or create it if
* it doesn't exist
*/
const getRpc = async (node: LndNode): Promise<LND.LnRpc> => {
const { name, ports, paths, networkId } = node;
// TODO: use node unique id for caching since is an application level global variable
const id = `n${networkId}-${name}`;
if (!rpcCache[id]) {
const config = {
server: `127.0.0.1:${ports.grpc}`,
tls: paths.tlsCert,
macaroonPath: paths.adminMacaroon,
};
rpcCache[id] = await createLndRpc(config);
}
return rpcCache[id];
};
const getInfo = async (args: { node: LndNode }): Promise<LND.GetInfoResponse> => {
const rpc = await getRpc(args.node);
return await rpc.getInfo();
};
const walletBalance = async (args: {
node: LndNode;
}): Promise<LND.WalletBalanceResponse> => {
const rpc = await getRpc(args.node);
return await rpc.walletBalance();
};
const newAddress = async (args: { node: LndNode }): Promise<LND.NewAddressResponse> => {
const rpc = await getRpc(args.node);
return await rpc.newAddress();
};
const listPeers = async (args: { node: LndNode }): Promise<LND.ListPeersResponse> => {
const rpc = await getRpc(args.node);
return await rpc.listPeers();
};
const connectPeer = async (args: {
node: LndNode;
req: LND.ConnectPeerRequest;
}): Promise<{}> => {
const rpc = await getRpc(args.node);
return await rpc.connectPeer(args.req);
};
const openChannel = async (args: {
node: LndNode;
req: LND.OpenChannelRequest;
}): Promise<LND.ChannelPoint> => {
const rpc = await getRpc(args.node);
return await rpc.openChannelSync(args.req);
};
const closeChannel = async (args: {
node: LndNode;
req: LND.CloseChannelRequest;
}): Promise<any> => {
const rpc = await getRpc(args.node);
// TODO: capture the stream events and push them to the UI
rpc.closeChannel(args.req);
};
const listChannels = async (args: {
node: LndNode;
req: LND.ListChannelsRequest;
}): Promise<LND.ListChannelsResponse> => {
const rpc = await getRpc(args.node);
return await rpc.listChannels(args.req);
};
const pendingChannels = async (args: {
node: LndNode;
}): Promise<LND.PendingChannelsResponse> => {
const rpc = await getRpc(args.node);
return await rpc.pendingChannels();
};
const createInvoice = async (args: {
node: LndNode;
req: LND.Invoice;
}): Promise<LND.AddInvoiceResponse> => {
const rpc = await getRpc(args.node);
return await rpc.addInvoice(args.req);
};
const payInvoice = async (args: {
node: LndNode;
req: LND.SendRequest;
}): Promise<LND.SendResponse> => {
const rpc = await getRpc(args.node);
return await rpc.sendPaymentSync(args.req);
};
const decodeInvoice = async (args: {
node: LndNode;
req: LND.PayReqString;
}): Promise<LND.PayReq> => {
const rpc = await getRpc(args.node);
return await rpc.decodePayReq(args.req);
};
/**
* A mapping of electron IPC channel names to the functions to execute when
* messages are received
*/
const listeners: {
[key: string]: (...args: any) => Promise<any>;
} = {
[ipcChannels.getInfo]: getInfo,
[ipcChannels.walletBalance]: walletBalance,
[ipcChannels.newAddress]: newAddress,
[ipcChannels.listPeers]: listPeers,
[ipcChannels.connectPeer]: connectPeer,
[ipcChannels.openChannel]: openChannel,
[ipcChannels.closeChannel]: closeChannel,
[ipcChannels.listChannels]: listChannels,
[ipcChannels.pendingChannels]: pendingChannels,
[ipcChannels.createInvoice]: createInvoice,
[ipcChannels.payInvoice]: payInvoice,
[ipcChannels.decodeInvoice]: decodeInvoice,
};
/**
* Sets up the IPC listeners for the main process and maps them to async
* functions.
* @param ipc the IPC object of the main process
*/
export const initLndProxy = (ipc: IpcMain) => {
debug('LndProxyServer: initialize');
Object.entries(listeners).forEach(([channel, func]) => {
const requestChan = `lnd-${channel}-request`;
const responseChan = `lnd-${channel}-response`;
debug(`LndProxyServer: listening for ipc command "${channel}"`);
ipc.on(requestChan, async (event, ...args) => {
// the a message is received by the main process...
debug(
`LndProxyServer: received request "${requestChan}"`,
JSON.stringify(args, null, 2),
);
// inspect the first arg to see if it has a specific channel to reply to
let uniqueChan = responseChan;
if (args && args[0] && args[0].replyTo) {
uniqueChan = args[0].replyTo;
}
try {
// attempt to execute the associated function
let result = await func(...args);
// merge the result with default values since LND omits falsy values
debug(
`LndProxyServer: send response "${uniqueChan}"`,
JSON.stringify(result, null, 2),
);
result = withDefaults(result, channel as DefaultsKey);
// response to the calling process with a reply
event.reply(uniqueChan, result);
} catch (err) {
// reply with an error message if the execution fails
debug(`LndProxyServer: send error "${uniqueChan}"`, JSON.stringify(err, null, 2));
event.reply(uniqueChan, { err: err.message });
}
});
});
};
/**
* Clears the cached rpc instances
*/
export const clearProxyCache = () => {
rpcCache = {};
};