JSON-RPC stdio server

It speaks JSON-RPC serialized into JSON Lines over stdio.
This commit is contained in:
link2xt
2022-10-31 21:41:24 +00:00
parent d29b0baa25
commit 75ed4fe398
11 changed files with 174 additions and 32 deletions

View File

@@ -41,7 +41,7 @@
"prettier:check": "prettier --check **.ts",
"prettier:fix": "prettier --write **.ts",
"test": "run-s test:prepare test:run-coverage test:report-coverage",
"test:prepare": "cargo build --features webserver --bin deltachat-jsonrpc-server",
"test:prepare": "cargo build --package deltachat-rpc-server --bin deltachat-rpc-server",
"test:report-coverage": "node report_api_coverage.mjs",
"test:run": "mocha dist/test",
"test:run-coverage": "COVERAGE=1 NODE_OPTIONS=--enable-source-maps c8 --include 'dist/*' -r text -r html -r json mocha dist/test"

View File

@@ -92,3 +92,34 @@ export class DeltaChat extends BaseDeltaChat<WebsocketTransport> {
this.opts = opts;
}
}
export class StdioDeltaChat extends BaseDeltaChat<StdioTransport> {
close() {}
constructor(input: any, output: any) {
const transport = new StdioTransport(input, output);
super(transport);
}
}
export class StdioTransport extends BaseTransport {
constructor(public input: any, public output: any) {
super();
var buffer = "";
this.output.on("data", (data: any) => {
buffer += data.toString();
while (buffer.includes("\n")) {
const n = buffer.indexOf("\n");
const line = buffer.substring(0, n);
const message = JSON.parse(line);
this._onmessage(message);
buffer = buffer.substring(n + 1);
}
});
}
_send(message: RPC.Message): void {
const serialized = JSON.stringify(message);
this.input.write(serialized + "\n");
}
}

View File

@@ -2,7 +2,7 @@ import { strictEqual } from "assert";
import chai, { assert, expect } from "chai";
import chaiAsPromised from "chai-as-promised";
chai.use(chaiAsPromised);
import { DeltaChat } from "../deltachat.js";
import { StdioDeltaChat as DeltaChat } from "../deltachat.js";
import {
RpcServerHandle,
@@ -15,9 +15,7 @@ describe("basic tests", () => {
before(async () => {
serverHandle = await startServer();
// make sure server is up by the time we continue
await new Promise((res) => setTimeout(res, 100));
dc = new DeltaChat(serverHandle.url)
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout)
// dc.on("ALL", (event) => {
//console.log("event", event);
// });

View File

@@ -1,5 +1,5 @@
import { assert, expect } from "chai";
import { DeltaChat, DcEvent } from "../deltachat.js";
import { StdioDeltaChat as DeltaChat, DcEvent } from "../deltachat.js";
import { RpcServerHandle, createTempUser, startServer } from "./test_base.js";
const EVENT_TIMEOUT = 20000;
@@ -27,7 +27,7 @@ describe("online tests", function () {
this.skip();
}
serverHandle = await startServer();
dc = new DeltaChat(serverHandle.url);
dc = new DeltaChat(serverHandle.stdin, serverHandle.stdout);
dc.on("ALL", (contextId, { type }) => {
if (type !== "Info") console.log(contextId, type);

View File

@@ -1,39 +1,38 @@
import { tmpdir } from "os";
import { join, resolve } from "path";
import { mkdtemp, rm } from "fs/promises";
import { existsSync } from "fs";
import { spawn, exec } from "child_process";
import fetch from "node-fetch";
export const RPC_SERVER_PORT = 20808;
import { Readable, Writable } from "node:stream";
export type RpcServerHandle = {
url: string,
close: () => Promise<void>
}
stdin: Writable;
stdout: Readable;
close: () => Promise<void>;
};
export async function startServer(port: number = RPC_SERVER_PORT): Promise<RpcServerHandle> {
export async function startServer(): Promise<RpcServerHandle> {
const tmpDir = await mkdtemp(join(tmpdir(), "deltachat-jsonrpc-test"));
const pathToServerBinary = resolve(join(await getTargetDir(), "debug/deltachat-jsonrpc-server"));
console.log('using server binary: ' + pathToServerBinary);
if (!existsSync(pathToServerBinary)) {
throw new Error(
"server executable does not exist, you need to build it first" +
"\nserver executable not found at " +
pathToServerBinary
);
}
const pathToServerBinary = resolve(
join(await getTargetDir(), "debug/deltachat-rpc-server")
);
const server = spawn(pathToServerBinary, {
cwd: tmpDir,
env: {
RUST_LOG: process.env.RUST_LOG || "info",
DC_PORT: '' + port,
RUST_MIN_STACK: "8388608"
RUST_MIN_STACK: "8388608",
},
});
server.on("error", (err) => {
throw new Error(
"Failed to start server executable " +
pathToServerBinary +
", make sure you built it first."
);
});
let shouldClose = false;
server.on("exit", () => {
@@ -44,12 +43,10 @@ export async function startServer(port: number = RPC_SERVER_PORT): Promise<RpcSe
});
server.stderr.pipe(process.stderr);
server.stdout.pipe(process.stdout)
const url = `ws://localhost:${port}/ws`
return {
url,
stdin: server.stdin,
stdout: server.stdout,
close: async () => {
shouldClose = true;
if (!server.kill()) {