mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 13:47:12 +00:00
json-rpc: Fork from @iov/jsonrpc
This commit is contained in:
parent
186cac31fa
commit
a9a12d5fbc
1
packages/json-rpc/.eslintignore
Symbolic link
1
packages/json-rpc/.eslintignore
Symbolic link
@ -0,0 +1 @@
|
||||
../../.eslintignore
|
3
packages/json-rpc/.gitignore
vendored
Normal file
3
packages/json-rpc/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
build/
|
||||
dist/
|
||||
docs/
|
12
packages/json-rpc/README.md
Normal file
12
packages/json-rpc/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# @iov/jsonrpc
|
||||
|
||||
[](https://www.npmjs.com/package/@iov/jsonrpc)
|
||||
|
||||
This package provides a light framework for implementing a [JSON-RPC 2.0 API](https://www.jsonrpc.org/specification).
|
||||
|
||||
## License
|
||||
|
||||
This package is part of the IOV-Core repository, licensed under the Apache
|
||||
License 2.0 (see
|
||||
[NOTICE](https://github.com/iov-one/iov-core/blob/master/NOTICE) and
|
||||
[LICENSE](https://github.com/iov-one/iov-core/blob/master/LICENSE)).
|
26
packages/json-rpc/jasmine-testrunner.js
Executable file
26
packages/json-rpc/jasmine-testrunner.js
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
require("source-map-support").install();
|
||||
const defaultSpecReporterConfig = require("../../jasmine-spec-reporter.config.json");
|
||||
|
||||
// setup Jasmine
|
||||
const Jasmine = require("jasmine");
|
||||
const jasmine = new Jasmine();
|
||||
jasmine.loadConfig({
|
||||
spec_dir: "build",
|
||||
spec_files: ["**/*.spec.js"],
|
||||
helpers: [],
|
||||
random: false,
|
||||
seed: null,
|
||||
stopSpecOnExpectationFailure: false,
|
||||
});
|
||||
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 15 * 1000;
|
||||
|
||||
// setup reporter
|
||||
const { SpecReporter } = require("jasmine-spec-reporter");
|
||||
const reporter = new SpecReporter({ ...defaultSpecReporterConfig });
|
||||
|
||||
// initialize and execute
|
||||
jasmine.env.clearReporters();
|
||||
jasmine.addReporter(reporter);
|
||||
jasmine.execute();
|
56
packages/json-rpc/karma.conf.js
Normal file
56
packages/json-rpc/karma.conf.js
Normal file
@ -0,0 +1,56 @@
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: ".",
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ["jasmine"],
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
"dist/web/tests.js",
|
||||
{
|
||||
pattern: "dist/web/dummyservice.worker.js",
|
||||
included: false,
|
||||
served: true,
|
||||
watched: false,
|
||||
nocache: true,
|
||||
},
|
||||
],
|
||||
|
||||
client: {
|
||||
jasmine: {
|
||||
random: false,
|
||||
timeoutInterval: 15000,
|
||||
},
|
||||
},
|
||||
|
||||
// test results reporter to use
|
||||
// possible values: 'dots', 'progress'
|
||||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
|
||||
reporters: ["progress", "kjhtml"],
|
||||
|
||||
// web server port
|
||||
port: 9876,
|
||||
|
||||
// enable / disable colors in the output (reporters and logs)
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
autoWatch: false,
|
||||
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ["Firefox"],
|
||||
|
||||
browserNoActivityTimeout: 30000,
|
||||
|
||||
// Keep brower open for debugging. This is overridden by yarn scripts
|
||||
singleRun: false,
|
||||
});
|
||||
};
|
0
packages/json-rpc/nonces/1552895838
Normal file
0
packages/json-rpc/nonces/1552895838
Normal file
0
packages/json-rpc/nonces/1553085893
Normal file
0
packages/json-rpc/nonces/1553085893
Normal file
0
packages/json-rpc/nonces/1554721221
Normal file
0
packages/json-rpc/nonces/1554721221
Normal file
0
packages/json-rpc/nonces/1554724217
Normal file
0
packages/json-rpc/nonces/1554724217
Normal file
0
packages/json-rpc/nonces/1554737147
Normal file
0
packages/json-rpc/nonces/1554737147
Normal file
0
packages/json-rpc/nonces/1555314694
Normal file
0
packages/json-rpc/nonces/1555314694
Normal file
0
packages/json-rpc/nonces/1556008552
Normal file
0
packages/json-rpc/nonces/1556008552
Normal file
0
packages/json-rpc/nonces/1556028926
Normal file
0
packages/json-rpc/nonces/1556028926
Normal file
0
packages/json-rpc/nonces/1556095341
Normal file
0
packages/json-rpc/nonces/1556095341
Normal file
0
packages/json-rpc/nonces/1556616100
Normal file
0
packages/json-rpc/nonces/1556616100
Normal file
0
packages/json-rpc/nonces/1557811966
Normal file
0
packages/json-rpc/nonces/1557811966
Normal file
0
packages/json-rpc/nonces/1558346811
Normal file
0
packages/json-rpc/nonces/1558346811
Normal file
0
packages/json-rpc/nonces/1558456815
Normal file
0
packages/json-rpc/nonces/1558456815
Normal file
0
packages/json-rpc/nonces/1558460837
Normal file
0
packages/json-rpc/nonces/1558460837
Normal file
0
packages/json-rpc/nonces/1559802671
Normal file
0
packages/json-rpc/nonces/1559802671
Normal file
0
packages/json-rpc/nonces/1561970534
Normal file
0
packages/json-rpc/nonces/1561970534
Normal file
0
packages/json-rpc/nonces/1562080432
Normal file
0
packages/json-rpc/nonces/1562080432
Normal file
0
packages/json-rpc/nonces/1563468776
Normal file
0
packages/json-rpc/nonces/1563468776
Normal file
0
packages/json-rpc/nonces/1563887488
Normal file
0
packages/json-rpc/nonces/1563887488
Normal file
0
packages/json-rpc/nonces/1563960408
Normal file
0
packages/json-rpc/nonces/1563960408
Normal file
0
packages/json-rpc/nonces/1563981076
Normal file
0
packages/json-rpc/nonces/1563981076
Normal file
0
packages/json-rpc/nonces/1564503008
Normal file
0
packages/json-rpc/nonces/1564503008
Normal file
0
packages/json-rpc/nonces/1564651088
Normal file
0
packages/json-rpc/nonces/1564651088
Normal file
0
packages/json-rpc/nonces/1565101189
Normal file
0
packages/json-rpc/nonces/1565101189
Normal file
0
packages/json-rpc/nonces/1565595547
Normal file
0
packages/json-rpc/nonces/1565595547
Normal file
0
packages/json-rpc/nonces/1565876849
Normal file
0
packages/json-rpc/nonces/1565876849
Normal file
0
packages/json-rpc/nonces/1566487600
Normal file
0
packages/json-rpc/nonces/1566487600
Normal file
0
packages/json-rpc/nonces/1567435567
Normal file
0
packages/json-rpc/nonces/1567435567
Normal file
0
packages/json-rpc/nonces/1567608963
Normal file
0
packages/json-rpc/nonces/1567608963
Normal file
0
packages/json-rpc/nonces/1567694160
Normal file
0
packages/json-rpc/nonces/1567694160
Normal file
0
packages/json-rpc/nonces/1568039925
Normal file
0
packages/json-rpc/nonces/1568039925
Normal file
0
packages/json-rpc/nonces/1568116477
Normal file
0
packages/json-rpc/nonces/1568116477
Normal file
0
packages/json-rpc/nonces/1568786866
Normal file
0
packages/json-rpc/nonces/1568786866
Normal file
0
packages/json-rpc/nonces/1568910632
Normal file
0
packages/json-rpc/nonces/1568910632
Normal file
0
packages/json-rpc/nonces/1569319493
Normal file
0
packages/json-rpc/nonces/1569319493
Normal file
0
packages/json-rpc/nonces/1569487848
Normal file
0
packages/json-rpc/nonces/1569487848
Normal file
0
packages/json-rpc/nonces/1569929617
Normal file
0
packages/json-rpc/nonces/1569929617
Normal file
0
packages/json-rpc/nonces/1570527883
Normal file
0
packages/json-rpc/nonces/1570527883
Normal file
0
packages/json-rpc/nonces/1573026590
Normal file
0
packages/json-rpc/nonces/1573026590
Normal file
0
packages/json-rpc/nonces/1574869843
Normal file
0
packages/json-rpc/nonces/1574869843
Normal file
0
packages/json-rpc/nonces/1576569788
Normal file
0
packages/json-rpc/nonces/1576569788
Normal file
0
packages/json-rpc/nonces/1576595306
Normal file
0
packages/json-rpc/nonces/1576595306
Normal file
0
packages/json-rpc/nonces/1576678551
Normal file
0
packages/json-rpc/nonces/1576678551
Normal file
0
packages/json-rpc/nonces/1576746493
Normal file
0
packages/json-rpc/nonces/1576746493
Normal file
0
packages/json-rpc/nonces/1576760285
Normal file
0
packages/json-rpc/nonces/1576760285
Normal file
0
packages/json-rpc/nonces/1576767119
Normal file
0
packages/json-rpc/nonces/1576767119
Normal file
0
packages/json-rpc/nonces/1579019908
Normal file
0
packages/json-rpc/nonces/1579019908
Normal file
0
packages/json-rpc/nonces/1581606289
Normal file
0
packages/json-rpc/nonces/1581606289
Normal file
0
packages/json-rpc/nonces/1581681020
Normal file
0
packages/json-rpc/nonces/1581681020
Normal file
0
packages/json-rpc/nonces/1584038020
Normal file
0
packages/json-rpc/nonces/1584038020
Normal file
0
packages/json-rpc/nonces/1588011428
Normal file
0
packages/json-rpc/nonces/1588011428
Normal file
0
packages/json-rpc/nonces/1591293896
Normal file
0
packages/json-rpc/nonces/1591293896
Normal file
1
packages/json-rpc/nonces/README.txt
Normal file
1
packages/json-rpc/nonces/README.txt
Normal file
@ -0,0 +1 @@
|
||||
Directory used to trigger lerna package updates for all packages
|
44
packages/json-rpc/package.json
Normal file
44
packages/json-rpc/package.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "@iov/jsonrpc",
|
||||
"version": "2.5.0",
|
||||
"description": "Framework for implementing a JSON-RPC 2.0 API",
|
||||
"author": "IOV SAS <admin@iov.one>",
|
||||
"license": "Apache-2.0",
|
||||
"main": "build/index.js",
|
||||
"types": "types/index.d.ts",
|
||||
"files": [
|
||||
"build/",
|
||||
"types/",
|
||||
"*.md",
|
||||
"!*.spec.*",
|
||||
"!**/testdata/"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/iov-one/iov-core/tree/master/packages/iov-jsonrpc"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"docs": "shx rm -rf docs && typedoc --options typedoc.js",
|
||||
"lint": "eslint --max-warnings 0 \"**/*.{js,ts}\" && tslint -t verbose --project .",
|
||||
"format": "prettier --write --loglevel warn \"./src/**/*.ts\"",
|
||||
"test-node": "node jasmine-testrunner.js",
|
||||
"test-edge": "yarn pack-web && karma start --single-run --browsers Edge",
|
||||
"test-firefox": "yarn pack-web && karma start --single-run --browsers Firefox",
|
||||
"test-chrome": "yarn pack-web && karma start --single-run --browsers ChromeHeadless",
|
||||
"test-safari": "yarn pack-web && karma start --single-run --browsers Safari",
|
||||
"test": "yarn build-or-skip && yarn test-node",
|
||||
"move-types": "shx rm -r ./types/* && shx mv build/types/* ./types && rm -rf ./types/testdata && shx rm -f ./types/*.spec.d.ts",
|
||||
"format-types": "prettier --write --loglevel warn \"./types/**/*.d.ts\"",
|
||||
"build": "shx rm -rf ./build && tsc && tsc -p tsconfig.workers.json && yarn move-types && yarn format-types",
|
||||
"build-or-skip": "[ -n \"$SKIP_BUILD\" ] || yarn build",
|
||||
"pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iov/encoding": "^2.5.0",
|
||||
"@iov/stream": "^2.3.2",
|
||||
"xstream": "^11.10.0"
|
||||
}
|
||||
}
|
21
packages/json-rpc/src/id.spec.ts
Normal file
21
packages/json-rpc/src/id.spec.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { makeJsonRpcId } from "./id";
|
||||
|
||||
describe("id", () => {
|
||||
describe("makeJsonRpcId", () => {
|
||||
it("returns a string or number", () => {
|
||||
const id = makeJsonRpcId();
|
||||
expect(["string", "number"]).toContain(typeof id);
|
||||
});
|
||||
|
||||
it("returns unique values", () => {
|
||||
const ids = new Set([
|
||||
makeJsonRpcId(),
|
||||
makeJsonRpcId(),
|
||||
makeJsonRpcId(),
|
||||
makeJsonRpcId(),
|
||||
makeJsonRpcId(),
|
||||
]);
|
||||
expect(ids.size).toEqual(5);
|
||||
});
|
||||
});
|
||||
});
|
13
packages/json-rpc/src/id.ts
Normal file
13
packages/json-rpc/src/id.ts
Normal file
@ -0,0 +1,13 @@
|
||||
// Start with 10001 to avoid possible collisions with all hand-selected values like e.g. 1,2,3,42,100
|
||||
let counter = 10000;
|
||||
|
||||
/**
|
||||
* Creates a new ID to be used for creating a JSON-RPC request.
|
||||
*
|
||||
* Multiple calls of this produce unique values.
|
||||
*
|
||||
* The output may be any value compatible to JSON-RPC request IDs with an undefined output format and generation logic.
|
||||
*/
|
||||
export function makeJsonRpcId(): number {
|
||||
return (counter += 1);
|
||||
}
|
20
packages/json-rpc/src/index.ts
Normal file
20
packages/json-rpc/src/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export { makeJsonRpcId } from "./id";
|
||||
export { JsonRpcClient, SimpleMessagingConnection } from "./jsonrpcclient";
|
||||
export {
|
||||
parseJsonRpcId,
|
||||
parseJsonRpcRequest,
|
||||
parseJsonRpcResponse,
|
||||
parseJsonRpcErrorResponse,
|
||||
parseJsonRpcSuccessResponse,
|
||||
} from "./parse";
|
||||
export {
|
||||
isJsonRpcErrorResponse,
|
||||
isJsonRpcSuccessResponse,
|
||||
JsonRpcError,
|
||||
JsonRpcErrorResponse,
|
||||
JsonRpcId,
|
||||
JsonRpcRequest,
|
||||
JsonRpcResponse,
|
||||
JsonRpcSuccessResponse,
|
||||
jsonRpcCode,
|
||||
} from "./types";
|
88
packages/json-rpc/src/jsonrpcclient.spec.ts
Normal file
88
packages/json-rpc/src/jsonrpcclient.spec.ts
Normal file
@ -0,0 +1,88 @@
|
||||
/// <reference lib="dom" />
|
||||
|
||||
import { Producer, Stream } from "xstream";
|
||||
|
||||
import { JsonRpcClient, SimpleMessagingConnection } from "./jsonrpcclient";
|
||||
import { parseJsonRpcResponse } from "./parse";
|
||||
import { JsonRpcRequest, JsonRpcResponse } from "./types";
|
||||
|
||||
function pendingWithoutWorker(): void {
|
||||
if (typeof Worker === "undefined") {
|
||||
pending("Environment without WebWorker support detected. Marked as pending.");
|
||||
}
|
||||
}
|
||||
|
||||
function makeSimpleMessagingConnection(
|
||||
worker: Worker,
|
||||
): SimpleMessagingConnection<JsonRpcRequest, JsonRpcResponse> {
|
||||
const producer: Producer<JsonRpcResponse> = {
|
||||
start: (listener) => {
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
worker.onmessage = (event) => {
|
||||
listener.next(parseJsonRpcResponse(event.data));
|
||||
};
|
||||
},
|
||||
stop: () => {
|
||||
// tslint:disable-next-line:no-object-mutation
|
||||
worker.onmessage = null;
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
responseStream: Stream.create(producer),
|
||||
sendRequest: (request) => worker.postMessage(request),
|
||||
};
|
||||
}
|
||||
|
||||
describe("JsonRpcClient", () => {
|
||||
const dummyserviceKarmaUrl = "/base/dist/web/dummyservice.worker.js";
|
||||
|
||||
it("can be constructed with a Worker", () => {
|
||||
pendingWithoutWorker();
|
||||
|
||||
const worker = new Worker(dummyserviceKarmaUrl);
|
||||
const client = new JsonRpcClient(makeSimpleMessagingConnection(worker));
|
||||
expect(client).toBeTruthy();
|
||||
worker.terminate();
|
||||
});
|
||||
|
||||
it("can communicate with worker", async () => {
|
||||
pendingWithoutWorker();
|
||||
|
||||
const worker = new Worker(dummyserviceKarmaUrl);
|
||||
|
||||
const client = new JsonRpcClient(makeSimpleMessagingConnection(worker));
|
||||
const response = await client.run({
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
method: "getIdentities",
|
||||
params: ["Who are you?"],
|
||||
});
|
||||
expect(response.jsonrpc).toEqual("2.0");
|
||||
expect(response.id).toEqual(123);
|
||||
expect(response.result).toEqual(`Called getIdentities("Who are you?")`);
|
||||
|
||||
worker.terminate();
|
||||
});
|
||||
|
||||
it("supports params as dictionary", async () => {
|
||||
pendingWithoutWorker();
|
||||
|
||||
const worker = new Worker(dummyserviceKarmaUrl);
|
||||
|
||||
const client = new JsonRpcClient(makeSimpleMessagingConnection(worker));
|
||||
const response = await client.run({
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
method: "getIdentities",
|
||||
params: {
|
||||
a: "Who are you?",
|
||||
},
|
||||
});
|
||||
expect(response.jsonrpc).toEqual("2.0");
|
||||
expect(response.id).toEqual(123);
|
||||
expect(response.result).toEqual(`Called getIdentities({"a":"Who are you?"})`);
|
||||
|
||||
worker.terminate();
|
||||
});
|
||||
});
|
37
packages/json-rpc/src/jsonrpcclient.ts
Normal file
37
packages/json-rpc/src/jsonrpcclient.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { firstEvent } from "@iov/stream";
|
||||
import { Stream } from "xstream";
|
||||
|
||||
import { isJsonRpcErrorResponse, JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse } from "./types";
|
||||
|
||||
export interface SimpleMessagingConnection<Request, Response> {
|
||||
readonly responseStream: Stream<Response>;
|
||||
readonly sendRequest: (request: Request) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* A thin wrapper that is used to bring together requests and responses by ID.
|
||||
*
|
||||
* Using this class is only advised for continous communication channels like
|
||||
* WebSockets or WebWorker messaging.
|
||||
*/
|
||||
export class JsonRpcClient {
|
||||
private readonly connection: SimpleMessagingConnection<JsonRpcRequest, JsonRpcResponse>;
|
||||
|
||||
public constructor(connection: SimpleMessagingConnection<JsonRpcRequest, JsonRpcResponse>) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public async run(request: JsonRpcRequest): Promise<JsonRpcSuccessResponse> {
|
||||
const filteredStream = this.connection.responseStream.filter((r) => r.id === request.id);
|
||||
const pendingResponses = firstEvent(filteredStream);
|
||||
this.connection.sendRequest(request);
|
||||
|
||||
const response = await pendingResponses;
|
||||
if (isJsonRpcErrorResponse(response)) {
|
||||
const error = response.error;
|
||||
throw new Error(`JSON RPC error: code=${error.code}; message='${error.message}'`);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
429
packages/json-rpc/src/parse.spec.ts
Normal file
429
packages/json-rpc/src/parse.spec.ts
Normal file
@ -0,0 +1,429 @@
|
||||
import {
|
||||
parseJsonRpcErrorResponse,
|
||||
parseJsonRpcId,
|
||||
parseJsonRpcResponse,
|
||||
parseJsonRpcSuccessResponse,
|
||||
} from "./parse";
|
||||
import { jsonRpcCode, JsonRpcErrorResponse, JsonRpcRequest, JsonRpcSuccessResponse } from "./types";
|
||||
|
||||
describe("parse", () => {
|
||||
describe("parseJsonRpcId", () => {
|
||||
it("works for number IDs", () => {
|
||||
const request: JsonRpcRequest = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
method: "foo",
|
||||
params: {},
|
||||
};
|
||||
expect(parseJsonRpcId(request)).toEqual(123);
|
||||
});
|
||||
|
||||
it("works for string IDs", () => {
|
||||
const request: JsonRpcRequest = {
|
||||
jsonrpc: "2.0",
|
||||
id: "329fg3b",
|
||||
method: "foo",
|
||||
params: {},
|
||||
};
|
||||
expect(parseJsonRpcId(request)).toEqual("329fg3b");
|
||||
});
|
||||
|
||||
it("returns null for invaid IDs", () => {
|
||||
// unset
|
||||
{
|
||||
const request = {
|
||||
jsonrpc: "2.0",
|
||||
method: "foo",
|
||||
params: {},
|
||||
};
|
||||
expect(parseJsonRpcId(request)).toBeNull();
|
||||
}
|
||||
// wrong type (object)
|
||||
{
|
||||
const request = {
|
||||
jsonrpc: "2.0",
|
||||
id: { content: 123 },
|
||||
method: "foo",
|
||||
params: {},
|
||||
};
|
||||
expect(parseJsonRpcId(request)).toBeNull();
|
||||
}
|
||||
// wrong type (Array)
|
||||
{
|
||||
const request = {
|
||||
jsonrpc: "2.0",
|
||||
id: [1, 2, 3],
|
||||
method: "foo",
|
||||
params: {},
|
||||
};
|
||||
expect(parseJsonRpcId(request)).toBeNull();
|
||||
}
|
||||
// wrong type (null)
|
||||
{
|
||||
const request = {
|
||||
jsonrpc: "2.0",
|
||||
id: null,
|
||||
method: "foo",
|
||||
params: {},
|
||||
};
|
||||
expect(parseJsonRpcId(request)).toBeNull();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseJsonRpcErrorResponse", () => {
|
||||
it("works for valid error", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
data: [2, 3, 4],
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcErrorResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for error with string ID", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: "a3g4g34g",
|
||||
error: {
|
||||
code: jsonRpcCode.parseError,
|
||||
message: "Could not parse request ID",
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcErrorResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for error with null ID", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: null,
|
||||
error: {
|
||||
code: jsonRpcCode.parseError,
|
||||
message: "Could not parse request ID",
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcErrorResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for error with null data", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
data: null,
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcErrorResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for error with unset data", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcErrorResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("throws for invalid type", () => {
|
||||
const expectedError = /data must be JSON compatible dictionary/i;
|
||||
expect(() => parseJsonRpcErrorResponse(undefined)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse(null)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse(false)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse("error")).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse(42)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse(() => true)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse({ foo: () => true })).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcErrorResponse({ foo: () => new Uint8Array([]) })).toThrowError(expectedError);
|
||||
});
|
||||
|
||||
it("throws for invalid version", () => {
|
||||
// wrong type
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: 2.0,
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(() => parseJsonRpcErrorResponse(response)).toThrowError(/got unexpected jsonrpc version/i);
|
||||
}
|
||||
// wrong version
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "1.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(() => parseJsonRpcErrorResponse(response)).toThrowError(/got unexpected jsonrpc version/i);
|
||||
}
|
||||
// unset
|
||||
{
|
||||
const response: any = {
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(() => parseJsonRpcErrorResponse(response)).toThrowError(/got unexpected jsonrpc version/i);
|
||||
}
|
||||
});
|
||||
|
||||
it("throws for invalid ID", () => {
|
||||
// wrong type
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: [1, 2, 3],
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(() => parseJsonRpcErrorResponse(response)).toThrowError(/invalid id field/i);
|
||||
}
|
||||
// unset
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(() => parseJsonRpcErrorResponse(response)).toThrowError(/invalid id field/i);
|
||||
}
|
||||
});
|
||||
|
||||
it("throws for success response", () => {
|
||||
const response: JsonRpcSuccessResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcErrorResponse(response)).toThrowError(/invalid error field/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseJsonRpcSuccessResponse", () => {
|
||||
it("works for response with dict result", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
result: {
|
||||
foo: "bar",
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcSuccessResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for response with null result", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
result: null,
|
||||
};
|
||||
expect(parseJsonRpcSuccessResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for response with number ID", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
result: {},
|
||||
};
|
||||
expect(parseJsonRpcSuccessResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for response with string ID", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: "40gfh408g",
|
||||
result: {},
|
||||
};
|
||||
expect(parseJsonRpcSuccessResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("throws for invalid type", () => {
|
||||
const expectedError = /data must be JSON compatible dictionary/i;
|
||||
expect(() => parseJsonRpcSuccessResponse(undefined)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcSuccessResponse(null)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcSuccessResponse(false)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcSuccessResponse("success")).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcSuccessResponse(42)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcSuccessResponse(() => true)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcSuccessResponse({ foo: () => true })).toThrowError(expectedError);
|
||||
});
|
||||
|
||||
it("throws for invalid version", () => {
|
||||
// wrong type
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: 2.0,
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/got unexpected jsonrpc version/i);
|
||||
}
|
||||
// wrong version
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "1.0",
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/got unexpected jsonrpc version/i);
|
||||
}
|
||||
// unset
|
||||
{
|
||||
const response: any = {
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/got unexpected jsonrpc version/i);
|
||||
}
|
||||
});
|
||||
|
||||
it("throws for invalid ID", () => {
|
||||
// wrong type
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: [1, 2, 3],
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/invalid id field/i);
|
||||
}
|
||||
// wrong type
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: null,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/invalid id field/i);
|
||||
}
|
||||
// unset
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/invalid id field/i);
|
||||
}
|
||||
});
|
||||
|
||||
it("throws for error response", () => {
|
||||
const response: JsonRpcErrorResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.parseError,
|
||||
message: "Could not parse request ID",
|
||||
},
|
||||
};
|
||||
expect(() => parseJsonRpcSuccessResponse(response)).toThrowError(/invalid result field/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe("parseJsonRpcResponse", () => {
|
||||
it("works for success response", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(parseJsonRpcResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("works for error response", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
data: [2, 3, 4],
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcResponse(response)).toEqual(response);
|
||||
});
|
||||
|
||||
it("favours error if response is error and success at the same time", () => {
|
||||
const response: any = {
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
result: 3000,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
};
|
||||
expect(parseJsonRpcResponse(response)).toEqual({
|
||||
jsonrpc: "2.0",
|
||||
id: 123,
|
||||
error: {
|
||||
code: jsonRpcCode.serverError.default,
|
||||
message: "Something bad happened",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("throws for invalid type", () => {
|
||||
const expectedError = /data must be JSON compatible dictionary/i;
|
||||
expect(() => parseJsonRpcResponse(undefined)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse(null)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse(false)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse("error")).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse(42)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse(() => true)).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse({ foo: () => true })).toThrowError(expectedError);
|
||||
expect(() => parseJsonRpcResponse({ foo: () => new Uint8Array([]) })).toThrowError(expectedError);
|
||||
});
|
||||
|
||||
it("throws for invalid version", () => {
|
||||
const expectedError = /got unexpected jsonrpc version/i;
|
||||
// wrong type
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: 2.0,
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcResponse(response)).toThrowError(expectedError);
|
||||
}
|
||||
// wrong version
|
||||
{
|
||||
const response: any = {
|
||||
jsonrpc: "1.0",
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcResponse(response)).toThrowError(expectedError);
|
||||
}
|
||||
// unset
|
||||
{
|
||||
const response: any = {
|
||||
id: 123,
|
||||
result: 3000,
|
||||
};
|
||||
expect(() => parseJsonRpcResponse(response)).toThrowError(expectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
158
packages/json-rpc/src/parse.ts
Normal file
158
packages/json-rpc/src/parse.ts
Normal file
@ -0,0 +1,158 @@
|
||||
import {
|
||||
isJsonCompatibleArray,
|
||||
isJsonCompatibleDictionary,
|
||||
isJsonCompatibleValue,
|
||||
JsonCompatibleDictionary,
|
||||
JsonCompatibleValue,
|
||||
} from "@iov/encoding";
|
||||
|
||||
import {
|
||||
JsonRpcError,
|
||||
JsonRpcErrorResponse,
|
||||
JsonRpcId,
|
||||
JsonRpcRequest,
|
||||
JsonRpcResponse,
|
||||
JsonRpcSuccessResponse,
|
||||
} from "./types";
|
||||
|
||||
/**
|
||||
* Extracts ID field from request or response object.
|
||||
*
|
||||
* Returns `null` when no valid ID was found.
|
||||
*/
|
||||
export function parseJsonRpcId(data: unknown): JsonRpcId | null {
|
||||
if (!isJsonCompatibleDictionary(data)) {
|
||||
throw new Error("Data must be JSON compatible dictionary");
|
||||
}
|
||||
|
||||
const id = data.id;
|
||||
if (typeof id !== "number" && typeof id !== "string") {
|
||||
return null;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function parseJsonRpcRequest(data: unknown): JsonRpcRequest {
|
||||
if (!isJsonCompatibleDictionary(data)) {
|
||||
throw new Error("Data must be JSON compatible dictionary");
|
||||
}
|
||||
|
||||
if (data.jsonrpc !== "2.0") {
|
||||
throw new Error(`Got unexpected jsonrpc version: ${data.jsonrpc}`);
|
||||
}
|
||||
|
||||
const id = parseJsonRpcId(data);
|
||||
if (id === null) {
|
||||
throw new Error("Invalid id field");
|
||||
}
|
||||
|
||||
const method = data.method;
|
||||
if (typeof method !== "string") {
|
||||
throw new Error("Invalid method field");
|
||||
}
|
||||
|
||||
if (!isJsonCompatibleArray(data.params) && !isJsonCompatibleDictionary(data.params)) {
|
||||
throw new Error("Invalid params field");
|
||||
}
|
||||
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
id: id,
|
||||
method: method,
|
||||
params: data.params,
|
||||
};
|
||||
}
|
||||
|
||||
function parseError(error: JsonCompatibleDictionary): JsonRpcError {
|
||||
if (typeof error.code !== "number") {
|
||||
throw new Error("Error property 'code' is not a number");
|
||||
}
|
||||
|
||||
if (typeof error.message !== "string") {
|
||||
throw new Error("Error property 'message' is not a string");
|
||||
}
|
||||
|
||||
let maybeUndefinedData: JsonCompatibleValue | undefined;
|
||||
|
||||
if (error.data === undefined) {
|
||||
maybeUndefinedData = undefined;
|
||||
} else if (isJsonCompatibleValue(error.data)) {
|
||||
maybeUndefinedData = error.data;
|
||||
} else {
|
||||
throw new Error("Error property 'data' is defined but not a JSON compatible value.");
|
||||
}
|
||||
|
||||
return {
|
||||
code: error.code,
|
||||
message: error.message,
|
||||
...(maybeUndefinedData !== undefined ? { data: maybeUndefinedData } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
/** Throws if data is not a JsonRpcErrorResponse */
|
||||
export function parseJsonRpcErrorResponse(data: unknown): JsonRpcErrorResponse {
|
||||
if (!isJsonCompatibleDictionary(data)) {
|
||||
throw new Error("Data must be JSON compatible dictionary");
|
||||
}
|
||||
|
||||
if (data.jsonrpc !== "2.0") {
|
||||
throw new Error(`Got unexpected jsonrpc version: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
const id = data.id;
|
||||
if (typeof id !== "number" && typeof id !== "string" && id !== null) {
|
||||
throw new Error("Invalid id field");
|
||||
}
|
||||
|
||||
if (typeof data.error === "undefined" || !isJsonCompatibleDictionary(data.error)) {
|
||||
throw new Error("Invalid error field");
|
||||
}
|
||||
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
id: id,
|
||||
error: parseError(data.error),
|
||||
};
|
||||
}
|
||||
|
||||
/** Throws if data is not a JsonRpcSuccessResponse */
|
||||
export function parseJsonRpcSuccessResponse(data: unknown): JsonRpcSuccessResponse {
|
||||
if (!isJsonCompatibleDictionary(data)) {
|
||||
throw new Error("Data must be JSON compatible dictionary");
|
||||
}
|
||||
|
||||
if (data.jsonrpc !== "2.0") {
|
||||
throw new Error(`Got unexpected jsonrpc version: ${JSON.stringify(data)}`);
|
||||
}
|
||||
|
||||
const id = data.id;
|
||||
if (typeof id !== "number" && typeof id !== "string") {
|
||||
throw new Error("Invalid id field");
|
||||
}
|
||||
|
||||
if (typeof data.result === "undefined") {
|
||||
throw new Error("Invalid result field");
|
||||
}
|
||||
|
||||
const result = data.result;
|
||||
|
||||
return {
|
||||
jsonrpc: "2.0",
|
||||
id: id,
|
||||
result: result,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JsonRpcErrorResponse if input can be parsed as a JSON-RPC error. Otherwise parses
|
||||
* input as JsonRpcSuccessResponse. Throws if input is neither a valid error nor success response.
|
||||
*/
|
||||
export function parseJsonRpcResponse(data: unknown): JsonRpcResponse {
|
||||
let response: JsonRpcResponse;
|
||||
try {
|
||||
response = parseJsonRpcErrorResponse(data);
|
||||
} catch (_) {
|
||||
response = parseJsonRpcSuccessResponse(data);
|
||||
}
|
||||
return response;
|
||||
}
|
59
packages/json-rpc/src/types.ts
Normal file
59
packages/json-rpc/src/types.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { JsonCompatibleArray, JsonCompatibleDictionary, JsonCompatibleValue } from "@iov/encoding";
|
||||
|
||||
export type JsonRpcId = number | string;
|
||||
|
||||
export interface JsonRpcRequest {
|
||||
readonly jsonrpc: "2.0";
|
||||
readonly id: JsonRpcId;
|
||||
readonly method: string;
|
||||
readonly params: JsonCompatibleArray | JsonCompatibleDictionary;
|
||||
}
|
||||
|
||||
export interface JsonRpcSuccessResponse {
|
||||
readonly jsonrpc: "2.0";
|
||||
readonly id: JsonRpcId;
|
||||
readonly result: any;
|
||||
}
|
||||
|
||||
export interface JsonRpcError {
|
||||
readonly code: number;
|
||||
readonly message: string;
|
||||
readonly data?: JsonCompatibleValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* And error object as described in https://www.jsonrpc.org/specification#error_object
|
||||
*/
|
||||
export interface JsonRpcErrorResponse {
|
||||
readonly jsonrpc: "2.0";
|
||||
readonly id: JsonRpcId | null;
|
||||
readonly error: JsonRpcError;
|
||||
}
|
||||
|
||||
export type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
||||
|
||||
export function isJsonRpcErrorResponse(response: JsonRpcResponse): response is JsonRpcErrorResponse {
|
||||
return typeof (response as JsonRpcErrorResponse).error === "object";
|
||||
}
|
||||
|
||||
export function isJsonRpcSuccessResponse(response: JsonRpcResponse): response is JsonRpcSuccessResponse {
|
||||
return !isJsonRpcErrorResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Error codes as specified in JSON-RPC 2.0
|
||||
*
|
||||
* @see https://www.jsonrpc.org/specification#error_object
|
||||
*/
|
||||
export const jsonRpcCode = {
|
||||
parseError: -32700,
|
||||
invalidRequest: -32600,
|
||||
methodNotFound: -32601,
|
||||
invalidParams: -32602,
|
||||
internalError: -32603,
|
||||
// server error (Reserved for implementation-defined server-errors.):
|
||||
// -32000 to -32099
|
||||
serverError: {
|
||||
default: -32000,
|
||||
},
|
||||
};
|
68
packages/json-rpc/src/workers/dummyservice.worker.ts
Normal file
68
packages/json-rpc/src/workers/dummyservice.worker.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/// <reference lib="webworker" />
|
||||
|
||||
// for testing only
|
||||
|
||||
import { isJsonCompatibleDictionary } from "@iov/encoding";
|
||||
|
||||
import { parseJsonRpcId, parseJsonRpcRequest } from "../parse";
|
||||
import {
|
||||
jsonRpcCode,
|
||||
JsonRpcErrorResponse,
|
||||
JsonRpcRequest,
|
||||
JsonRpcResponse,
|
||||
JsonRpcSuccessResponse,
|
||||
} from "../types";
|
||||
|
||||
function handleRequest(event: MessageEvent): JsonRpcResponse {
|
||||
let request: JsonRpcRequest;
|
||||
try {
|
||||
request = parseJsonRpcRequest(event.data);
|
||||
} catch (error) {
|
||||
const requestId = parseJsonRpcId(event.data);
|
||||
const errorResponse: JsonRpcErrorResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: requestId,
|
||||
error: {
|
||||
code: jsonRpcCode.invalidRequest,
|
||||
message: error.toString(),
|
||||
},
|
||||
};
|
||||
return errorResponse;
|
||||
}
|
||||
|
||||
let paramsString: string;
|
||||
if (isJsonCompatibleDictionary(request.params)) {
|
||||
paramsString = JSON.stringify(request.params);
|
||||
} else {
|
||||
paramsString = request.params
|
||||
.map((p) => {
|
||||
if (typeof p === "number") {
|
||||
return p;
|
||||
} else if (p === null) {
|
||||
return `null`;
|
||||
} else if (typeof p === "string") {
|
||||
return `"${p}"`;
|
||||
} else {
|
||||
return p.toString();
|
||||
}
|
||||
})
|
||||
.join(", ");
|
||||
}
|
||||
|
||||
const response: JsonRpcSuccessResponse = {
|
||||
jsonrpc: "2.0",
|
||||
id: request.id,
|
||||
result: `Called ${request.method}(${paramsString})`,
|
||||
};
|
||||
return response;
|
||||
}
|
||||
|
||||
onmessage = (event) => {
|
||||
// filter out empty {"isTrusted":true} events
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = handleRequest(event);
|
||||
setTimeout(() => postMessage(response), 50);
|
||||
};
|
15
packages/json-rpc/tsconfig.json
Normal file
15
packages/json-rpc/tsconfig.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"src/workers/**/*"
|
||||
]
|
||||
}
|
12
packages/json-rpc/tsconfig.workers.json
Normal file
12
packages/json-rpc/tsconfig.workers.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"outDir": "build",
|
||||
"declarationDir": "build/types",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": [
|
||||
"src/workers/**/*"
|
||||
]
|
||||
}
|
3
packages/json-rpc/tslint.json
Normal file
3
packages/json-rpc/tslint.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "../../tslint.json"
|
||||
}
|
14
packages/json-rpc/typedoc.js
Normal file
14
packages/json-rpc/typedoc.js
Normal file
@ -0,0 +1,14 @@
|
||||
const packageJson = require("./package.json");
|
||||
|
||||
module.exports = {
|
||||
src: ["./src"],
|
||||
out: "docs",
|
||||
exclude: "**/*.spec.ts",
|
||||
target: "es6",
|
||||
name: `${packageJson.name} Documentation`,
|
||||
readme: "README.md",
|
||||
mode: "file",
|
||||
excludeExternals: true,
|
||||
excludeNotExported: true,
|
||||
excludePrivate: true,
|
||||
};
|
8
packages/json-rpc/types/id.d.ts
vendored
Normal file
8
packages/json-rpc/types/id.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Creates a new ID to be used for creating a JSON-RPC request.
|
||||
*
|
||||
* Multiple calls of this produce unique values.
|
||||
*
|
||||
* The output may be any value compatible to JSON-RPC request IDs with an undefined output format and generation logic.
|
||||
*/
|
||||
export declare function makeJsonRpcId(): number;
|
20
packages/json-rpc/types/index.d.ts
vendored
Normal file
20
packages/json-rpc/types/index.d.ts
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
export { makeJsonRpcId } from "./id";
|
||||
export { JsonRpcClient, SimpleMessagingConnection } from "./jsonrpcclient";
|
||||
export {
|
||||
parseJsonRpcId,
|
||||
parseJsonRpcRequest,
|
||||
parseJsonRpcResponse,
|
||||
parseJsonRpcErrorResponse,
|
||||
parseJsonRpcSuccessResponse,
|
||||
} from "./parse";
|
||||
export {
|
||||
isJsonRpcErrorResponse,
|
||||
isJsonRpcSuccessResponse,
|
||||
JsonRpcError,
|
||||
JsonRpcErrorResponse,
|
||||
JsonRpcId,
|
||||
JsonRpcRequest,
|
||||
JsonRpcResponse,
|
||||
JsonRpcSuccessResponse,
|
||||
jsonRpcCode,
|
||||
} from "./types";
|
17
packages/json-rpc/types/jsonrpcclient.d.ts
vendored
Normal file
17
packages/json-rpc/types/jsonrpcclient.d.ts
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
import { Stream } from "xstream";
|
||||
import { JsonRpcRequest, JsonRpcResponse, JsonRpcSuccessResponse } from "./types";
|
||||
export interface SimpleMessagingConnection<Request, Response> {
|
||||
readonly responseStream: Stream<Response>;
|
||||
readonly sendRequest: (request: Request) => void;
|
||||
}
|
||||
/**
|
||||
* A thin wrapper that is used to bring together requests and responses by ID.
|
||||
*
|
||||
* Using this class is only advised for continous communication channels like
|
||||
* WebSockets or WebWorker messaging.
|
||||
*/
|
||||
export declare class JsonRpcClient {
|
||||
private readonly connection;
|
||||
constructor(connection: SimpleMessagingConnection<JsonRpcRequest, JsonRpcResponse>);
|
||||
run(request: JsonRpcRequest): Promise<JsonRpcSuccessResponse>;
|
||||
}
|
23
packages/json-rpc/types/parse.d.ts
vendored
Normal file
23
packages/json-rpc/types/parse.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
import {
|
||||
JsonRpcErrorResponse,
|
||||
JsonRpcId,
|
||||
JsonRpcRequest,
|
||||
JsonRpcResponse,
|
||||
JsonRpcSuccessResponse,
|
||||
} from "./types";
|
||||
/**
|
||||
* Extracts ID field from request or response object.
|
||||
*
|
||||
* Returns `null` when no valid ID was found.
|
||||
*/
|
||||
export declare function parseJsonRpcId(data: unknown): JsonRpcId | null;
|
||||
export declare function parseJsonRpcRequest(data: unknown): JsonRpcRequest;
|
||||
/** Throws if data is not a JsonRpcErrorResponse */
|
||||
export declare function parseJsonRpcErrorResponse(data: unknown): JsonRpcErrorResponse;
|
||||
/** Throws if data is not a JsonRpcSuccessResponse */
|
||||
export declare function parseJsonRpcSuccessResponse(data: unknown): JsonRpcSuccessResponse;
|
||||
/**
|
||||
* Returns a JsonRpcErrorResponse if input can be parsed as a JSON-RPC error. Otherwise parses
|
||||
* input as JsonRpcSuccessResponse. Throws if input is neither a valid error nor success response.
|
||||
*/
|
||||
export declare function parseJsonRpcResponse(data: unknown): JsonRpcResponse;
|
46
packages/json-rpc/types/types.d.ts
vendored
Normal file
46
packages/json-rpc/types/types.d.ts
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
import { JsonCompatibleArray, JsonCompatibleDictionary, JsonCompatibleValue } from "@iov/encoding";
|
||||
export declare type JsonRpcId = number | string;
|
||||
export interface JsonRpcRequest {
|
||||
readonly jsonrpc: "2.0";
|
||||
readonly id: JsonRpcId;
|
||||
readonly method: string;
|
||||
readonly params: JsonCompatibleArray | JsonCompatibleDictionary;
|
||||
}
|
||||
export interface JsonRpcSuccessResponse {
|
||||
readonly jsonrpc: "2.0";
|
||||
readonly id: JsonRpcId;
|
||||
readonly result: any;
|
||||
}
|
||||
export interface JsonRpcError {
|
||||
readonly code: number;
|
||||
readonly message: string;
|
||||
readonly data?: JsonCompatibleValue;
|
||||
}
|
||||
/**
|
||||
* And error object as described in https://www.jsonrpc.org/specification#error_object
|
||||
*/
|
||||
export interface JsonRpcErrorResponse {
|
||||
readonly jsonrpc: "2.0";
|
||||
readonly id: JsonRpcId | null;
|
||||
readonly error: JsonRpcError;
|
||||
}
|
||||
export declare type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
|
||||
export declare function isJsonRpcErrorResponse(response: JsonRpcResponse): response is JsonRpcErrorResponse;
|
||||
export declare function isJsonRpcSuccessResponse(
|
||||
response: JsonRpcResponse,
|
||||
): response is JsonRpcSuccessResponse;
|
||||
/**
|
||||
* Error codes as specified in JSON-RPC 2.0
|
||||
*
|
||||
* @see https://www.jsonrpc.org/specification#error_object
|
||||
*/
|
||||
export declare const jsonRpcCode: {
|
||||
parseError: number;
|
||||
invalidRequest: number;
|
||||
methodNotFound: number;
|
||||
invalidParams: number;
|
||||
internalError: number;
|
||||
serverError: {
|
||||
default: number;
|
||||
};
|
||||
};
|
2
packages/json-rpc/types/workers/dummyservice.worker.d.ts
vendored
Normal file
2
packages/json-rpc/types/workers/dummyservice.worker.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference lib="webworker" />
|
||||
export {};
|
28
packages/json-rpc/webpack.web.config.js
Normal file
28
packages/json-rpc/webpack.web.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
const glob = require("glob");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const target = "web";
|
||||
const distdir = path.join(__dirname, "dist", "web");
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
// bundle for WebWorker tests
|
||||
target: target,
|
||||
entry: "./build/workers/dummyservice.worker.js",
|
||||
output: {
|
||||
path: distdir,
|
||||
filename: "dummyservice.worker.js",
|
||||
},
|
||||
},
|
||||
{
|
||||
// bundle used for Karma tests
|
||||
target: target,
|
||||
entry: glob.sync("./build/**/*.spec.js"),
|
||||
output: {
|
||||
path: distdir,
|
||||
filename: "tests.js",
|
||||
},
|
||||
plugins: [new webpack.EnvironmentPlugin([])],
|
||||
},
|
||||
];
|
Loading…
x
Reference in New Issue
Block a user