API Subscriptions (#62)
* Upgrades * Add getHead & newHead * Handle pubsub interfaces * Flow fixes
This commit is contained in:
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@polkadot/primitives-json": "^0.9.5",
|
||||
"@polkadot/util": "^0.18.2",
|
||||
"@polkadot/util": "^0.18.4",
|
||||
"babel-runtime": "^6.26.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2018 Jaco Greeff
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { InterfaceMethodDefinition } from '../types';
|
||||
|
||||
/**
|
||||
@name getHead
|
||||
@signature chain_getHead (): HeaderHash
|
||||
@summary Retrieves the best headerHash.
|
||||
@description
|
||||
Return the block hash for the lastest/best.
|
||||
*/
|
||||
module.exports = ({
|
||||
inputs: [],
|
||||
output: {
|
||||
type: 'HeaderHash'
|
||||
}
|
||||
}: InterfaceMethodDefinition);
|
||||
@@ -5,13 +5,17 @@
|
||||
|
||||
import type { InterfaceDefinition } from '../types';
|
||||
|
||||
const getHead = require('./getHead');
|
||||
const getHeader = require('./getHeader');
|
||||
const newHead = require('./newHead');
|
||||
|
||||
/**
|
||||
@summary Methods to retrieve chain data.
|
||||
*/
|
||||
module.exports = ({
|
||||
methods: {
|
||||
getHeader
|
||||
getHead,
|
||||
getHeader,
|
||||
newHead
|
||||
}
|
||||
}: InterfaceDefinition);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2018 Jaco Greeff
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { InterfaceMethodDefinition } from '../types';
|
||||
|
||||
/**
|
||||
@name getHead
|
||||
@signature chain_newHead (): HeaderHash
|
||||
@summary Retrieves the best headerHash via subscription.
|
||||
@description
|
||||
Return the block hash for the lastest/best.
|
||||
*/
|
||||
module.exports = ({
|
||||
isSubscription: true,
|
||||
inputs: [],
|
||||
output: {
|
||||
type: 'HeaderHash'
|
||||
}
|
||||
}: InterfaceMethodDefinition);
|
||||
@@ -7,7 +7,7 @@ export type InterfaceTypes = 'author' | 'chain' | 'extra' | 'state';
|
||||
|
||||
export type FormatInputType = 'Bytes' | 'H256' | 'HeaderHash' | 'String';
|
||||
|
||||
export type FormatOutputType = 'BlockNumber' | 'Bytes' | 'Header' | 'U64';
|
||||
export type FormatOutputType = 'BlockNumber' | 'Bytes' | 'Header' | 'HeaderHash' | 'U64';
|
||||
|
||||
export type InterfaceInputType = {
|
||||
name: string,
|
||||
@@ -18,8 +18,14 @@ export type InterfaceOutputType = {
|
||||
type: FormatOutputType
|
||||
};
|
||||
|
||||
export type InterfaceMethodDefinition$Subscribe = {
|
||||
subscribe: string,
|
||||
unsubscribe: string
|
||||
}
|
||||
|
||||
export type InterfaceMethodDefinition = {
|
||||
deprecated?: boolean,
|
||||
isDeprecated?: boolean,
|
||||
isSubscription?: boolean,
|
||||
inputs: Array<InterfaceInputType>,
|
||||
output: InterfaceOutputType
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"nock": "^9.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@polkadot/util": "^0.18.2",
|
||||
"@polkadot/util": "^0.18.4",
|
||||
"babel-runtime": "^6.26.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"websocket": "^1.0.25"
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"@polkadot/api-format": "^0.8.6",
|
||||
"@polkadot/api-jsonrpc": "^0.8.6",
|
||||
"@polkadot/api-provider": "^0.8.6",
|
||||
"@polkadot/util": "^0.18.2",
|
||||
"@polkadot/util": "^0.18.4",
|
||||
"babel-runtime": "^6.26.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,23 +9,23 @@ import type { ApiInterface$Section } from '../types';
|
||||
|
||||
const interfaces = require('@polkadot/api-jsonrpc');
|
||||
|
||||
const createMethod = require('./method');
|
||||
const createSubscribe = require('./subscribe');
|
||||
const methodSend = require('./methodSend');
|
||||
const methodSubscribe = require('./methodSubscribe');
|
||||
|
||||
module.exports = function createInterface (provider: ProviderInterface, section: InterfaceTypes): ApiInterface$Section {
|
||||
const exposed: $Shape<ApiInterface$Section> = {};
|
||||
const { methods } = interfaces[section];
|
||||
|
||||
exposed.subscribe = createSubscribe(provider, section, methods);
|
||||
exposed.unsubscribe = provider.unsubscribe;
|
||||
|
||||
return Object
|
||||
.keys(methods)
|
||||
.reduce((exposed, name: string) => {
|
||||
const rpcName = `${section}_${name}`;
|
||||
const method = createMethod(provider, rpcName, methods[name]);
|
||||
const def = methods[name];
|
||||
|
||||
exposed[name] = method;
|
||||
// flowlint-next-line sketchy-null-bool:off
|
||||
exposed[name] = def.isSubscription
|
||||
? methodSubscribe(provider, rpcName, name, methods[name])
|
||||
: methodSend(provider, rpcName, name, methods[name]);
|
||||
|
||||
return exposed;
|
||||
}, exposed);
|
||||
|
||||
@@ -16,6 +16,11 @@ jest.mock('./@polkadot/api-jsonrpc', () => ({
|
||||
bleh: {
|
||||
inputs: [],
|
||||
output: { type: 'Address' }
|
||||
},
|
||||
pubsub: {
|
||||
isSubscription: true,
|
||||
inputs: [],
|
||||
output: { type: 'Address' }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +43,7 @@ describe('createInterface', () => {
|
||||
|
||||
it('adds the specified methods to the interface', () => {
|
||||
expect(Object.keys(container)).toEqual(
|
||||
['subscribe', 'unsubscribe', 'blah', 'bleh']
|
||||
['blah', 'bleh', 'pubsub']
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
|
||||
import type { InterfaceMethodDefinition } from '@polkadot/api-jsonrpc/types';
|
||||
import type { ProviderInterface } from '@polkadot/api-provider/types';
|
||||
import type { ApiInterface$Section$Method } from '../types';
|
||||
|
||||
const formatOutput = require('@polkadot/api-format/output');
|
||||
const ExtError = require('@polkadot/util/ext/error');
|
||||
const jsonrpcSignature = require('@polkadot/util/jsonrpc/signature');
|
||||
|
||||
type Method = (..._params: Array<mixed>) => Promise<mixed>;
|
||||
|
||||
const createParams = require('./params');
|
||||
|
||||
module.exports = function createMethod (provider: ProviderInterface, rpcName: string, { inputs, output }: InterfaceMethodDefinition): Method {
|
||||
return async (..._params: Array<mixed>): Promise<mixed> => {
|
||||
module.exports = function createMethodSend (provider: ProviderInterface, rpcName: string, name: string, { inputs, output }: InterfaceMethodDefinition): ApiInterface$Section$Method {
|
||||
const call = async (..._params: Array<mixed>): Promise<mixed> => {
|
||||
// TODO: Deprecated warning
|
||||
try {
|
||||
const params = createParams(rpcName, _params, inputs);
|
||||
const result = await provider.send(rpcName, params);
|
||||
@@ -25,4 +25,7 @@ module.exports = function createMethod (provider: ProviderInterface, rpcName: st
|
||||
throw new ExtError(`${jsonrpcSignature(rpcName, inputs, output)}:: ${error.message}`, (error: ExtError).code);
|
||||
}
|
||||
};
|
||||
|
||||
// flowlint-next-line unclear-type:off
|
||||
return ((call: any): ApiInterface$Section$Method);
|
||||
};
|
||||
@@ -2,9 +2,9 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
const createMethod = require('./method');
|
||||
const createMethod = require('./methodSend');
|
||||
|
||||
describe('createMethod', () => {
|
||||
describe('methodCall', () => {
|
||||
let methods;
|
||||
let provider;
|
||||
|
||||
@@ -30,7 +30,7 @@ describe('createMethod', () => {
|
||||
});
|
||||
|
||||
it('wraps errors with the call signature', () => {
|
||||
const method = createMethod(provider, 'test_blah', methods.blah);
|
||||
const method = createMethod(provider, 'test_blah', 'blah', methods.blah);
|
||||
|
||||
return method().catch((error) => {
|
||||
expect(error.message).toMatch(/test_blah \(foo: Bytes\): Bytes/);
|
||||
@@ -38,7 +38,7 @@ describe('createMethod', () => {
|
||||
});
|
||||
|
||||
it('checks for mismatched parameters', () => {
|
||||
const method = createMethod(provider, 'test_bleh', methods.bleh);
|
||||
const method = createMethod(provider, 'test_bleh', 'bleh', methods.bleh);
|
||||
|
||||
return method(1).catch((error) => {
|
||||
expect(error.message).toMatch(/0 params expected, found 1 instead/);
|
||||
@@ -46,7 +46,7 @@ describe('createMethod', () => {
|
||||
});
|
||||
|
||||
it('calls the provider with the correct parameters', () => {
|
||||
const method = createMethod(provider, 'test_blah', methods.blah);
|
||||
const method = createMethod(provider, 'test_blah', 'blah', methods.blah);
|
||||
|
||||
return method(new Uint8Array([0x12, 0x34])).then(() => {
|
||||
expect(provider.send).toHaveBeenCalledWith('test_blah', ['0x1234']);
|
||||
+11
-5
@@ -5,6 +5,7 @@
|
||||
|
||||
import type { InterfaceMethodDefinition } from '@polkadot/api-jsonrpc/types';
|
||||
import type { ProviderInterface, ProviderInterface$Callback } from '@polkadot/api-provider/types';
|
||||
import type { ApiInterface$Section$Method } from '../types';
|
||||
|
||||
const formatOutput = require('@polkadot/api-format/output');
|
||||
const assert = require('@polkadot/util/assert');
|
||||
@@ -12,12 +13,12 @@ const ExtError = require('@polkadot/util/ext/error');
|
||||
const isFunction = require('@polkadot/util/is/function');
|
||||
const jsonrpcSignature = require('@polkadot/util/jsonrpc/signature');
|
||||
|
||||
type Method = (...params: Array<mixed>) => Promise<number>;
|
||||
|
||||
const createParams = require('./params');
|
||||
|
||||
module.exports = function createSubscribeMethod (provider: ProviderInterface, rpcName: string, { inputs, output }: InterfaceMethodDefinition): Method {
|
||||
return async (..._params: Array<mixed>): Promise<number> => {
|
||||
module.exports = function methodSubscribe (provider: ProviderInterface, rpcName: string, name: string, { inputs, output }: InterfaceMethodDefinition): ApiInterface$Section$Method {
|
||||
const unsubscribe = (subscriptionId: mixed): Promise<mixed> =>
|
||||
provider.send(`unsubscribe_${name}`, [subscriptionId]);
|
||||
const call = async (..._params: Array<mixed>): Promise<mixed> => {
|
||||
try {
|
||||
// flowlint-next-line unclear-type:off
|
||||
const cb = ((_params.pop(): any): ProviderInterface$Callback);
|
||||
@@ -26,7 +27,7 @@ module.exports = function createSubscribeMethod (provider: ProviderInterface, rp
|
||||
|
||||
const params = createParams(rpcName, _params, inputs);
|
||||
|
||||
return provider.subscribe(rpcName, params, (error: ?Error, result: mixed) => {
|
||||
return provider.subscribe(`subscribe_${name}`, params, (error: ?Error, result: mixed) => {
|
||||
if (error) {
|
||||
cb(error);
|
||||
} else {
|
||||
@@ -37,4 +38,9 @@ module.exports = function createSubscribeMethod (provider: ProviderInterface, rp
|
||||
throw new ExtError(`${jsonrpcSignature(rpcName, inputs, output)}:: ${error.message}`, (error: ExtError).code);
|
||||
}
|
||||
};
|
||||
|
||||
call.unsubscribe = unsubscribe;
|
||||
|
||||
// flowlint-next-line unclear-type:off
|
||||
return ((call: any): ApiInterface$Section$Method);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
// Copyright 2017-2018 Jaco Greeff
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { InterfaceDefinition$Methods } from '@polkadot/api-jsonrpc/types';
|
||||
import type { ProviderInterface } from '@polkadot/api-provider/types';
|
||||
|
||||
const assert = require('@polkadot/util/assert');
|
||||
|
||||
type Method = (name: string, ...params: Array<mixed>) => Promise<number>;
|
||||
|
||||
const subscribeMethod = require('./subscribeMethod');
|
||||
|
||||
module.exports = function createSubscribe (provider: ProviderInterface, section: string, methods: InterfaceDefinition$Methods): Method {
|
||||
return async (name: string, ...params: Array<mixed>): Promise<number> => {
|
||||
const rpcName = `${section}_${name}`;
|
||||
|
||||
assert(methods[name], `Unable to find '${rpcName}' subscription`);
|
||||
|
||||
const fn = subscribeMethod(provider, rpcName, methods[name]);
|
||||
|
||||
return fn.apply(null, params);
|
||||
};
|
||||
};
|
||||
@@ -1,76 +0,0 @@
|
||||
// Copyright 2017-2018 Jaco Greeff
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
const createSubscribe = require('./subscribe');
|
||||
|
||||
describe('createSubscribe', () => {
|
||||
let methods;
|
||||
let provider;
|
||||
let sub;
|
||||
let cb;
|
||||
|
||||
beforeEach(() => {
|
||||
methods = {
|
||||
blah: {
|
||||
inputs: [
|
||||
{ name: 'foo', type: 'Address' }
|
||||
],
|
||||
output: { type: 'Address' }
|
||||
},
|
||||
bleh: {
|
||||
inputs: [],
|
||||
output: { type: 'Address' }
|
||||
}
|
||||
};
|
||||
|
||||
provider = {
|
||||
subscribe: jest.fn((method, params, _cb) => {
|
||||
cb = _cb;
|
||||
|
||||
return Promise.resolve(12345);
|
||||
}),
|
||||
unsubscribe: jest.fn(() => {
|
||||
return Promise.resolve(true);
|
||||
})
|
||||
};
|
||||
|
||||
sub = createSubscribe(provider, 'test', methods);
|
||||
});
|
||||
|
||||
it('it does not subscribe to not-found endpoint', () => {
|
||||
return sub('notFound').catch((error) => {
|
||||
expect(error.message).toMatch(/Unable to find 'test_notFound' subscription/);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the subscription', () => {
|
||||
return sub('bleh', () => true).then((id) => {
|
||||
expect(id).toEqual(12345);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns values as they are available', (done) => {
|
||||
sub('bleh', () => {
|
||||
done();
|
||||
});
|
||||
|
||||
cb(null, '0x123');
|
||||
});
|
||||
|
||||
it('returns errors as they are available', (done) => {
|
||||
sub('bleh', (error) => {
|
||||
expect(error.message).toEqual('test error');
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
cb(new Error('test error'));
|
||||
});
|
||||
|
||||
it('checks that valid callback is provided', () => {
|
||||
return sub('bleh').catch((error) => {
|
||||
expect(error.message).toMatch(/Expected callback in last position/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -5,13 +5,13 @@
|
||||
|
||||
import type { InterfaceTypes } from '@polkadot/api-jsonrpc/types';
|
||||
|
||||
export type ApiInterface$Section$Method = (...params: Array<mixed>) => Promise<mixed>;
|
||||
export type ApiInterface$Section$Method = {
|
||||
(...params: Array<mixed>): Promise<mixed>;
|
||||
unsubscribe: (id: number) => Promise<mixed>;
|
||||
}
|
||||
|
||||
export type ApiInterface$Section = {
|
||||
[string]: ApiInterface$Section$Method,
|
||||
|
||||
subscribe: (name: string, ...params: Array<mixed>) => Promise<number>,
|
||||
unsubscribe: (id: number) => Promise<boolean>
|
||||
[string]: ApiInterface$Section$Method
|
||||
};
|
||||
|
||||
export type ApiInterface = {
|
||||
|
||||
@@ -209,9 +209,9 @@
|
||||
deasync "^0.1.12"
|
||||
ip-regex "^2.1.0"
|
||||
|
||||
"@polkadot/util@^0.18.2":
|
||||
version "0.18.2"
|
||||
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-0.18.2.tgz#a00fc2c27e63f9ed1b297eb9109db97234677194"
|
||||
"@polkadot/util@^0.18.4":
|
||||
version "0.18.4"
|
||||
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-0.18.4.tgz#b1886a3d4663d302fe1234705f506fae95b371b8"
|
||||
dependencies:
|
||||
babel-runtime "^6.26.0"
|
||||
bn.js "^4.11.8"
|
||||
|
||||
Reference in New Issue
Block a user