bare-rpc 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -18
- package/index.d.ts +26 -12
- package/index.js +53 -15
- package/lib/command-router.js +17 -10
- package/lib/errors.d.ts +2 -0
- package/lib/errors.js +8 -0
- package/lib/outgoing-stream.js +14 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,40 +8,119 @@ npm i bare-rpc
|
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
+
This example wires two peers together over IPC and makes one request:
|
|
12
|
+
|
|
11
13
|
```js
|
|
14
|
+
import IPC from 'bare-ipc'
|
|
12
15
|
import RPC from 'bare-rpc'
|
|
13
16
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
console.log(req.data.toString()) // ping
|
|
18
|
-
req.reply('pong')
|
|
19
|
-
}
|
|
20
|
-
})
|
|
17
|
+
const [a, b] = IPC.open().map((port) => port.connect())
|
|
18
|
+
|
|
19
|
+
const server = new RPC(a, (req) => req.reply('pong'))
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
const client = new RPC(b)
|
|
22
|
+
|
|
23
|
+
const req = client.request(1)
|
|
24
24
|
req.send('ping')
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
console.log((await req.reply()).toString()) // pong
|
|
27
|
+
|
|
28
|
+
a.destroy()
|
|
29
|
+
b.destroy()
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
`new RPC(stream, onrequest, opts)` runs a session over a duplex `stream`, such as a pipe, a socket or an IPC port. `onrequest` is a function that is called with each request and event of the peer, or a `CommandRouter`. `opts` takes `maxFrameSize` and `maxBufferedBytes`, as described under [Untrusted peers](#untrusted-peers).
|
|
33
|
+
|
|
34
|
+
See the [`bare-rpc` reference](https://docs.pears.com/reference/bare/modules/bare-rpc) for the full API.
|
|
35
|
+
|
|
36
|
+
## Command routing
|
|
37
|
+
|
|
38
|
+
A `CommandRouter` sends each request and event to the handler registered for its command. Pass it to `RPC` as `onrequest`:
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
const router = new RPC.CommandRouter()
|
|
42
|
+
|
|
43
|
+
router.respond(1, (req, data) => Buffer.from('pong'))
|
|
44
|
+
router.respond(2, (req, data) => console.log(req.command))
|
|
45
|
+
|
|
46
|
+
const rpc = new RPC(stream, router)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
A handler is called with the request and its data, and returns the data to reply with, unless it has answered the request itself. Events for a command go to the same handler, and its return value is ignored. A request for a command with no handler is rejected with the code `UNKNOWN_COMMAND`. An event for a command with no handler is dropped.
|
|
50
|
+
|
|
51
|
+
The data is decoded and the reply encoded with a [compact-encoding](https://github.com/holepunchto/compact-encoding), `c.raw` by default. Pass `valueEncoding` to `new CommandRouter()` to change the default, or `valueEncoding`, `requestEncoding` and `responseEncoding` to `respond(command, opts, handler)` for one command.
|
|
52
|
+
|
|
53
|
+
## Requests and responses
|
|
54
|
+
|
|
55
|
+
`rpc.request(command)` returns a request. Call `send(data)` on it to send it, and `await reply()` to get its reply. `rpc.event(command)` returns an event, which gets no reply. Call `send(data)` on it to send it. Each can be sent once, and a reply can be read once. Sending again throws an `RPCError` with the code `ALREADY_SENT`, and reading again throws one with the code `ALREADY_RECEIVED`.
|
|
56
|
+
|
|
57
|
+
Data can be a buffer, or a string that `send(data, encoding)` and `reply(data, encoding)` encode with `encoding`, `utf8` by default. `reply(encoding)` on a request you sent decodes the reply to a string.
|
|
58
|
+
|
|
59
|
+
A request of the peer is answered once, with `reply()` or `createResponseStream()`. Answering again throws an `RPCError` with the code `ALREADY_SENT`, and `req.sent` tells whether the request has been answered. Events reach `onrequest` as requests without `reply()`. Each request and event of the peer is handled on its own, so a slow async handler does not hold back the others.
|
|
60
|
+
|
|
61
|
+
If a request handler throws before it answers, the request is rejected with the `message` of the error, and its `code` and `errno` if it has them. If the handler had opened a response stream, the stream is destroyed with the error instead.
|
|
62
|
+
|
|
63
|
+
When the peer rejects a request, `reply()` rejects with an `Error` with the `message`, `code` and `errno` of the peer.
|
|
31
64
|
|
|
32
|
-
|
|
65
|
+
Without `onrequest`, every request is rejected with the code `UNKNOWN_COMMAND`, and events are dropped.
|
|
66
|
+
|
|
67
|
+
## Streaming requests and responses
|
|
68
|
+
|
|
69
|
+
A request can stream its body, its reply, or both:
|
|
70
|
+
|
|
71
|
+
- `createRequestStream()` returns a `Writable` for the body, and `await reply()` gets a single reply.
|
|
72
|
+
- `send(data)` sends a plain request, and `createResponseStream()` returns a `Readable` for the reply.
|
|
73
|
+
- `createRequestStream()` and `createResponseStream()` together stream both.
|
|
74
|
+
|
|
75
|
+
On the answering side, `req.createRequestStream()` returns a `Readable` for the body, and `req.createResponseStream()` returns a `Writable` for the reply. This handler echoes the body back:
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const server = new RPC(stream, (req) => {
|
|
79
|
+
req.createRequestStream().pipe(req.createResponseStream())
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const req = client.request(1)
|
|
83
|
+
const outgoing = req.createRequestStream()
|
|
84
|
+
const incoming = req.createResponseStream()
|
|
85
|
+
|
|
86
|
+
outgoing.end('ping')
|
|
87
|
+
|
|
88
|
+
for await (const chunk of incoming) console.log(chunk.toString()) // ping
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A stream only carries data once both ends have opened it, so data written before then is held back until the peer has opened its end. The handler opens the body when it calls `req.createRequestStream()`. If the peer answers before it opens the body, the body is destroyed, with the error of the reply if it has one.
|
|
92
|
+
|
|
93
|
+
An incoming stream asks the peer to pause once it buffers its `highWaterMark`, which is 16 KiB by default, and to resume once it is read. While the peer has paused an outgoing stream, its writes are held back. If the peer keeps sending anyway, an incoming stream holds at most `maxBufferedBytes`, 16 MiB by default, before the session fails with an `RPCError` with the code `STREAM_OVERFLOW`.
|
|
94
|
+
|
|
95
|
+
The body of a request and the reply to a request can each be read once. Calling `createRequestStream()` on a request of the peer again, or `createResponseStream()` or `reply()` on a request you sent again, throws an `RPCError` with the code `ALREADY_RECEIVED`.
|
|
96
|
+
|
|
97
|
+
## Termination
|
|
98
|
+
|
|
99
|
+
The session ends with an `RPCError` with the code `CHANNEL_CLOSED` when `stream` closes. It fails with an error instead when:
|
|
100
|
+
|
|
101
|
+
- `stream` emits an error, with that error.
|
|
102
|
+
- `stream` ends partway through a frame, with the code `INVALID_MESSAGE`.
|
|
103
|
+
- An event handler throws, with that error.
|
|
104
|
+
- The peer sends a bad frame, as described under [Untrusted peers](#untrusted-peers).
|
|
105
|
+
|
|
106
|
+
Pending replies then reject with the error, and so do later replies. Open streams are destroyed with it, and so are streams created later. Events sent after the end are dropped.
|
|
107
|
+
|
|
108
|
+
`RPC` does not close `stream` when the session ends cleanly. When it fails for any reason but an error of `stream`, `RPC` destroys `stream` with the error, so the frames still queued in it are not written. To end the session yourself, destroy `stream`. `rpc.idle` tells whether no requests or streams are in flight, so that it is safe to do so.
|
|
33
109
|
|
|
34
110
|
## Untrusted peers
|
|
35
111
|
|
|
36
|
-
`
|
|
112
|
+
`RPC` does not trust the peer to send valid frames. If a frame is larger than `maxFrameSize`, cannot be decoded, or breaks the protocol, the session fails with an `RPCError`. The code tells you why:
|
|
37
113
|
|
|
38
|
-
- `FRAME_TOO_LARGE`: The length prefix is larger than `maxFrameSize
|
|
39
|
-
- `UNKNOWN_MESSAGE`: The frame has a message type that `
|
|
40
|
-
- `INVALID_MESSAGE`: The frame cannot be decoded,
|
|
114
|
+
- `FRAME_TOO_LARGE`: The frame, with its 4-byte length prefix, is larger than `maxFrameSize`, 16 MiB by default. This is checked as soon as the length prefix arrives, before the rest of the frame is read.
|
|
115
|
+
- `UNKNOWN_MESSAGE`: The frame has a message type that `RPC` does not know.
|
|
116
|
+
- `INVALID_MESSAGE`: The frame cannot be decoded, `stream` ends partway through a frame, or the frame is one that a valid peer never sends. Examples are a response or stream message with an id of 0, a stream message with an invalid set of flags, and a new request that reuses the id of a request that is still in flight.
|
|
117
|
+
- `STREAM_OVERFLOW`: An incoming stream holds more than `maxBufferedBytes` of data that has not been read. This only happens when the peer keeps sending after it was asked to pause.
|
|
41
118
|
|
|
42
119
|
A frame that refers to a request or stream that has already finished, or that was never known, is ignored. The same goes for frames that arrive for a stream that has already ended or been destroyed.
|
|
43
120
|
|
|
44
|
-
If you read the
|
|
121
|
+
If you read the reply as a stream but the peer replies without one, the reply arrives on the stream as a single chunk, unless it is empty, and the stream then ends. An error reply destroys the stream with the error instead.
|
|
122
|
+
|
|
123
|
+
If you expect a plain reply but the peer replies with a stream, `reply()` rejects with an `RPCError` with the code `UNEXPECTED_STREAM`. The session destroys the stream toward the peer and stays open.
|
|
45
124
|
|
|
46
125
|
## License
|
|
47
126
|
|
package/index.d.ts
CHANGED
|
@@ -124,7 +124,9 @@ interface RPCOutgoingRequest {
|
|
|
124
124
|
* @param encoding - If given, decodes the reply payload to a string using this encoding; omit to
|
|
125
125
|
* receive the raw `Buffer`.
|
|
126
126
|
* @returns a promise that resolves with the remote end's reply payload, or rejects with the
|
|
127
|
-
* channel's teardown error if it closes before a reply arrives.
|
|
127
|
+
* channel's teardown error if it closes before a reply arrives. If the remote end replies with a
|
|
128
|
+
* stream, the promise rejects with an error whose code is `UNEXPECTED_STREAM`, the remote end's
|
|
129
|
+
* stream is destroyed, and the connection stays open.
|
|
128
130
|
* @throws {ALREADY_RECEIVED} a reply is already being received for this request.
|
|
129
131
|
*/
|
|
130
132
|
reply(encoding?: BufferEncoding): Promise<Buffer | string | null>
|
|
@@ -213,18 +215,20 @@ interface RPCRespondOptions {
|
|
|
213
215
|
}
|
|
214
216
|
|
|
215
217
|
/**
|
|
216
|
-
* @param req - The incoming request.
|
|
218
|
+
* @param req - The incoming request. For an event, this is an `RPCIncomingEvent`.
|
|
217
219
|
* @param data - The request data, decoded with the request encoding of the command.
|
|
218
220
|
* @returns the response data, encoded with the response encoding of the command and sent as the
|
|
219
|
-
* reply unless the handler has already replied.
|
|
221
|
+
* reply unless the handler has already replied. For an event, the return value is ignored and
|
|
222
|
+
* nothing is sent.
|
|
220
223
|
*/
|
|
221
224
|
type RPCRequestHandler = (req: RPCIncomingRequest, data: any) => unknown
|
|
222
225
|
|
|
223
226
|
interface RPCCommandRouter {
|
|
224
227
|
/**
|
|
225
|
-
* Answer requests for `command` with `onrequest`, in place of any handler registered before.
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
+
* Answer requests for `command` with `onrequest`, in place of any handler registered before.
|
|
229
|
+
* Events for `command` are also passed to `onrequest`, but are never answered. A request for a
|
|
230
|
+
* command with no handler is answered with an error whose code is `UNKNOWN_COMMAND`, and an event
|
|
231
|
+
* for such a command is dropped.
|
|
228
232
|
* @param command - The command number to answer.
|
|
229
233
|
* @param opts - The encodings of the command.
|
|
230
234
|
* @param onrequest - The handler of the requests.
|
|
@@ -247,6 +251,12 @@ interface RPCOptions {
|
|
|
247
251
|
* Defaults to 16 MiB.
|
|
248
252
|
*/
|
|
249
253
|
maxFrameSize?: number
|
|
254
|
+
/**
|
|
255
|
+
* The most data, in bytes, that one incoming stream may hold before it is read. A remote end that
|
|
256
|
+
* keeps sending past this limit, rather than stop when asked to pause, fails the connection with
|
|
257
|
+
* `STREAM_OVERFLOW`. Defaults to 16 MiB.
|
|
258
|
+
*/
|
|
259
|
+
maxBufferedBytes?: number
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
interface RPC {
|
|
@@ -276,19 +286,23 @@ declare class RPC {
|
|
|
276
286
|
* @param stream - The duplex stream to frame RPC messages over, such as a pipe or socket.
|
|
277
287
|
* @param onrequest - Callback run for each incoming request or event, receiving an
|
|
278
288
|
* `RPCIncomingRequest` (or `RPCIncomingEvent`) to inspect and `reply()` to, or an
|
|
279
|
-
* `RPCCommandRouter` that answers each request with the handler of its command.
|
|
280
|
-
*
|
|
289
|
+
* `RPCCommandRouter` that answers each request with the handler of its command. If it throws, the
|
|
290
|
+
* request is answered with the error, or, if it has already created a response stream, that
|
|
291
|
+
* stream is destroyed with the error. If it has already replied, nothing more is sent. If
|
|
292
|
+
* omitted, every request is answered with an error whose code is `UNKNOWN_COMMAND` and every
|
|
293
|
+
* event is dropped.
|
|
281
294
|
* @param opts - Options for the connection.
|
|
282
295
|
*
|
|
283
296
|
* The remote end is not trusted to send valid frames. A frame that is too large, cannot be
|
|
284
|
-
* decoded, or breaks the protocol fails the connection
|
|
285
|
-
*
|
|
286
|
-
*
|
|
297
|
+
* decoded, or breaks the protocol fails the connection, and so does a stream that holds more
|
|
298
|
+
* unread data than `maxBufferedBytes`: `stream` is destroyed with an `RPCError` whose code is
|
|
299
|
+
* `FRAME_TOO_LARGE`, `UNKNOWN_MESSAGE`, `INVALID_MESSAGE` or `STREAM_OVERFLOW`, and every request
|
|
300
|
+
* and stream in flight is rejected or destroyed with it. A frame that refers to a request or stream
|
|
287
301
|
* that has already finished, or that was never known, is ignored.
|
|
288
302
|
*/
|
|
289
303
|
constructor(
|
|
290
304
|
stream: Duplex,
|
|
291
|
-
onrequest?: ((req: RPCIncomingRequest) => void | Promise<void>) | RPCCommandRouter,
|
|
305
|
+
onrequest?: ((req: RPCIncomingRequest) => void | Promise<void>) | RPCCommandRouter | null,
|
|
292
306
|
opts?: RPCOptions
|
|
293
307
|
)
|
|
294
308
|
}
|
package/index.js
CHANGED
|
@@ -14,6 +14,7 @@ const OutgoingStream = require('./lib/outgoing-stream')
|
|
|
14
14
|
const CommandRouter = require('./lib/command-router')
|
|
15
15
|
|
|
16
16
|
const DEFAULT_MAX_FRAME_SIZE = 16 * 1024 * 1024
|
|
17
|
+
const DEFAULT_MAX_BUFFERED_BYTES = 16 * 1024 * 1024
|
|
17
18
|
const MAX_PENDING_RESPONSES = 1024
|
|
18
19
|
|
|
19
20
|
const streamFlags = new Set()
|
|
@@ -35,11 +36,13 @@ for (const direction of [s.REQUEST, s.RESPONSE]) {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
module.exports = exports = class RPC {
|
|
38
|
-
constructor(stream, onrequest =
|
|
39
|
-
const { maxFrameSize = DEFAULT_MAX_FRAME_SIZE } =
|
|
39
|
+
constructor(stream, onrequest = null, opts = {}) {
|
|
40
|
+
const { maxFrameSize = DEFAULT_MAX_FRAME_SIZE, maxBufferedBytes = DEFAULT_MAX_BUFFERED_BYTES } =
|
|
41
|
+
opts
|
|
40
42
|
|
|
41
43
|
this._stream = stream
|
|
42
44
|
this._maxFrameSize = maxFrameSize
|
|
45
|
+
this._maxBufferedBytes = maxBufferedBytes
|
|
43
46
|
this._id = 0
|
|
44
47
|
|
|
45
48
|
this._outgoingRequests = new Map()
|
|
@@ -58,6 +61,10 @@ module.exports = exports = class RPC {
|
|
|
58
61
|
this._closed = false
|
|
59
62
|
this._error = null
|
|
60
63
|
|
|
64
|
+
if (onrequest === null) {
|
|
65
|
+
onrequest = new CommandRouter()
|
|
66
|
+
}
|
|
67
|
+
|
|
61
68
|
if (typeof onrequest === 'function') {
|
|
62
69
|
onrequest = onrequest.bind(this)
|
|
63
70
|
} else {
|
|
@@ -157,6 +164,10 @@ module.exports = exports = class RPC {
|
|
|
157
164
|
|
|
158
165
|
this._outgoingRequests.set(request.id, request)
|
|
159
166
|
|
|
167
|
+
// Announce the stream right away rather than when it opens, so that the
|
|
168
|
+
// remote end also learns of a stream that is destroyed before it opens.
|
|
169
|
+
request._requestStream._sendOpen()
|
|
170
|
+
|
|
160
171
|
request._requestStream.on('close', () => this._gcOutgoingRequest(request))
|
|
161
172
|
} else {
|
|
162
173
|
request._requestStream = new IncomingStream(this, request, t.REQUEST, opts)
|
|
@@ -197,6 +208,8 @@ module.exports = exports = class RPC {
|
|
|
197
208
|
|
|
198
209
|
this._outgoingResponses.set(request.id, request)
|
|
199
210
|
|
|
211
|
+
request._responseStream._sendOpen()
|
|
212
|
+
|
|
200
213
|
if (this._onanswered(request)) request._responseStream._continueOpen()
|
|
201
214
|
|
|
202
215
|
request._responseStream.on('close', () => this._outgoingResponses.delete(request.id))
|
|
@@ -421,8 +434,9 @@ module.exports = exports = class RPC {
|
|
|
421
434
|
} catch (err) {
|
|
422
435
|
safetyCatch(err)
|
|
423
436
|
|
|
424
|
-
if (message.id) this.
|
|
425
|
-
else
|
|
437
|
+
if (message.id === 0) this._stream.destroy(err)
|
|
438
|
+
else if (request._responseStream !== null) request._responseStream.destroy(err)
|
|
439
|
+
else if (!request.sent) this._sendError(request, err)
|
|
426
440
|
}
|
|
427
441
|
}
|
|
428
442
|
|
|
@@ -431,19 +445,13 @@ module.exports = exports = class RPC {
|
|
|
431
445
|
if (request === undefined) return
|
|
432
446
|
|
|
433
447
|
if (message.error) {
|
|
434
|
-
request.
|
|
435
|
-
this._gcOutgoingRequest(request)
|
|
436
|
-
|
|
437
|
-
request._reject(message.error)
|
|
448
|
+
this._settle(request, message.error, null)
|
|
438
449
|
|
|
439
450
|
if (request._responseStream !== null) request._responseStream.destroy(message.error)
|
|
440
451
|
|
|
441
452
|
this._releaseRequestStream(request, message.error)
|
|
442
453
|
} else if (message.stream === 0) {
|
|
443
|
-
request
|
|
444
|
-
this._gcOutgoingRequest(request)
|
|
445
|
-
|
|
446
|
-
request._resolve(message.data)
|
|
454
|
+
this._settle(request, null, message.data)
|
|
447
455
|
|
|
448
456
|
const stream = request._responseStream
|
|
449
457
|
|
|
@@ -453,9 +461,31 @@ module.exports = exports = class RPC {
|
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
this._releaseRequestStream(request, null)
|
|
464
|
+
} else if (request._responseStream === null) {
|
|
465
|
+
const err = errors.UNEXPECTED_STREAM(`Request '${request.id}' was answered with a stream`)
|
|
466
|
+
|
|
467
|
+
this._settle(request, err, null)
|
|
468
|
+
|
|
469
|
+
this._sendMessage({
|
|
470
|
+
type: t.STREAM,
|
|
471
|
+
id: request.id,
|
|
472
|
+
stream: s.RESPONSE | s.DESTROY,
|
|
473
|
+
error: null,
|
|
474
|
+
data: null
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
this._releaseRequestStream(request, err)
|
|
456
478
|
}
|
|
457
479
|
}
|
|
458
480
|
|
|
481
|
+
_settle(request, err, data) {
|
|
482
|
+
request._responded = true
|
|
483
|
+
this._gcOutgoingRequest(request)
|
|
484
|
+
|
|
485
|
+
if (err) request._reject(err)
|
|
486
|
+
else request._resolve(data)
|
|
487
|
+
}
|
|
488
|
+
|
|
459
489
|
// Once the request is answered, a request stream that the remote end never
|
|
460
490
|
// opened will never be read, and would otherwise wait for its open forever.
|
|
461
491
|
_releaseRequestStream(request, err) {
|
|
@@ -525,7 +555,17 @@ module.exports = exports = class RPC {
|
|
|
525
555
|
const stream = this._incomingStream(message)
|
|
526
556
|
if (!isPushable(stream)) return
|
|
527
557
|
|
|
528
|
-
|
|
558
|
+
const flushed = stream.push(message.data)
|
|
559
|
+
|
|
560
|
+
if (stream._readableState.buffered > this._maxBufferedBytes) {
|
|
561
|
+
return this._fail(
|
|
562
|
+
errors.STREAM_OVERFLOW(
|
|
563
|
+
`Stream buffered more than the limit of ${this._maxBufferedBytes} bytes`
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (flushed === false) {
|
|
529
569
|
this._sendMessage({
|
|
530
570
|
type: t.STREAM,
|
|
531
571
|
id: stream._request.id,
|
|
@@ -605,5 +645,3 @@ function findStream(message, requests, responses) {
|
|
|
605
645
|
function isPushable(stream) {
|
|
606
646
|
return stream !== null && !stream.destroying && !isEnding(stream)
|
|
607
647
|
}
|
|
608
|
-
|
|
609
|
-
function noop() {}
|
package/lib/command-router.js
CHANGED
|
@@ -31,24 +31,31 @@ module.exports = class RPCCommandRouter {
|
|
|
31
31
|
async _onrequest(req) {
|
|
32
32
|
const responder = this._responders.get(req.command)
|
|
33
33
|
|
|
34
|
-
if (
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
throw errors.UNKNOWN_COMMAND(`Unknown command ${req.command}`)
|
|
34
|
+
if (req.id === undefined) {
|
|
35
|
+
if (responder !== undefined) await this._dispatch(responder, req)
|
|
36
|
+
return
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let data = req.data
|
|
39
|
+
if (responder === undefined) throw errors.UNKNOWN_COMMAND(`Unknown command ${req.command}`)
|
|
43
40
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
data = await onrequest(req, data)
|
|
41
|
+
let data = await this._dispatch(responder, req)
|
|
47
42
|
|
|
48
43
|
if (req.sent) return
|
|
49
44
|
|
|
45
|
+
const { responseEncoding } = responder
|
|
46
|
+
|
|
50
47
|
if (responseEncoding) data = c.encode(responseEncoding, data)
|
|
51
48
|
|
|
52
49
|
req.reply(data)
|
|
53
50
|
}
|
|
51
|
+
|
|
52
|
+
_dispatch(responder, req) {
|
|
53
|
+
const { onrequest, requestEncoding } = responder
|
|
54
|
+
|
|
55
|
+
let data = req.data
|
|
56
|
+
|
|
57
|
+
if (requestEncoding) data = c.decode(requestEncoding, data)
|
|
58
|
+
|
|
59
|
+
return onrequest(req, data)
|
|
60
|
+
}
|
|
54
61
|
}
|
package/lib/errors.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ declare class RPCError extends Error {
|
|
|
6
6
|
static ALREADY_SENT(msg: string): RPCError
|
|
7
7
|
static ALREADY_RECEIVED(msg: string): RPCError
|
|
8
8
|
static CHANNEL_CLOSED(msg: string): RPCError
|
|
9
|
+
static UNEXPECTED_STREAM(msg: string): RPCError
|
|
10
|
+
static STREAM_OVERFLOW(msg: string): RPCError
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
export = RPCError
|
package/lib/errors.js
CHANGED
|
@@ -39,4 +39,12 @@ module.exports = class RPCError extends Error {
|
|
|
39
39
|
static CHANNEL_CLOSED(msg) {
|
|
40
40
|
return new RPCError(msg, 'CHANNEL_CLOSED', RPCError.CHANNEL_CLOSED)
|
|
41
41
|
}
|
|
42
|
+
|
|
43
|
+
static UNEXPECTED_STREAM(msg) {
|
|
44
|
+
return new RPCError(msg, 'UNEXPECTED_STREAM', RPCError.UNEXPECTED_STREAM)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static STREAM_OVERFLOW(msg) {
|
|
48
|
+
return new RPCError(msg, 'STREAM_OVERFLOW', RPCError.STREAM_OVERFLOW)
|
|
49
|
+
}
|
|
42
50
|
}
|
package/lib/outgoing-stream.js
CHANGED
|
@@ -12,14 +12,15 @@ module.exports = class RPCOutgoingStream extends Writable {
|
|
|
12
12
|
this._mask = type === t.REQUEST ? s.REQUEST : s.RESPONSE
|
|
13
13
|
|
|
14
14
|
this._pendingOpen = null
|
|
15
|
+
this._flushed = false
|
|
15
16
|
this._acknowledged = false
|
|
16
17
|
this._ended = false
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
_sendOpen() {
|
|
20
21
|
const onflushed = () => {
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
this._flushed = true
|
|
23
|
+
this._finishOpen()
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
switch (this._type) {
|
|
@@ -51,14 +52,22 @@ module.exports = class RPCOutgoingStream extends Writable {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
_open(cb) {
|
|
56
|
+
this._pendingOpen = cb
|
|
57
|
+
this._finishOpen()
|
|
58
|
+
}
|
|
59
|
+
|
|
54
60
|
_continueOpen() {
|
|
55
61
|
this._acknowledged = true
|
|
62
|
+
this._finishOpen()
|
|
63
|
+
}
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
_finishOpen() {
|
|
66
|
+
if (!this._flushed || !this._acknowledged || this._pendingOpen === null) return
|
|
58
67
|
|
|
59
68
|
const cb = this._pendingOpen
|
|
60
69
|
this._pendingOpen = null
|
|
61
|
-
cb()
|
|
70
|
+
cb(null)
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
_predestroy() {
|