Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CustomProvider #2426

Merged
merged 8 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/web3-providers/src/factories/ProvidersModuleFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import BatchRequest from '../batch-request/BatchRequest';
import EthereumProvider from '../providers/EthereumProvider';
import MetamaskProvider from '../providers/MetamaskProvider';
import MistEthereumProvider from '../providers/MistEthereumProvider';
import CustomProvider from '../providers/CustomProvider';

export default class ProvidersModuleFactory {
/**
Expand Down Expand Up @@ -203,4 +204,17 @@ export default class ProvidersModuleFactory {
createMistEthereumProvider(mistEthereumProvider) {
return new MistEthereumProvider(mistEthereumProvider);
}

/**
* Returns an CustomProvider object
*
* @method createCustomProvider
*
* @param {Object} connection
*
* @returns {CustomProvider}
*/
createCustomProvider(connection) {
return new CustomProvider(connection);
}
}
138 changes: 138 additions & 0 deletions packages/web3-providers/src/providers/CustomProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file CustomProvider.js
* @author Samuel Furter <samuel@ethereum.org>
* @date 2019
*/
import JsonRpcMapper from '../mappers/JsonRpcMapper';
import JsonRpcResponseValidator from '../validators/JsonRpcResponseValidator';

export default class CustomProvider {
/**
* @param {Object} connection
*
* @constructor
*/
constructor(connection) {
this.host = 'CustomProvider';
this.connection = connection;
this.checkConnectionMethods();
}

/**
* Checks if the given custom provider does have the required methods
*
* @method checkConnectionMethods
*
* @returns {Boolean}
*/
checkConnectionMethods() {
if (this.connection.send || this.connection.sendAsync) {
return true;
}

throw new Error('Invalid provider injected!');
}

/**
* Added this method to have a better error message if someone is trying to create a subscription with this provider.
*/
subscribe() {
throw new Error('Subscriptions are not supported with the CustomProvider.');
}

/**
* Added this method to have a better error message if someone is trying to unsubscribe with this provider.
*/
unsubscribe() {
throw new Error('Subscriptions are not supported with the CustomProvider.');
}

/**
* Creates the JSON-RPC payload and sends it to the node.
*
* @method send
*
* @param {String} method
* @param {Array} parameters
*
* @returns {Promise<any>}
*/
send(method, parameters) {
return this.sendPayload(JsonRpcMapper.toPayload(method, parameters)).then((response) => {
const validationResult = JsonRpcResponseValidator.validate(response);

if (validationResult instanceof Error) {
throw validationResult;
}

return response.result;
});
}

/**
* Creates the JSON-RPC batch payload and sends it to the node.
*
* @method sendBatch
*
* @param {AbstractMethod[]} methods
* @param {AbstractWeb3Module} moduleInstance
*
* @returns Promise<Object[]>
*/
sendBatch(methods, moduleInstance) {
let payload = [];

methods.forEach((method) => {
method.beforeExecution(moduleInstance);
payload.push(JsonRpcMapper.toPayload(method.rpcMethod, method.parameters));
});

return this.sendPayload(payload);
}

/**
* Sends the JSON-RPC payload to the node.
*
* @method sendPayload
*
* @param {Object} payload
*
* @returns {Promise<any>}
*/
sendPayload(payload) {
return new Promise((resolve, reject) => {
if (this.connection.sendAsync) {
this.connection.sendAsync(payload, (error, response) => {
if (!error) {
resolve(response);
}

reject(error);
});

return;
}

this.connection.send(payload, (error, response) => {
if (!error) {
resolve(response);
}

reject(error);
});
});
}
}
15 changes: 10 additions & 5 deletions packages/web3-providers/src/resolvers/ProviderResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,19 @@ export default class ProviderResolver {
return this.providersModuleFactory.createMistEthereumProvider(provider);
}

if (provider.constructor.name === 'MetamaskInpageProvider') {
return this.providersModuleFactory.createMetamaskProvider(provider);
}

if (provider.isEIP1193) {
return this.providersModuleFactory.createEthereumProvider(provider);
}

return provider;
switch (provider.constructor.name) {
case 'MetamaskInpageProvider':
return this.providersModuleFactory.createMetamaskProvider(provider);
case 'HttpProvider':
case 'IpcProvider':
case 'WebsocketProvider':
return provider;
default:
return this.providersModuleFactory.createCustomProvider(provider);
}
}
}
137 changes: 137 additions & 0 deletions packages/web3-providers/tests/src/providers/CustomProviderTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import JsonRpcMapper from '../../../src/mappers/JsonRpcMapper';
import JsonRpcResponseValidator from '../../../src/validators/JsonRpcResponseValidator';
import AbstractWeb3Module from '../../__mocks__/AbstractWeb3Module';
import AbstractMethod from '../../__mocks__/AbstractMethod';
import CustomProvider from '../../../src/providers/CustomProvider';

// Mocks
jest.mock('../../../src/factories/ProvidersModuleFactory');

/**
* CustomProvider test
*/
describe('CustomProviderTest', () => {
let customProvider, connectionMock;

beforeEach(() => {
connectionMock = {send: jest.fn(), sendAsync: jest.fn()};

customProvider = new CustomProvider(connectionMock);
});

it('constructor check', () => {
expect(customProvider.connection).toEqual(connectionMock);
});

it('calls subscribe and throws error', () => {
expect(() => {
customProvider.subscribe();
}).toThrow('Subscriptions are not supported with the CustomProvider.');
});

it('calls unsubscribe and throws error', () => {
expect(() => {
customProvider.unsubscribe();
}).toThrow('Subscriptions are not supported with the CustomProvider.');
});

it('calls send and returns with a resolved promise', async () => {
JsonRpcMapper.toPayload = jest.fn();
JsonRpcMapper.toPayload.mockReturnValueOnce({id: '0x0'});

JsonRpcResponseValidator.validate = jest.fn();
JsonRpcResponseValidator.validate.mockReturnValueOnce(true);

connectionMock.sendAsync = jest.fn((payload, callback) => {
expect(payload).toEqual({id: '0x0'});

callback(false, {result: true});
});

const response = await customProvider.send('rpc_method', []);

expect(response).toEqual(true);

expect(JsonRpcMapper.toPayload).toHaveBeenCalledWith('rpc_method', []);

expect(JsonRpcResponseValidator.validate).toHaveBeenCalledWith({result: true});

expect(connectionMock.sendAsync).toHaveBeenCalled();
});

it('calls send without the sendAsync method and returns with a resolved promise', async () => {
JsonRpcMapper.toPayload = jest.fn();
JsonRpcMapper.toPayload.mockReturnValueOnce({id: '0x0'});

JsonRpcResponseValidator.validate = jest.fn();
JsonRpcResponseValidator.validate.mockReturnValueOnce(true);

connectionMock.sendAsync = false;
connectionMock.send = jest.fn((payload, callback) => {
expect(payload).toEqual({id: '0x0'});

callback(false, {result: true});
});

const response = await customProvider.send('rpc_method', []);

expect(response).toEqual(true);

expect(JsonRpcMapper.toPayload).toHaveBeenCalledWith('rpc_method', []);

expect(JsonRpcResponseValidator.validate).toHaveBeenCalledWith({result: true});

expect(connectionMock.send).toHaveBeenCalled();
});

it('calls send and returns with a rejected promise because of an invalid JSON-RPC response', async () => {
JsonRpcMapper.toPayload = jest.fn();
JsonRpcMapper.toPayload.mockReturnValueOnce({id: '0x0'});

JsonRpcResponseValidator.validate = jest.fn();
JsonRpcResponseValidator.validate.mockReturnValueOnce(new Error('invalid'));

connectionMock.sendAsync = jest.fn((payload, callback) => {
expect(payload).toEqual({id: '0x0'});

callback(false, true);
});

await expect(customProvider.send('rpc_method', [])).rejects.toThrow('invalid');

expect(JsonRpcResponseValidator.validate).toHaveBeenCalledWith(true);

expect(JsonRpcMapper.toPayload).toHaveBeenCalledWith('rpc_method', []);

expect(connectionMock.sendAsync).toHaveBeenCalled();
});

it('calls sendBatch and returns with a resolved promise', async () => {
const abstractMethodMock = new AbstractMethod();

const moduleInstanceMock = new AbstractWeb3Module();

abstractMethodMock.beforeExecution = jest.fn();
abstractMethodMock.rpcMethod = 'rpc_method';
abstractMethodMock.parameters = [];

JsonRpcMapper.toPayload = jest.fn();
JsonRpcMapper.toPayload.mockReturnValueOnce({id: '0x0'});

connectionMock.sendAsync = jest.fn((payload, callback) => {
expect(payload).toEqual([{id: '0x0'}]);

callback(false, true);
});

const response = await customProvider.sendBatch([abstractMethodMock], moduleInstanceMock);

expect(response).toEqual(true);

expect(JsonRpcMapper.toPayload).toHaveBeenCalledWith('rpc_method', []);

expect(connectionMock.sendAsync).toHaveBeenCalled();

expect(abstractMethodMock.beforeExecution).toHaveBeenCalled();
});
});
Loading