diff --git a/packages/krpc-client/src/client.ts b/packages/krpc-client/src/client.ts index 451eeb7..f3e369c 100644 --- a/packages/krpc-client/src/client.ts +++ b/packages/krpc-client/src/client.ts @@ -226,15 +226,22 @@ export class KRPCClient { sendMessage(this.rpcSocket, KRPC.Request, { calls: [call] }); const raw = await recvRawMessage(this.rpcSocket); const response = decodeMessage<{ - error?: { service: string; name: string; description: string }; - results: { - error?: { service: string; name: string; description: string }; - value: Uint8Array; - }[]; + // The error/result fields are `bytes` in the kRPC schema. They + // carry a serialized Error or value sub-message that we have + // to decode on its own — not a pre-decoded object. + error?: Uint8Array; + results: { error?: Uint8Array; value: Uint8Array }[]; }>(KRPC.Response, raw); - if (response.error) { + if (response.error && response.error.length > 0) { + const err = decodeMessage<{ + service: string; + name: string; + description: string; + stackTrace: string; + }>(KRPC.Error, response.error); throw new Error( - `RPC error: ${response.error.service}.${response.error.name}: ${response.error.description}`, + `RPC error: ${err.service}.${err.name}: ${err.description}` + + (err.stackTrace ? `\n${err.stackTrace}` : ''), ); } if (response.results.length === 0) { @@ -244,9 +251,16 @@ export class KRPCClient { if (!r) { throw new Error('empty response result'); } - if (r.error) { + if (r.error && r.error.length > 0) { + const err = decodeMessage<{ + service: string; + name: string; + description: string; + stackTrace: string; + }>(KRPC.Error, r.error); throw new Error( - `RPC result error: ${r.error.service}.${r.error.name}: ${r.error.description}`, + `RPC result error: ${err.service}.${err.name}: ${err.description}` + + (err.stackTrace ? `\n${err.stackTrace}` : ''), ); } return r.value; @@ -276,13 +290,21 @@ export class KRPCClient { }; sendMessage(this.rpcSocket, KRPC.Request, { calls: [addCall] }); const response = decodeMessage<{ - results: { value: Uint8Array; error?: { name: string; description: string } }[]; + results: { value: Uint8Array; error?: Uint8Array }[]; }>(KRPC.Response, await recvRawMessage(this.rpcSocket)); if (response.results.length === 0) throw new Error('empty AddStream response'); const r0 = response.results[0]; if (!r0) throw new Error('empty AddStream result'); - if (r0.error) { - throw new Error(`AddStream error: ${r0.error.name}: ${r0.error.description}`); + if (r0.error && r0.error.length > 0) { + const err = decodeMessage<{ + service: string; + name: string; + description: string; + stackTrace: string; + }>(KRPC.Error, r0.error); + throw new Error( + `AddStream error: ${err.service}.${err.name}: ${err.description}`, + ); } const stream = decodeMessage<{ id: number }>(KRPC.Stream, r0.value); return stream.id; @@ -330,9 +352,24 @@ export class KRPCClient { try { const raw = await recvRawMessage(this.streamSocket); const update = decodeMessage<{ - results: { id: number; result: { value: Uint8Array } }[]; + results: { id: number; result: { error?: Uint8Array; value: Uint8Array } }[]; }>(KRPC.StreamUpdate, raw); for (const r of update.results) { + if (r.result.error && r.result.error.length > 0) { + const err = decodeMessage<{ + service: string; + name: string; + description: string; + stackTrace: string; + }>(KRPC.Error, r.result.error); + // eslint-disable-next-line no-console + console.warn( + `[krpc-client] stream ${r.id} error: ${err.service}.${err.name}: ${err.description}`, + ); + // Pass empty value so the stream handler sees "no data" rather than crashing + for (const h of this.streamHandlers) h(r.id, new Uint8Array()); + continue; + } for (const h of this.streamHandlers) h(r.id, r.result.value); } } catch (err) {