diff --git a/README.md b/README.md index d3eca78..9dc30f3 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A browser-compatible JSON-RPC client with multiple transports: import { RequestManager, HTTPTransport, Client } from "@open-rpc/client-js"; const transport = new HTTPTransport("http://localhost:8545"); const client = new Client(new RequestManager([transport])); -const result = await client.request("addition", [2, 2]); +const result = await client.request({method: "addition", params: [2, 2]}); // => { jsonrpc: '2.0', id: 1, result: 4 } ``` diff --git a/package.json b/package.json index 2e49366..f763d49 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": " A browser-compatible JSON-RPC client with multiple transports.", "main": "build/index.js", "scripts": { - "build": "tsc && typedoc --out docs", + "build": "tsc && typedoc --out docs --includeDeclarations --excludeExternals --excludeNotExported --excludePrivate --hideGenerator --mode file --readme none", "lint": "tslint --fix -p .", "test": "npm run lint && jest --coverage" }, diff --git a/src/Error.ts b/src/Error.ts index 355e8dc..4943883 100644 --- a/src/Error.ts +++ b/src/Error.ts @@ -1,8 +1,10 @@ +import { ProviderRpcError } from "./ProviderInterface"; + export const ERR_TIMEOUT = 7777; export const ERR_MISSIING_ID = 7878; export const ERR_UNKNOWN = 7979; -export class JSONRPCError extends Error { +export class JSONRPCError extends Error implements ProviderRpcError{ public message: string; public code: number; public data: any; diff --git a/src/ProviderInterface.ts b/src/ProviderInterface.ts new file mode 100644 index 0000000..09a3e42 --- /dev/null +++ b/src/ProviderInterface.ts @@ -0,0 +1,14 @@ +export interface ProviderRequestArguments { + readonly method: string; + readonly params?: readonly unknown[] | object; +} + +export interface Provider { + request(args: ProviderRequestArguments): Promise; +} + +export interface ProviderRpcError extends Error { + message: string; + code: number; + data?: unknown; +} diff --git a/src/RequestManager.test.ts b/src/RequestManager.test.ts index 25f991c..033092b 100644 --- a/src/RequestManager.test.ts +++ b/src/RequestManager.test.ts @@ -25,7 +25,7 @@ describe("client-js", () => { addMockServerTransport(emitter, "to1://local/rpc-request", "from1"); const transport = new EventEmitterTransport(emitter, "from1", "to1://local/rpc-request"); const c = new RequestManager([transport]); - const result = await c.request("foo", ["bar"]); + const result = await c.request({ method: "foo", params: ["bar"] }); expect(result.method).toEqual("foo"); expect(result.params).toEqual(["bar"]); }); @@ -35,8 +35,8 @@ describe("client-js", () => { addMockServerTransport(emitter, "to1://local/rpc-error", "from1"); const transport = new EventEmitterTransport(emitter, "from1", "to1://local/rpc-error"); const c = new RequestManager([transport]); - await expect(c.request("foo", ["bar"])).rejects.toThrowError("Error message"); - }); + await expect(c.request({ method: "foo", params: ["bar"] })).rejects.toThrowError("Error message"); + }); it("can error on malformed response and recieve error", async () => { const emitter = new EventEmitter(); @@ -48,7 +48,7 @@ describe("client-js", () => { resolve(d); }); }); - await expect(c.request("foo", ["bar"], false, 1000)) + await expect(c.request({ method: "foo", params: ["bar"] }, false, 1000)) .rejects.toThrowError("Request timeout request took longer than 1000 ms to resolve"); const formatError = await unknownError as JSONRPCError; expect(formatError.message).toContain("Bad response format"); @@ -69,8 +69,8 @@ describe("client-js", () => { const c = new RequestManager([transport]); c.startBatch(); const requests = [ - c.request("foo", ["bar"]), - c.request("foo", ["bar"]), + c.request({ method: "foo", params: ["bar"] }), + c.request({ method: "foo", params: ["bar"] }), ]; c.stopBatch(); await expect(Promise.all(requests)).rejects.toThrowError("Error message"); @@ -85,8 +85,8 @@ describe("client-js", () => { const c = new RequestManager([transport]); c.startBatch(); const requests = [ - c.request("foo", []), - c.request("foo", ["bar"]), + c.request({ method: "foo", params: [] }), + c.request({ method: "foo", params: ["bar"] }) ]; c.stopBatch(); const [a, b] = await Promise.all(requests); @@ -105,8 +105,8 @@ describe("client-js", () => { const c = new RequestManager([transport]); c.startBatch(); const requests = [ - c.request("foo", [], true), - c.request("foo", ["bar"], true), + c.request({ method: "foo", params: [] }, true), + c.request({ method: "foo", params: ["bar"] }, true), ]; c.stopBatch(); const [a, b] = await Promise.all(requests); @@ -131,10 +131,10 @@ describe("client-js", () => { const c = new RequestManager([transport]); await c.connect(); c.startBatch(); - c.request("foo", []); + c.request({ method: "foo", params: [] }); expect(c.batch.length).toBe(1); c.startBatch(); - c.request("foo", []); + c.request({ method: "foo", params: [] }); expect(c.batch.length).toBe(2); }); }); diff --git a/src/RequestManager.ts b/src/RequestManager.ts index 8afcfe2..06d8b9b 100644 --- a/src/RequestManager.ts +++ b/src/RequestManager.ts @@ -3,6 +3,7 @@ import { IJSONRPCRequest, IJSONRPCNotification, IBatchRequest } from "./Request" import { JSONRPCError } from "./Error"; import StrictEventEmitter from "strict-event-emitter-types"; import { EventEmitter } from "events"; +import { ProviderRequestArguments, Provider } from "./ProviderInterface"; export type RequestChannel = StrictEventEmitter; @@ -16,7 +17,7 @@ export interface IRequestEvents { * If a transport fails, or times out, move on to the next. */ -class RequestManager { +class RequestManager implements Provider { public transports: Transport[]; public connectPromise: Promise; public batch: IBatchRequest[] = []; @@ -43,11 +44,11 @@ class RequestManager { return this.transports[0]; } - public async request(method: string, params: any[], notification: boolean = false, timeout?: number): Promise { + public async request(args: ProviderRequestArguments, notification: boolean = false, timeout?: number): Promise { const internalID = (++this.lastId).toString(); const id = notification ? null : internalID; // naively grab first transport and use it - const payload = {request: this.makeRequest(method, params, id) , internalID}; + const payload = {request: this.makeRequest(args.method, args.params || [], id) , internalID}; if (this.batchStarted) { const result = new Promise((resolve, reject) => { this.batch.push({ resolve, reject, request: payload }); diff --git a/src/example.ts b/src/example.ts index b0d8d5e..948a6dc 100644 --- a/src/example.ts +++ b/src/example.ts @@ -2,6 +2,6 @@ import { Client, RequestManager, HTTPTransport } from "."; const t = new HTTPTransport("http://localhost:3333"); const c = new Client(new RequestManager([t])); -c.request("addition", [2, 2]).then((result: any) => { +c.request({method: "addition", params: [2, 2]}).then((result: any) => { console.log('addition result: ', result); // tslint:disable-line }); diff --git a/src/index.test.ts b/src/index.test.ts index 5571dbe..1f12dc4 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -14,14 +14,14 @@ describe("client-js", () => { const emitter = new EventEmitter(); const c = new Client(new RequestManager([new EventEmitterTransport(emitter, "from1", "to1")])); expect(typeof c.request).toEqual("function"); - expect(typeof c.request("my_method", null).then).toEqual("function"); + expect(typeof c.request({ method: "my_method"}).then).toEqual("function"); }); it("has a notify method that returns a promise", () => { const emitter = new EventEmitter(); const c = new Client(new RequestManager([new EventEmitterTransport(emitter, "from1", "to1")])); expect(typeof c.request).toEqual("function"); - expect(typeof c.notify("my_method", null).then).toEqual("function"); + expect(typeof c.notify({method: "my_method"}).then).toEqual("function"); }); it("can register error and subscription handlers", () => { diff --git a/src/index.ts b/src/index.ts index afa7c33..9f17b8e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,10 +5,7 @@ import WebSocketTransport from "./transports/WebSocketTransport"; import PostMessageWindowTransport from "./transports/PostMessageWindowTransport"; import PostMessageIframeTransport from "./transports/PostMessageIframeTransport"; import { JSONRPCError } from "./Error"; - -interface IClient { - request(method: string, params: any): Promise; -} +import { Provider, ProviderRequestArguments } from "./ProviderInterface"; /** * OpenRPC Client JS is a browser-compatible JSON-RPC client with multiple transports and @@ -19,12 +16,12 @@ interface IClient { * import { RequestManager, HTTPTransport, Client } from '@open-rpc/client-js'; * const transport = new HTTPTransport('http://localhost:3333'); * const client = new Client(new RequestManager([transport])); - * const result = await client.request(‘addition’, [2, 2]); + * const result = await client.request({method: 'addition', params: [2, 2]}); * // => { jsonrpc: '2.0', id: 1, result: 4 } * ``` * */ -class Client implements IClient { +class Client implements Provider { public requestManager: RequestManager; constructor(requestManager: RequestManager) { this.requestManager = requestManager; @@ -39,8 +36,8 @@ class Client implements IClient { * * @example * myClient.startBatch(); - * myClient.request("foo", ["bar"]).then(() => console.log('foobar')); - * myClient.request("foo", ["baz"]).then(() => console.log('foobaz')); + * myClient.request({method: "foo", params: ["bar"]}).then(() => console.log('foobar')); + * myClient.request({method: "foo", params: ["baz"]}).then(() => console.log('foobaz')); * myClient.stopBatch(); */ public startBatch(): void { @@ -55,8 +52,8 @@ class Client implements IClient { * * @example * myClient.startBatch(); - * myClient.request("foo", ["bar"]).then(() => console.log('foobar')); - * myClient.request("foo", ["baz"]).then(() => console.log('foobaz')); + * myClient.request({method: "foo", params: ["bar"]}).then(() => console.log('foobar')); + * myClient.request({method: "foo", params: ["baz"]}).then(() => console.log('foobaz')); * myClient.stopBatch(); */ public stopBatch(): void { @@ -66,19 +63,22 @@ class Client implements IClient { /** * A JSON-RPC call is represented by sending a Request object to a Server. * - * @param method A String containing the name of the method to be invoked. Method names that begin with the word rpc + * @param requestObject.method A String containing the name of the method to be invoked. Method names that begin with the word rpc * followed by a period character (U+002E or ASCII 46) are reserved for rpc-internal methods and extensions and * MUST NOT be used for anything else. - * @param params A Structured value that holds the parameter values to be used during the invocation of the method. + * @param requestObject.params A Structured value that holds the parameter values to be used during the invocation of the method. + * + * @example + * myClient.request({method: "foo", params: ["bar"]}).then(() => console.log('foobar')); */ - public async request(method: string, params: any, timeout?: number) { + public async request(requestObject: ProviderRequestArguments, timeout?: number) { await this.requestManager.connectPromise; - return this.requestManager.request(method, params, false, timeout); + return this.requestManager.request(requestObject, false, timeout); } - public async notify(method: string, params: any) { + public async notify(requestObject: ProviderRequestArguments) { await this.requestManager.connectPromise; - return this.requestManager.request(method, params, true); + return this.requestManager.request(requestObject, true); } public onNotification(callback: (data: any) => void) {