Switch over to using electron protocol, remove code thats no longer necessary

This commit is contained in:
Will O'Beirne 2018-04-29 16:04:34 -04:00
parent 82302b92f6
commit 4c7c6d0a3b
No known key found for this signature in database
GPG Key ID: 44C190DB5DEAF9F6
12 changed files with 127 additions and 187 deletions

View File

@ -8,7 +8,7 @@ import { render } from 'react-dom';
import Root from './Root';
import { configuredStore } from './store';
import consoleAdvertisement from './utils/consoleAdvertisement';
import EnclaveAPI, { registerClient } from 'shared/enclave/client';
import EnclaveAPI from 'shared/enclave/client';
const appEl = document.getElementById('app');
@ -27,6 +27,5 @@ if (process.env.NODE_ENV === 'production') {
}
if (process.env.BUILD_ELECTRON) {
registerClient();
(window as any).EnclaveAPI = EnclaveAPI;
}

View File

@ -1,4 +1,5 @@
import { app } from 'electron';
import { registerServer } from 'shared/enclave/server';
import getWindow from './window';
// Quit application when all windows are closed
@ -20,3 +21,6 @@ app.on('activate', () => {
app.on('ready', () => {
getWindow();
});
// Register enclave protocol
registerServer(app);

View File

@ -4,7 +4,6 @@ import path from 'path';
import MENU from './menu';
import updater from './updater';
import { APP_TITLE } from '../constants';
import ledger from 'shared/enclave/server/wallets/ledger';
const isDevelopment = process.env.NODE_ENV !== 'production';
// Cached reference, preventing recreations

View File

@ -1,2 +0,0 @@
import { registerServer } from 'shared/enclave/server';
registerServer();

View File

@ -1,2 +1,2 @@
import { registerServer } from 'shared/enclave/server';
registerServer();
import { setupClient } from 'shared/enclave/client/preload';
setupClient();

View File

@ -1,6 +1,6 @@
import { listenForResponses, makeRequestExpectingResponse } from './requests';
import { makeRequest } from './requests';
import {
EnclaveEvents,
EnclaveMethods,
GetAddressesParams,
GetAddressesResponse,
GetChainCodeParams,
@ -9,24 +9,17 @@ import {
SignTransactionResponse
} from 'shared/enclave/types';
export function registerClient() {
listenForResponses();
}
const api = {
getAddresses(params: GetAddressesParams) {
return makeRequestExpectingResponse<GetAddressesResponse>(EnclaveEvents.GET_ADDRESSES, params);
return makeRequest<GetAddressesResponse>(EnclaveMethods.GET_ADDRESSES, params);
},
getChainCode(params: GetChainCodeParams) {
return makeRequestExpectingResponse<GetChainCodeResponse>(EnclaveEvents.GET_CHAIN_CODE, params);
return makeRequest<GetChainCodeResponse>(EnclaveMethods.GET_CHAIN_CODE, params);
},
signTransaction(params: SignTransactionParams) {
return makeRequestExpectingResponse<SignTransactionResponse>(
EnclaveEvents.SIGN_TRANSACTION,
params
);
return makeRequest<SignTransactionResponse>(EnclaveMethods.SIGN_TRANSACTION, params);
}
};

View File

@ -0,0 +1,6 @@
import { webFrame } from 'electron';
import { PROTOCOL_NAME } from 'shared/enclave/utils';
export function setupClient() {
webFrame.registerURLSchemeAsPrivileged(PROTOCOL_NAME);
}

View File

@ -1,67 +1,19 @@
import EventEmitter from 'events';
import { EnclaveEvents, EventParams, RpcEvent, RpcRequest } from 'shared/enclave/types';
import { idGeneratorFactory } from 'shared/enclave/utils';
import { EnclaveMethods, EnclaveMethodParams, EnclaveResponse } from 'shared/enclave/types';
import { PROTOCOL_NAME } from 'shared/enclave/utils';
let isListening = false;
const ee = new EventEmitter();
const genId = idGeneratorFactory();
const resEventPrefix = 'enclave-response:';
export function listenForResponses() {
if (isListening) {
return;
}
isListening = true;
window.addEventListener('message', ev => {
// Only take in messages from preload
if (ev.origin !== window.location.origin) {
return;
}
try {
const response = JSON.parse(ev.data);
if (response && response.isResponse && response.id) {
ee.emit(`${resEventPrefix}${response.id}`, response as RpcEvent);
}
} catch (err) {
// no-op, not meant for us
}
});
}
export function makeRequest(type: EnclaveEvents, params: EventParams): RpcRequest {
const id = genId();
const req: RpcRequest = {
id,
type,
params,
isRequest: true
};
window.postMessage(JSON.stringify(req), window.location.origin);
return req;
}
export function makeRequestExpectingResponse<T>(
type: EnclaveEvents,
params: EventParams,
timeout: number = 10000
): Promise<T> {
return new Promise((resolve, reject) => {
const req = makeRequest(type, params);
const eventName = `${resEventPrefix}${req.id}`;
console.log('Listening for', eventName);
ee.once(eventName, (res: RpcEvent<T>) => {
if (res.payload) {
resolve(res.payload);
} else if (res.errMsg) {
reject(new Error(res.errMsg));
export function makeRequest<T>(type: EnclaveMethods, params: EnclaveMethodParams): Promise<T> {
return fetch(`${PROTOCOL_NAME}://${type}`, {
method: 'POST',
body: JSON.stringify(params)
})
.then(res => res.json())
.then((res: EnclaveResponse<T>) => {
if (res.data) {
return res.data;
} else if (res.error) {
throw new Error(res.error.message);
} else {
throw new Error('Unknown response from server');
}
});
setTimeout(() => {
ee.removeAllListeners(eventName);
reject(new Error('Request timed out'));
}, timeout);
});
}

View File

@ -1,14 +1,16 @@
import getAddresses from './getAddresses';
import getChainCode from './getChainCode';
import signTransaction from './signTransaction';
import { EnclaveEvents, EventParams, EventResponse } from 'shared/enclave/types';
import { EnclaveMethods, EnclaveMethodParams, EnclaveMethodResponse } from 'shared/enclave/types';
const handlers: {
[key in EnclaveEvents]: (params: EventParams) => EventResponse | Promise<EventResponse>
[key in EnclaveMethods]: (
params: EnclaveMethodParams
) => EnclaveMethodResponse | Promise<EnclaveMethodResponse>
} = {
[EnclaveEvents.GET_ADDRESSES]: getAddresses,
[EnclaveEvents.GET_CHAIN_CODE]: getChainCode,
[EnclaveEvents.SIGN_TRANSACTION]: signTransaction
[EnclaveMethods.GET_ADDRESSES]: getAddresses,
[EnclaveMethods.GET_CHAIN_CODE]: getChainCode,
[EnclaveMethods.SIGN_TRANSACTION]: signTransaction
};
export default handlers;

View File

@ -1,53 +1,73 @@
import { protocol, App } from 'electron';
import handlers from './handlers';
import { isValidEventType } from 'shared/enclave/utils';
import { RpcRequest, RpcEventSuccess, RpcEventFailure, EventResponse } from 'shared/enclave/types';
import { PROTOCOL_NAME, isValidEventType } from 'shared/enclave/utils';
import { EnclaveMethods, EnclaveMethodParams } from 'shared/enclave/types';
export function registerServer(app: App) {
protocol.registerStandardSchemes([PROTOCOL_NAME]);
app.on('ready', () => {
protocol.registerStringProtocol(PROTOCOL_NAME, async (req, cb) => {
try {
const method = getMethod(req);
const params = getParams(method, req);
const response = await processRequest(method, params);
return cb(JSON.stringify({ data: response }));
} catch (err) {
console.error(`Request to '${req.url}' failed with error:`, err);
return cb(
JSON.stringify({
code: 500,
type: err.name,
message: err.message
})
);
}
});
});
}
function getMethod(req: Electron.RegisterStringProtocolRequest): EnclaveMethods {
const urlSplit = req.url.split(`${PROTOCOL_NAME}://`);
if (!urlSplit[1]) {
throw new Error('No method provided');
}
const method = urlSplit[1].replace('/', '');
if (!isValidEventType(method)) {
throw new Error(`Invalid or unknown method '${method}'`);
}
return method as EnclaveMethods;
}
function getParams(
method: EnclaveMethods,
req: Electron.RegisterStringProtocolRequest
): EnclaveMethodParams {
const data = req.uploadData.find(d => !!d.bytes);
if (!data) {
throw new Error(`No data provided for '${method}'`);
}
async function processRequest(req: RpcRequest) {
try {
const data = await handlers[req.type](req.params);
if (data) {
respondWithPayload(req, data);
}
// TODO: Validate params based on provided method
const params = JSON.parse(data.bytes.toString());
return params.params as EnclaveMethodParams;
} catch (err) {
console.error('Request', req.type, 'failed with error:', err);
respondWithError(req, err.toString());
throw new Error(`Invalid JSON blob provided for '${method}'`);
}
}
function respondWithPayload(req: RpcRequest, payload: EventResponse) {
const response: RpcEventSuccess = {
id: req.id,
isResponse: true,
errMsg: undefined,
payload
};
window.postMessage(JSON.stringify(response), window.location.origin);
}
function respondWithError(req: RpcRequest, errMsg: string) {
const response: RpcEventFailure = {
id: req.id,
isResponse: true,
payload: undefined,
errMsg
};
window.postMessage(JSON.stringify(response), window.location.origin);
}
export function registerServer() {
window.addEventListener('message', (ev: MessageEvent) => {
// Only take in messages from the webview
if (ev.origin !== window.location.origin) {
return;
async function processRequest(method: EnclaveMethods, params: EnclaveMethodParams) {
try {
const data = await handlers[method](params);
if (data) {
return data;
}
try {
const request = JSON.parse(ev.data);
if (request && request.isRequest && isValidEventType(request.type)) {
processRequest(request as RpcRequest);
}
} catch (err) {
// no-op, not meant for us
}
});
} catch (err) {
throw new Error(err);
}
}

View File

@ -1,5 +1,5 @@
// Enclave enums
export enum EnclaveEvents {
export enum EnclaveMethods {
GET_ADDRESSES = 'get-addresses',
GET_CHAIN_CODE = 'get-chain-code',
SIGN_TRANSACTION = 'sign-transaction'
@ -44,32 +44,30 @@ export interface SignTransactionResponse {
}
// All Requests & Responses
export type EventParams = GetAddressesParams | GetChainCodeParams | SignTransactionParams;
export type EventResponse = GetAddressesResponse | GetChainCodeResponse | SignTransactionResponse;
export type EnclaveMethodParams = GetAddressesParams | GetChainCodeParams | SignTransactionParams;
export type EnclaveMethodResponse =
| GetAddressesResponse
| GetChainCodeResponse
| SignTransactionResponse;
// RPC requests, responses & failures
export interface RpcRequest {
isRequest: true;
params: EventParams;
type: EnclaveEvents;
id: number;
export interface EnclaveSuccessResponse<T = EnclaveMethodResponse> {
data: T;
error?: undefined;
}
export interface RpcEventSuccess<T = any> {
isResponse: true;
payload: T;
errMsg: undefined;
id: number;
export interface EnclaveErrorResponse {
data?: undefined;
error: {
code: number;
type: string;
message: string;
};
}
export interface RpcEventFailure {
isResponse: true;
errMsg: string;
payload: undefined;
id: number;
}
export type RpcEvent<T = any> = RpcEventFailure | RpcEventSuccess<T>;
export type EnclaveResponse<T = EnclaveMethodResponse> =
| EnclaveSuccessResponse<T>
| EnclaveErrorResponse;
// Wallet lib
export interface WalletLib {

View File

@ -1,37 +1,6 @@
import { EnclaveEvents, RpcEventHandler } from './types';
import { EnclaveMethods } from './types';
export const idGeneratorFactory = () => {
let callId = 0;
return () => {
const currValue = callId;
callId += 1;
return currValue;
};
};
export const PROTOCOL_NAME = 'eth-enclave';
export const createRpcRequestedEv = (e: EnclaveEvents) => `${e}-requested`;
export const createRpcProcessedEv = (e: EnclaveEvents) => `${e}-processed`;
const eventTypes = Object.values(EnclaveEvents);
export const isValidEventType = (e: EnclaveEvents) => eventTypes.includes(e);
type Resolve<T = any> = (value?: T | PromiseLike<T>) => void;
type Reject<T = any> = (reason?: T) => void;
export type ReceiveCb<Arguments, Ret> = (
res: Resolve,
rej: Reject,
id?: number
) => RpcEventHandler<Arguments, Ret>;
export const receiveOnChannelFactory = <CbArgs = any, CbRet = any, Ret = any>(
cb: ReceiveCb<CbArgs, CbRet>
) => (channel: string, target: any, on: any, id?: number): Promise<Ret> =>
new Promise((res, rej) => on.call(target, channel, cb(res, rej, id)));
export const sendOnChannel = (
channel: string,
payload: any,
target: any,
emit: (args: any) => void
) => emit.call(target, channel, payload);
const eventTypes = Object.values(EnclaveMethods);
export const isValidEventType = (e: string): e is EnclaveMethods => eventTypes.includes(e);