|
|
|
@@ -38,6 +38,32 @@ export interface ProcedureCallRequest {
|
|
|
|
|
type StreamHandler = (streamId: number, result: Uint8Array) => void;
|
|
|
|
|
export type { StreamHandler };
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Format an error value for human consumption. Handles the cases where
|
|
|
|
|
* the thrown value is null, undefined, a string, or an Error with
|
|
|
|
|
* .code (NodeJS.ErrnoException). Falls back to JSON.stringify for
|
|
|
|
|
* unknown shapes.
|
|
|
|
|
*/
|
|
|
|
|
function formatErr(e: unknown): string {
|
|
|
|
|
if (e === null) return 'null';
|
|
|
|
|
if (e === undefined) return 'undefined';
|
|
|
|
|
if (typeof e === 'string') return e;
|
|
|
|
|
if (typeof e === 'object') {
|
|
|
|
|
const obj = e as { code?: unknown; message?: unknown; errno?: unknown };
|
|
|
|
|
const parts: string[] = [];
|
|
|
|
|
if (typeof obj.code === 'string') parts.push(`code=${obj.code}`);
|
|
|
|
|
if (typeof obj.errno === 'number') parts.push(`errno=${obj.errno}`);
|
|
|
|
|
if (typeof obj.message === 'string') parts.push(obj.message);
|
|
|
|
|
if (parts.length > 0) return parts.join(': ');
|
|
|
|
|
try {
|
|
|
|
|
return JSON.stringify(e);
|
|
|
|
|
} catch {
|
|
|
|
|
return String(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return String(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class KRPCClient {
|
|
|
|
|
private opts: Required<KRPCClientOptions>;
|
|
|
|
|
private rpcSocket: net.Socket | null = null;
|
|
|
|
@@ -59,11 +85,17 @@ export class KRPCClient {
|
|
|
|
|
|
|
|
|
|
async connect(): Promise<void> {
|
|
|
|
|
// RPC handshake
|
|
|
|
|
this.rpcSocket = await tcpConnect(
|
|
|
|
|
this.opts.host,
|
|
|
|
|
this.opts.rpcPort,
|
|
|
|
|
this.opts.connectTimeoutMs,
|
|
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
this.rpcSocket = await tcpConnect(
|
|
|
|
|
this.opts.host,
|
|
|
|
|
this.opts.rpcPort,
|
|
|
|
|
this.opts.connectTimeoutMs,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`kRPC RPC TCP connect to ${this.opts.host}:${this.opts.rpcPort} failed: ${formatErr(e)}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// The ConnectionRequest.Type enum has RPC = 0, STREAM = 1. We pass
|
|
|
|
|
// the numeric value directly because the nested-enum name lookup
|
|
|
|
|
// is brittle across protobufjs versions when the enum is nested
|
|
|
|
@@ -72,11 +104,16 @@ export class KRPCClient {
|
|
|
|
|
type: 0, // RPC
|
|
|
|
|
clientName: this.opts.clientName,
|
|
|
|
|
});
|
|
|
|
|
const resp = decodeMessage<{
|
|
|
|
|
status: number | string;
|
|
|
|
|
message: string;
|
|
|
|
|
clientIdentifier: Uint8Array;
|
|
|
|
|
}>(KRPC.ConnectionResponse, await recvRawMessage(this.rpcSocket));
|
|
|
|
|
let resp: { status: number | string; message: string; clientIdentifier: Uint8Array };
|
|
|
|
|
try {
|
|
|
|
|
resp = decodeMessage<{
|
|
|
|
|
status: number | string;
|
|
|
|
|
message: string;
|
|
|
|
|
clientIdentifier: Uint8Array;
|
|
|
|
|
}>(KRPC.ConnectionResponse, await recvRawMessage(this.rpcSocket));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new Error(`kRPC RPC handshake (response decode) failed: ${formatErr(e)}`);
|
|
|
|
|
}
|
|
|
|
|
// protobufjs decodes enums to numbers by default; OK == 0
|
|
|
|
|
if (resp.status !== 'OK' && resp.status !== 0) {
|
|
|
|
|
throw new Error(`RPC handshake failed: ${resp.status} ${resp.message}`);
|
|
|
|
@@ -84,11 +121,17 @@ export class KRPCClient {
|
|
|
|
|
this.clientIdentifier = Buffer.from(resp.clientIdentifier);
|
|
|
|
|
|
|
|
|
|
// Stream handshake
|
|
|
|
|
this.streamSocket = await tcpConnect(
|
|
|
|
|
this.opts.host,
|
|
|
|
|
this.opts.streamPort,
|
|
|
|
|
this.opts.connectTimeoutMs,
|
|
|
|
|
);
|
|
|
|
|
try {
|
|
|
|
|
this.streamSocket = await tcpConnect(
|
|
|
|
|
this.opts.host,
|
|
|
|
|
this.opts.streamPort,
|
|
|
|
|
this.opts.connectTimeoutMs,
|
|
|
|
|
);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`kRPC Stream TCP connect to ${this.opts.host}:${this.opts.streamPort} failed: ${formatErr(e)}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
sendMessage(this.streamSocket, KRPC.ConnectionRequest, {
|
|
|
|
|
type: 1, // STREAM
|
|
|
|
|
clientIdentifier: this.clientIdentifier,
|
|
|
|
|