@@ -5,8 +5,8 @@ import type {
55 JSONRPCRequestMessage ,
66 StreamFactory ,
77 ClientManifest ,
8- RPCStream ,
9- } from ' ./types' ;
8+ RPCStream , JSONRPCResponseResult
9+ } from " ./types" ;
1010import type { JSONValue } from '../types' ;
1111import type {
1212 JSONRPCRequest ,
@@ -20,7 +20,8 @@ import { Timer } from '@matrixai/timer';
2020import * as rpcUtilsMiddleware from './utils/middleware' ;
2121import * as rpcErrors from './errors' ;
2222import * as rpcUtils from './utils/utils' ;
23- import { promise } from '../utils' ;
23+ import { never , promise } from "../utils" ;
24+ import { parseJSONRPCResponse } from "./utils/utils" ;
2425
2526const timerCleanupReasonSymbol = Symbol ( 'timerCleanUpReasonSymbol' ) ;
2627
@@ -260,12 +261,14 @@ class RPCClient<M extends ClientManifest> {
260261 const abortRaceProm = promise < never > ( ) ;
261262 // Prevent unhandled rejection when we're done with the promise
262263 abortRaceProm . p . catch ( ( ) => { } ) ;
264+ signal . addEventListener ( 'abort' , ( ) => {
265+ abortRaceProm . rejectP ( signal . reason ) ;
266+ } , { once : true } ) ;
263267 let abortHandler : ( ) => void ;
264268 if ( ctx . signal != null ) {
265269 // Propagate signal events
266270 abortHandler = ( ) => {
267271 abortController . abort ( ctx . signal ?. reason ) ;
268- abortRaceProm . rejectP ( ctx . signal ?. reason ) ;
269272 } ;
270273 if ( ctx . signal . aborted ) abortHandler ( ) ;
271274 ctx . signal . addEventListener ( 'abort' , abortHandler ) ;
@@ -288,7 +291,6 @@ class RPCClient<M extends ClientManifest> {
288291 void timer . then (
289292 ( ) => {
290293 abortController . abort ( timeoutError ) ;
291- abortRaceProm . rejectP ( timeoutError ) ;
292294 } ,
293295 ( ) => { } , // Ignore cancellation error
294296 ) ;
@@ -384,29 +386,27 @@ class RPCClient<M extends ClientManifest> {
384386 public async rawStreamCaller (
385387 method : string ,
386388 headerParams : JSONValue ,
387- ctx : Partial < ContextTimed > = { } ,
388- ) : Promise < RPCStream < Uint8Array , Uint8Array > > {
389+ ctx : Partial < ContextTimedInput > = { } ,
390+ ) : Promise < RPCStream < Uint8Array , Uint8Array , Record < string , JSONValue > & { result : JSONValue , command : string } > > {
389391 const abortController = new AbortController ( ) ;
390392 const signal = abortController . signal ;
391- // A promise that will reject if there is an abort signal or timeout
392- const abortRaceProm = promise < never > ( ) ;
393- // Prevent unhandled rejection when we're done with the promise
394- abortRaceProm . p . catch ( ( ) => { } ) ;
395393 let abortHandler : ( ) => void ;
396394 if ( ctx . signal != null ) {
397395 // Propagate signal events
398396 abortHandler = ( ) => {
399397 abortController . abort ( ctx . signal ?. reason ) ;
400- abortRaceProm . rejectP ( ctx . signal ?. reason ) ;
401398 } ;
402399 if ( ctx . signal . aborted ) abortHandler ( ) ;
403400 ctx . signal . addEventListener ( 'abort' , abortHandler ) ;
404401 }
405- const timer =
406- ctx . timer ??
407- new Timer ( {
408- delay : this . streamKeepAliveTimeoutTime ,
402+ let timer : Timer ;
403+ if ( ! ( ctx . timer instanceof Timer ) ) {
404+ timer = new Timer ( {
405+ delay : ctx . timer ?? this . streamKeepAliveTimeoutTime ,
409406 } ) ;
407+ } else {
408+ timer = ctx . timer ;
409+ }
410410 const cleanUp = ( ) => {
411411 // Clean up the timer and signal
412412 if ( ctx . timer == null ) timer . cancel ( timerCleanupReasonSymbol ) ;
@@ -416,13 +416,22 @@ class RPCClient<M extends ClientManifest> {
416416 void timer . then (
417417 ( ) => {
418418 abortController . abort ( timeoutError ) ;
419- abortRaceProm . rejectP ( timeoutError ) ;
420419 } ,
421420 ( ) => { } ,
422421 ) ;
423- let rpcStream : RPCStream < Uint8Array , Uint8Array > ;
424- const setupStream = async ( ) => {
425- const rpcStream = await this . streamFactory ( { signal, timer } ) ;
422+ let streamCreation : [ JSONValue , RPCStream < Uint8Array , Uint8Array > ] ;
423+ const setupStream = async ( ) : Promise < [ JSONValue , RPCStream < Uint8Array , Uint8Array > ] > => {
424+ if ( signal . aborted ) throw signal . reason ;
425+ const abortProm = promise < never > ( ) ;
426+ // ignore error if orphaned
427+ void abortProm . p . catch ( ( ) => { } ) ;
428+ signal . addEventListener ( 'abort' , ( ) => {
429+ abortProm . rejectP ( signal . reason ) ;
430+ } , { once : true } ) ;
431+ const rpcStream = await Promise . race ( [
432+ this . streamFactory ( { signal, timer } ) ,
433+ abortProm . p ,
434+ ] ) ;
426435 const tempWriter = rpcStream . writable . getWriter ( ) ;
427436 const header : JSONRPCRequestMessage = {
428437 jsonrpc : '2.0' ,
@@ -432,15 +441,51 @@ class RPCClient<M extends ClientManifest> {
432441 } ;
433442 await tempWriter . write ( Buffer . from ( JSON . stringify ( header ) ) ) ;
434443 tempWriter . releaseLock ( ) ;
435- return rpcStream ;
444+ const headTransformStream = rpcUtilsMiddleware . binaryToJsonMessageStream (
445+ rpcUtils . parseJSONRPCResponse ,
446+ ) ;
447+ void rpcStream . readable
448+ // Allow us to re-use the readable after reading the first message
449+ . pipeTo ( headTransformStream . writable , {
450+ preventClose : true ,
451+ preventCancel : true ,
452+ } )
453+ // Ignore any errors here, we only care that it ended
454+ . catch ( ( ) => { } ) ;
455+ const tempReader = headTransformStream . readable . getReader ( ) ;
456+ let leadingMessage : JSONRPCResponseResult ;
457+ try {
458+ const message = await Promise . race ( [
459+ tempReader . read ( ) ,
460+ abortProm . p ,
461+ ] ) ;
462+ if ( message . done ) never ( ) ;
463+ if ( 'error' in message . value ) {
464+ const metadata = {
465+ ...( rpcStream . meta ?? { } ) ,
466+ command : method ,
467+ } ;
468+ throw rpcUtils . toError ( message . value . error . data , metadata ) ;
469+ }
470+ leadingMessage = message . value ;
471+ } catch ( e ) {
472+ await tempReader . cancel ( ) ;
473+ rpcStream . cancel ( Error ( 'TMP received error in leading response' ) ) ;
474+ throw e ;
475+ }
476+ // Downgrade back to the raw stream
477+ await tempReader . cancel ( ) ;
478+ return [ leadingMessage . result , rpcStream ] ;
436479 } ;
437480 try {
438- rpcStream = await Promise . race ( [ setupStream ( ) , abortRaceProm . p ] ) ;
481+ streamCreation = await setupStream ( ) ;
439482 } finally {
440483 cleanUp ( ) ;
441484 }
485+ const [ result , rpcStream ] = streamCreation
442486 const metadata = {
443487 ...( rpcStream . meta ?? { } ) ,
488+ result,
444489 command : method ,
445490 } ;
446491 return {
0 commit comments