bare-rpc 1.3.7 → 1.3.9
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 +1 -120
- package/index.d.ts +180 -2
- package/index.js +52 -15
- package/lib/messages.js +20 -13
- package/lib/outgoing-request.js +6 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -29,126 +29,7 @@ console.log(replyBuffer.toString()) // pong
|
|
|
29
29
|
|
|
30
30
|
## API
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#### `const rpc = new RPC(stream[, onrequest])`
|
|
35
|
-
|
|
36
|
-
Create an RPC instance using a duplex `stream`. `onrequest` is an optional callback run when a remote request is received. This is where processing and responding to an RPC request happens. `onrequest` receives a `RPCIncomingRequest` as an argument, for example:
|
|
37
|
-
|
|
38
|
-
```js
|
|
39
|
-
const rpc = new RPC(stream, (req) => {
|
|
40
|
-
if (req.command === 42) {
|
|
41
|
-
req.reply('pong')
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
See [`RPCIncomingRequest`](#rpcincomingrequest) for properties and methods for processing the request.
|
|
47
|
-
|
|
48
|
-
`onrequest` can also be a [`RPCCommandRouter`](#rpccommandrouter).
|
|
49
|
-
|
|
50
|
-
#### `const req = rpc.request(command)`
|
|
51
|
-
|
|
52
|
-
Create a request for `command`. `command` is a unique number that should be used to differentiate different requests on the remote end. Returns a `RPCOutgoingRequest`.
|
|
53
|
-
|
|
54
|
-
### `RPCOutgoingRequest`
|
|
55
|
-
|
|
56
|
-
#### `req.command`
|
|
57
|
-
|
|
58
|
-
The command that the request was created with. A command is a unique number.
|
|
59
|
-
|
|
60
|
-
#### `req.sent`
|
|
61
|
-
|
|
62
|
-
A boolean for whether the request has been sent.
|
|
63
|
-
|
|
64
|
-
#### `req.received`
|
|
65
|
-
|
|
66
|
-
A boolean for whether the request has received a reply.
|
|
67
|
-
|
|
68
|
-
#### `req.send([data[, encoding]])`
|
|
69
|
-
|
|
70
|
-
Send the request with the provided `data`. `data` can be a buffer or a string which will be encoded using `encoding`.
|
|
71
|
-
|
|
72
|
-
`.send()` can only be called once per request.
|
|
73
|
-
|
|
74
|
-
#### `const data = await req.reply([encoding])`
|
|
75
|
-
|
|
76
|
-
Await the reply from the remote end to the request. `encoding` can be defined for decoding the response `data` buffer back into a string.
|
|
77
|
-
|
|
78
|
-
#### `const stream = req.createRequestStream([options])`
|
|
79
|
-
|
|
80
|
-
Create a [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream for sending data with the request.
|
|
81
|
-
|
|
82
|
-
#### `const stream = req.createResponseStream([options])`
|
|
83
|
-
|
|
84
|
-
Create a [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream for receiving data in reply to the request.
|
|
85
|
-
|
|
86
|
-
### `RPCIncomingRequest`
|
|
87
|
-
|
|
88
|
-
#### `req.command`
|
|
89
|
-
|
|
90
|
-
The command that the request was sent as. A command is a unique number.
|
|
91
|
-
|
|
92
|
-
#### `req.data`
|
|
93
|
-
|
|
94
|
-
The data buffer sent with the request.
|
|
95
|
-
|
|
96
|
-
#### `req.sent`
|
|
97
|
-
|
|
98
|
-
A boolean for whether a reply has been sent.
|
|
99
|
-
|
|
100
|
-
#### `req.received`
|
|
101
|
-
|
|
102
|
-
A boolean for whether the request has been received as a stream. See [`req.createRequestStream()`](#const-stream--reqcreaterequeststream) for receiving requests as a stream.
|
|
103
|
-
|
|
104
|
-
#### `req.reply([data, [encoding]])`
|
|
105
|
-
|
|
106
|
-
Reply to the request with the provided `data`. `data` can be a buffer or a string which will be encoded using `encoding`.
|
|
107
|
-
|
|
108
|
-
#### `const stream = req.createRequestStream([options])`
|
|
109
|
-
|
|
110
|
-
Create a `Readable` stream for receiving data from the request.
|
|
111
|
-
|
|
112
|
-
#### `const stream = req.createResponseStream([options])`
|
|
113
|
-
|
|
114
|
-
Create a `Writable` stream for sending data in reply to the request.
|
|
115
|
-
|
|
116
|
-
### `RPCCommandRouter`
|
|
117
|
-
|
|
118
|
-
An alternative way to define commands and handlers for receiving them.
|
|
119
|
-
|
|
120
|
-
#### `const router = new RPC.CommandRouter()`
|
|
121
|
-
|
|
122
|
-
Create a new command router. This router can then be used when creating an `rpc`. For example:
|
|
123
|
-
|
|
124
|
-
```js
|
|
125
|
-
const router = new RPC.CommandRouter()
|
|
126
|
-
|
|
127
|
-
router.respond(42, (req, data) => {
|
|
128
|
-
console.log(data.toString()) // ping
|
|
129
|
-
|
|
130
|
-
return Buffer.from('pong')
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
const rpc = new RPC(stream, router)
|
|
134
|
-
const req = rpc.request(42)
|
|
135
|
-
req.send('ping')
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
#### `router.respond(command[, options], async (req, data) => {})`
|
|
139
|
-
|
|
140
|
-
Define a command and the handler for it. The callback for a command receives both the request (`req`) and the `data` buffer and can return a value to respond. If the request is responded to in the callback, the return value is ignored.
|
|
141
|
-
|
|
142
|
-
Options include:
|
|
143
|
-
|
|
144
|
-
```js
|
|
145
|
-
options = {
|
|
146
|
-
// Encoding for incoming request
|
|
147
|
-
requestEncoding: c.raw,
|
|
148
|
-
// Encoding for outgoing response
|
|
149
|
-
responseEncoding: c.raw
|
|
150
|
-
}
|
|
151
|
-
```
|
|
32
|
+
See the [`bare-rpc` reference](https://docs.pears.com/reference/bare/modules/bare-rpc).
|
|
152
33
|
|
|
153
34
|
## License
|
|
154
35
|
|
package/index.d.ts
CHANGED
|
@@ -19,11 +19,18 @@ declare const constants: {
|
|
|
19
19
|
|
|
20
20
|
interface RPCIncomingEvent {
|
|
21
21
|
readonly rpc: RPC
|
|
22
|
+
/** The command that the request was created with. A command is a unique number. */
|
|
22
23
|
readonly command: number
|
|
24
|
+
/** The data buffer sent with the request. */
|
|
23
25
|
readonly data: Buffer | null
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
declare class RPCIncomingEvent {
|
|
29
|
+
/**
|
|
30
|
+
* @param rpc - The `RPC` instance the event arrived on.
|
|
31
|
+
* @param command - The command number the event was sent with.
|
|
32
|
+
* @param data - The payload buffer sent with the event.
|
|
33
|
+
*/
|
|
27
34
|
constructor(rpc: RPC, command: number, data: Buffer)
|
|
28
35
|
}
|
|
29
36
|
|
|
@@ -32,16 +39,45 @@ interface RPCIncomingRequest {
|
|
|
32
39
|
readonly id: number
|
|
33
40
|
readonly command: number
|
|
34
41
|
readonly data: Buffer | null
|
|
42
|
+
/** A boolean for whether the request has been sent. */
|
|
35
43
|
readonly sent: boolean
|
|
44
|
+
/** A boolean for whether the request has received a reply. */
|
|
36
45
|
readonly received: boolean
|
|
37
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Send a reply to the request. `data` may be a buffer or a string encoded using `encoding`.
|
|
49
|
+
* @param data - The payload to send: a `Buffer`, or a string encoded using `encoding`. Omit to
|
|
50
|
+
* send no data.
|
|
51
|
+
* @param encoding - The encoding used when `data` is a string (defaults to `utf8`).
|
|
52
|
+
* @throws {ALREADY_SENT} a response has already been sent for this request.
|
|
53
|
+
*/
|
|
38
54
|
reply(data?: Buffer | string | null, encoding?: BufferEncoding): void
|
|
39
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Create a [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream for sending
|
|
58
|
+
* the streamed reply to the request.
|
|
59
|
+
* @param opts - Options for the returned
|
|
60
|
+
* [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream.
|
|
61
|
+
* @throws {ALREADY_SENT} a response has already been sent for this request.
|
|
62
|
+
*/
|
|
40
63
|
createResponseStream(opts?: WritableOptions): RPCOutgoingStream
|
|
64
|
+
/**
|
|
65
|
+
* Create a [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream for
|
|
66
|
+
* receiving the request's streamed data.
|
|
67
|
+
* @param opts - Options for the returned
|
|
68
|
+
* [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream.
|
|
69
|
+
* @throws {ALREADY_RECEIVED} the request has already been received.
|
|
70
|
+
*/
|
|
41
71
|
createRequestStream(opts?: ReadableOptions): RPCIncomingStream
|
|
42
72
|
}
|
|
43
73
|
|
|
44
74
|
declare class RPCIncomingRequest {
|
|
75
|
+
/**
|
|
76
|
+
* @param rpc - The `RPC` instance the request arrived on.
|
|
77
|
+
* @param id - The request id, used to correlate the reply with the request.
|
|
78
|
+
* @param command - The command number the request was sent with.
|
|
79
|
+
* @param data - The payload buffer sent with the request.
|
|
80
|
+
*/
|
|
45
81
|
constructor(rpc: RPC, id: number, command: number, data: Buffer)
|
|
46
82
|
}
|
|
47
83
|
|
|
@@ -50,10 +86,20 @@ interface RPCOutgoingEvent {
|
|
|
50
86
|
readonly command: number
|
|
51
87
|
readonly sent: boolean
|
|
52
88
|
|
|
89
|
+
/**
|
|
90
|
+
* @param data - The payload to send: a `Buffer`, or a string encoded using `encoding`. Omit to
|
|
91
|
+
* send no data.
|
|
92
|
+
* @param encoding - The encoding used when `data` is a string (defaults to `utf8`).
|
|
93
|
+
* @throws {ALREADY_SENT} the event has already been sent.
|
|
94
|
+
*/
|
|
53
95
|
send(data?: Buffer | string | null, encoding?: BufferEncoding): void
|
|
54
96
|
}
|
|
55
97
|
|
|
56
98
|
declare class RPCOutgoingEvent {
|
|
99
|
+
/**
|
|
100
|
+
* @param rpc - The `RPC` instance to send the event on.
|
|
101
|
+
* @param command - The command number to send.
|
|
102
|
+
*/
|
|
57
103
|
constructor(rpc: RPC, command: number)
|
|
58
104
|
}
|
|
59
105
|
|
|
@@ -64,19 +110,61 @@ interface RPCOutgoingRequest {
|
|
|
64
110
|
readonly sent: boolean
|
|
65
111
|
readonly received: boolean
|
|
66
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @param data - The payload to send: a `Buffer`, or a string encoded using `encoding`. Omit to
|
|
115
|
+
* send no data.
|
|
116
|
+
* @param encoding - The encoding used when `data` is a string (defaults to `utf8`).
|
|
117
|
+
* @throws {ALREADY_SENT} the request has already been sent.
|
|
118
|
+
*/
|
|
67
119
|
send(data?: Buffer | string | null, encoding?: BufferEncoding): void
|
|
68
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Await the reply from the remote end to the request. `encoding` can be defined for decoding the
|
|
123
|
+
* response `data` buffer back into a string.
|
|
124
|
+
* @param encoding - If given, decodes the reply payload to a string using this encoding; omit to
|
|
125
|
+
* receive the raw `Buffer`.
|
|
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.
|
|
128
|
+
* @throws {ALREADY_RECEIVED} a reply is already being received for this request.
|
|
129
|
+
*/
|
|
69
130
|
reply(encoding?: BufferEncoding): Promise<Buffer | string | null>
|
|
70
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Create a [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream for sending
|
|
134
|
+
* data with the request.
|
|
135
|
+
* @param opts - Options for the returned
|
|
136
|
+
* [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream.
|
|
137
|
+
* @throws {ALREADY_SENT} the request has already been sent.
|
|
138
|
+
*/
|
|
71
139
|
createRequestStream(opts?: WritableOptions): RPCOutgoingStream
|
|
140
|
+
/**
|
|
141
|
+
* Create a [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream for
|
|
142
|
+
* receiving data in reply to the request.
|
|
143
|
+
* @param opts - Options for the returned
|
|
144
|
+
* [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream.
|
|
145
|
+
* @throws {ALREADY_RECEIVED} the response has already been received.
|
|
146
|
+
*/
|
|
72
147
|
createResponseStream(opts?: ReadableOptions): RPCIncomingStream
|
|
73
148
|
}
|
|
74
149
|
|
|
75
150
|
declare class RPCOutgoingRequest {
|
|
151
|
+
/**
|
|
152
|
+
* @param rpc - The `RPC` instance to send the request on.
|
|
153
|
+
* @param id - The request id, used to correlate the reply with the request.
|
|
154
|
+
* @param command - The command number to send.
|
|
155
|
+
*/
|
|
76
156
|
constructor(rpc: RPC, id: number, command: number)
|
|
77
157
|
}
|
|
78
158
|
|
|
79
159
|
declare class RPCIncomingStream extends Readable {
|
|
160
|
+
/**
|
|
161
|
+
* @param rpc - The `RPC` instance the stream belongs to.
|
|
162
|
+
* @param request - The request the stream carries data for.
|
|
163
|
+
* @param type - Whether the stream carries the request body (`constants.type.REQUEST`) or the
|
|
164
|
+
* response body (`constants.type.RESPONSE`).
|
|
165
|
+
* @param opts - Options for the underlying
|
|
166
|
+
* [`Readable`](https://github.com/mafintosh/streamx#readable-stream) stream.
|
|
167
|
+
*/
|
|
80
168
|
constructor(
|
|
81
169
|
rpc: RPC,
|
|
82
170
|
request: RPCIncomingRequest | RPCOutgoingRequest,
|
|
@@ -86,6 +174,14 @@ declare class RPCIncomingStream extends Readable {
|
|
|
86
174
|
}
|
|
87
175
|
|
|
88
176
|
declare class RPCOutgoingStream extends Writable {
|
|
177
|
+
/**
|
|
178
|
+
* @param rpc - The `RPC` instance the stream belongs to.
|
|
179
|
+
* @param request - The request the stream carries data for.
|
|
180
|
+
* @param type - Whether the stream carries the request body (`constants.type.REQUEST`) or the
|
|
181
|
+
* response body (`constants.type.RESPONSE`).
|
|
182
|
+
* @param opts - Options for the underlying
|
|
183
|
+
* [`Writable`](https://github.com/mafintosh/streamx#writable-stream) stream.
|
|
184
|
+
*/
|
|
89
185
|
constructor(
|
|
90
186
|
rpc: RPC,
|
|
91
187
|
request: RPCIncomingRequest | RPCOutgoingRequest,
|
|
@@ -94,17 +190,94 @@ declare class RPCOutgoingStream extends Writable {
|
|
|
94
190
|
)
|
|
95
191
|
}
|
|
96
192
|
|
|
193
|
+
/** A [`compact-encoding`](https://github.com/holepunchto/compact-encoding) encoding, such as `c.raw`. */
|
|
194
|
+
interface RPCValueEncoding {
|
|
195
|
+
preencode(state: unknown, value: unknown): void
|
|
196
|
+
encode(state: unknown, value: unknown): void
|
|
197
|
+
decode(state: unknown): unknown
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
interface RPCCommandRouterOptions {
|
|
201
|
+
/** The encoding of request and response data for commands that set none (defaults to `c.raw`). */
|
|
202
|
+
valueEncoding?: RPCValueEncoding
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
interface RPCRespondOptions {
|
|
206
|
+
/** The encoding of both the request and the response data. */
|
|
207
|
+
valueEncoding?: RPCValueEncoding
|
|
208
|
+
/** The encoding to decode the request data with, or `null` to pass the raw buffer. */
|
|
209
|
+
requestEncoding?: RPCValueEncoding | null
|
|
210
|
+
/** The encoding to encode the response data with, or `null` to reply with the value as is. */
|
|
211
|
+
responseEncoding?: RPCValueEncoding | null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param req - The incoming request.
|
|
216
|
+
* @param data - The request data, decoded with the request encoding of the command.
|
|
217
|
+
* @returns the response data, encoded with the response encoding of the command and sent as the
|
|
218
|
+
* reply unless the handler has already replied.
|
|
219
|
+
*/
|
|
220
|
+
type RPCRequestHandler = (req: RPCIncomingRequest, data: any) => unknown
|
|
221
|
+
|
|
222
|
+
interface RPCCommandRouter {
|
|
223
|
+
/**
|
|
224
|
+
* Answer requests for `command` with `onrequest`, in place of any handler registered before.
|
|
225
|
+
* @param command - The command number to answer.
|
|
226
|
+
* @param opts - The encodings of the command.
|
|
227
|
+
* @param onrequest - The handler of the requests.
|
|
228
|
+
*/
|
|
229
|
+
respond(command: number, onrequest: RPCRequestHandler): void
|
|
230
|
+
respond(command: number, opts: RPCRespondOptions, onrequest: RPCRequestHandler): void
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
declare class RPCCommandRouter {
|
|
234
|
+
/**
|
|
235
|
+
* @param opts - The default encodings of the commands.
|
|
236
|
+
*/
|
|
237
|
+
constructor(opts?: RPCCommandRouterOptions)
|
|
238
|
+
}
|
|
239
|
+
|
|
97
240
|
interface RPC {
|
|
241
|
+
/**
|
|
242
|
+
* Whether there are no requests or responses currently in flight. Useful for determining when it
|
|
243
|
+
* is safe to tear down the underlying stream.
|
|
244
|
+
*/
|
|
98
245
|
readonly idle: boolean
|
|
99
246
|
|
|
247
|
+
/**
|
|
248
|
+
* @param command - A unique number identifying the event; the remote end differentiates events by
|
|
249
|
+
* it.
|
|
250
|
+
* @returns an `RPCOutgoingEvent`; call `send()` on it to dispatch the one-way event.
|
|
251
|
+
*/
|
|
100
252
|
event(command: number): RPCOutgoingEvent
|
|
253
|
+
/**
|
|
254
|
+
* @param command - A unique number identifying the request; the remote end differentiates
|
|
255
|
+
* requests by it.
|
|
256
|
+
* @returns an `RPCOutgoingRequest`; call `send()` on it to dispatch the request and `await
|
|
257
|
+
* reply()` for the response.
|
|
258
|
+
*/
|
|
101
259
|
request(command: number): RPCOutgoingRequest
|
|
102
260
|
}
|
|
103
261
|
|
|
104
262
|
declare class RPC {
|
|
105
|
-
|
|
263
|
+
/**
|
|
264
|
+
* @param stream - The duplex stream to frame RPC messages over, such as a pipe or socket.
|
|
265
|
+
* @param onrequest - Callback run for each incoming request or event, receiving an
|
|
266
|
+
* `RPCIncomingRequest` (or `RPCIncomingEvent`) to inspect and `reply()` to, or an
|
|
267
|
+
* `RPCCommandRouter` that answers each request with the handler of its command. Defaults to a
|
|
268
|
+
* no-op.
|
|
269
|
+
*/
|
|
270
|
+
constructor(
|
|
271
|
+
stream: Duplex,
|
|
272
|
+
onrequest?: ((req: RPCIncomingRequest) => void | Promise<void>) | RPCCommandRouter
|
|
273
|
+
)
|
|
106
274
|
}
|
|
107
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Create an RPC instance using a duplex `stream`. `onrequest` is an optional callback run when a
|
|
278
|
+
* remote request is received. This is where processing and responding to an RPC request happens.
|
|
279
|
+
* `onrequest` receives a `RPCIncomingRequest` as an argument.
|
|
280
|
+
*/
|
|
108
281
|
declare namespace RPC {
|
|
109
282
|
export {
|
|
110
283
|
type RPCIncomingEvent as IncomingEvent,
|
|
@@ -112,7 +285,12 @@ declare namespace RPC {
|
|
|
112
285
|
type RPCIncomingRequest as IncomingRequest,
|
|
113
286
|
type RPCOutgoingRequest as OutgoingRequest,
|
|
114
287
|
type RPCIncomingStream as IncomingStream,
|
|
115
|
-
type RPCOutgoingStream as OutgoingStream
|
|
288
|
+
type RPCOutgoingStream as OutgoingStream,
|
|
289
|
+
RPCCommandRouter as CommandRouter,
|
|
290
|
+
type RPCCommandRouterOptions as CommandRouterOptions,
|
|
291
|
+
type RPCRespondOptions as RespondOptions,
|
|
292
|
+
type RPCRequestHandler as RequestHandler,
|
|
293
|
+
type RPCValueEncoding as ValueEncoding
|
|
116
294
|
}
|
|
117
295
|
}
|
|
118
296
|
|
package/index.js
CHANGED
|
@@ -29,6 +29,9 @@ module.exports = exports = class RPC {
|
|
|
29
29
|
this._frame = -1
|
|
30
30
|
this._draining = []
|
|
31
31
|
|
|
32
|
+
this._closed = false
|
|
33
|
+
this._error = null
|
|
34
|
+
|
|
32
35
|
if (typeof onrequest === 'function') {
|
|
33
36
|
onrequest = onrequest.bind(this)
|
|
34
37
|
} else {
|
|
@@ -82,7 +85,7 @@ module.exports = exports = class RPC {
|
|
|
82
85
|
|
|
83
86
|
let flushed = this._stream.write(header)
|
|
84
87
|
|
|
85
|
-
if (message.data) flushed = this._stream.write(message.data)
|
|
88
|
+
if (message.data && m.hasData(message)) flushed = this._stream.write(message.data)
|
|
86
89
|
|
|
87
90
|
if (cb) {
|
|
88
91
|
if (flushed) cb(null)
|
|
@@ -101,6 +104,11 @@ module.exports = exports = class RPC {
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
_sendRequest(request, data = null) {
|
|
107
|
+
// Once the channel has been torn down there is nowhere left to send the
|
|
108
|
+
// request. Skip registering it so that it never leaks into the maps; the
|
|
109
|
+
// awaiting reply is rejected through `RPCOutgoingRequest.reply()` instead.
|
|
110
|
+
if (this._closed) return
|
|
111
|
+
|
|
104
112
|
this._outgoingRequests.set(request.id, request)
|
|
105
113
|
|
|
106
114
|
this._sendMessage({
|
|
@@ -114,21 +122,33 @@ module.exports = exports = class RPC {
|
|
|
114
122
|
|
|
115
123
|
_createRequestStream(request, isInitiator, opts) {
|
|
116
124
|
if (isInitiator) {
|
|
117
|
-
this._outgoingRequests.set(request.id, request)
|
|
118
|
-
|
|
119
125
|
request._requestStream = new OutgoingStream(this, request, t.REQUEST, opts)
|
|
120
126
|
|
|
127
|
+
// Once the channel has been torn down there is nowhere left to open the
|
|
128
|
+
// stream, so destroy it right away rather than leave it hanging.
|
|
129
|
+
if (this._closed) return request._requestStream.destroy(this._error)
|
|
130
|
+
|
|
131
|
+
this._outgoingRequests.set(request.id, request)
|
|
132
|
+
|
|
121
133
|
request._requestStream.on('close', () => this._gcOutgoingRequest(request))
|
|
122
134
|
} else {
|
|
123
|
-
this._incomingRequests.set(request.id, request)
|
|
124
|
-
|
|
125
135
|
request._requestStream = new IncomingStream(this, request, t.REQUEST, opts)
|
|
126
136
|
|
|
137
|
+
if (this._closed) return request._requestStream.destroy(this._error)
|
|
138
|
+
|
|
139
|
+
this._incomingRequests.set(request.id, request)
|
|
140
|
+
|
|
127
141
|
request._requestStream.on('close', () => this._incomingRequests.delete(request.id))
|
|
128
142
|
}
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
_sendResponse(request, data) {
|
|
146
|
+
// Once the channel has been torn down there is nowhere left to send the
|
|
147
|
+
// response, so drop it rather than encode and write a message that would
|
|
148
|
+
// only be discarded. The reply is fire-and-forget, so there is nothing to
|
|
149
|
+
// settle.
|
|
150
|
+
if (this._closed) return
|
|
151
|
+
|
|
132
152
|
this._sendMessage({
|
|
133
153
|
type: t.RESPONSE,
|
|
134
154
|
id: request.id,
|
|
@@ -140,16 +160,22 @@ module.exports = exports = class RPC {
|
|
|
140
160
|
|
|
141
161
|
_createResponseStream(request, isInitiator, opts) {
|
|
142
162
|
if (isInitiator) {
|
|
143
|
-
this._outgoingResponses.set(request.id, request)
|
|
144
|
-
|
|
145
163
|
request._responseStream = new OutgoingStream(this, request, t.RESPONSE, opts)
|
|
146
164
|
|
|
165
|
+
// Once the channel has been torn down there is nowhere left to open the
|
|
166
|
+
// stream, so destroy it right away rather than leave it hanging.
|
|
167
|
+
if (this._closed) return request._responseStream.destroy(this._error)
|
|
168
|
+
|
|
169
|
+
this._outgoingResponses.set(request.id, request)
|
|
170
|
+
|
|
147
171
|
request._responseStream.on('close', () => this._outgoingResponses.delete(request.id))
|
|
148
172
|
} else {
|
|
149
|
-
this._incomingResponses.set(request.id, request)
|
|
150
|
-
|
|
151
173
|
request._responseStream = new IncomingStream(this, request, t.RESPONSE, opts)
|
|
152
174
|
|
|
175
|
+
if (this._closed) return request._responseStream.destroy(this._error)
|
|
176
|
+
|
|
177
|
+
this._incomingResponses.set(request.id, request)
|
|
178
|
+
|
|
153
179
|
request._responseStream.on('close', () => {
|
|
154
180
|
this._incomingResponses.delete(request.id)
|
|
155
181
|
|
|
@@ -192,6 +218,11 @@ module.exports = exports = class RPC {
|
|
|
192
218
|
}
|
|
193
219
|
|
|
194
220
|
_teardown(err) {
|
|
221
|
+
// Mark the channel as closed so that any calls made from here on are
|
|
222
|
+
// rejected or destroyed immediately rather than left hanging.
|
|
223
|
+
this._closed = true
|
|
224
|
+
this._error = err
|
|
225
|
+
|
|
195
226
|
this._ondrain(err)
|
|
196
227
|
|
|
197
228
|
// Reject any request awaiting a plain reply. Requests whose response is
|
|
@@ -242,19 +273,26 @@ module.exports = exports = class RPC {
|
|
|
242
273
|
}
|
|
243
274
|
|
|
244
275
|
_onbeforeframe() {
|
|
245
|
-
|
|
276
|
+
while (this._buffered >= 4) {
|
|
277
|
+
const buffer = this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
|
|
246
278
|
|
|
247
|
-
|
|
279
|
+
this._buffer = [buffer]
|
|
280
|
+
this._frame = 4 + c.uint32.decode(c.state(0, 4, buffer))
|
|
248
281
|
|
|
249
|
-
|
|
250
|
-
this._frame = 4 + c.uint32.decode(c.state(0, 4, buffer))
|
|
282
|
+
if (this._buffered < this._frame) return
|
|
251
283
|
|
|
252
|
-
|
|
284
|
+
this._onframe()
|
|
285
|
+
}
|
|
253
286
|
}
|
|
254
287
|
|
|
255
288
|
_onafterframe() {
|
|
256
289
|
if (this._buffered < this._frame) return
|
|
257
290
|
|
|
291
|
+
this._onframe()
|
|
292
|
+
this._onbeforeframe()
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
_onframe() {
|
|
258
296
|
const buffer = this._buffer.length === 1 ? this._buffer[0] : b4a.concat(this._buffer)
|
|
259
297
|
|
|
260
298
|
const frame = this._frame
|
|
@@ -264,7 +302,6 @@ module.exports = exports = class RPC {
|
|
|
264
302
|
this._frame = -1
|
|
265
303
|
|
|
266
304
|
this._onmessage(buffer.subarray(0, frame))
|
|
267
|
-
this._onbeforeframe()
|
|
268
305
|
}
|
|
269
306
|
|
|
270
307
|
async _onmessage(buffer) {
|
package/lib/messages.js
CHANGED
|
@@ -21,19 +21,31 @@ const error = {
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function hasData(m) {
|
|
25
|
+
switch (m.type) {
|
|
26
|
+
case t.REQUEST:
|
|
27
|
+
return m.stream === 0
|
|
28
|
+
case t.RESPONSE:
|
|
29
|
+
return !m.error && m.stream === 0
|
|
30
|
+
case t.STREAM:
|
|
31
|
+
return (m.stream & s.ERROR) === 0 && (m.stream & s.DATA) !== 0
|
|
32
|
+
default:
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.hasData = hasData
|
|
38
|
+
|
|
24
39
|
exports.header = {
|
|
25
40
|
preencode(state, m) {
|
|
26
41
|
c.uint32.preencode(state, 0) // Frame
|
|
27
42
|
c.uint.preencode(state, m.type)
|
|
28
43
|
c.uint.preencode(state, m.id)
|
|
29
44
|
|
|
30
|
-
let hasData = false
|
|
31
|
-
|
|
32
45
|
switch (m.type) {
|
|
33
46
|
case t.REQUEST:
|
|
34
47
|
c.uint.preencode(state, m.command)
|
|
35
48
|
c.uint.preencode(state, m.stream)
|
|
36
|
-
if (m.stream === 0) hasData = true
|
|
37
49
|
break
|
|
38
50
|
|
|
39
51
|
case t.RESPONSE:
|
|
@@ -41,18 +53,16 @@ exports.header = {
|
|
|
41
53
|
c.uint.preencode(state, m.stream)
|
|
42
54
|
|
|
43
55
|
if (m.error) error.preencode(state, m.error)
|
|
44
|
-
else if (m.stream === 0) hasData = true
|
|
45
56
|
break
|
|
46
57
|
|
|
47
58
|
case t.STREAM:
|
|
48
59
|
c.uint.preencode(state, m.stream)
|
|
49
60
|
|
|
50
61
|
if (m.stream & s.ERROR) error.preencode(state, m.error)
|
|
51
|
-
else if (m.stream & s.DATA) hasData = true
|
|
52
62
|
break
|
|
53
63
|
}
|
|
54
64
|
|
|
55
|
-
if (hasData) c.uint.preencode(state, m.data ? m.data.byteLength : 0)
|
|
65
|
+
if (hasData(m)) c.uint.preencode(state, m.data ? m.data.byteLength : 0)
|
|
56
66
|
},
|
|
57
67
|
encode(state, m) {
|
|
58
68
|
const frame = state.start
|
|
@@ -64,13 +74,10 @@ exports.header = {
|
|
|
64
74
|
c.uint.encode(state, m.type)
|
|
65
75
|
c.uint.encode(state, m.id)
|
|
66
76
|
|
|
67
|
-
let hasData = false
|
|
68
|
-
|
|
69
77
|
switch (m.type) {
|
|
70
78
|
case t.REQUEST:
|
|
71
79
|
c.uint.encode(state, m.command)
|
|
72
80
|
c.uint.encode(state, m.stream)
|
|
73
|
-
if (m.stream === 0) hasData = true
|
|
74
81
|
break
|
|
75
82
|
|
|
76
83
|
case t.RESPONSE:
|
|
@@ -78,24 +85,24 @@ exports.header = {
|
|
|
78
85
|
c.uint.encode(state, m.stream)
|
|
79
86
|
|
|
80
87
|
if (m.error) error.encode(state, m.error)
|
|
81
|
-
else if (m.stream === 0) hasData = true
|
|
82
88
|
break
|
|
83
89
|
|
|
84
90
|
case t.STREAM:
|
|
85
91
|
c.uint.encode(state, m.stream)
|
|
86
92
|
|
|
87
93
|
if (m.stream & s.ERROR) error.encode(state, m.error)
|
|
88
|
-
else if (m.stream & s.DATA) hasData = true
|
|
89
94
|
break
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
|
|
97
|
+
const data = hasData(m)
|
|
98
|
+
|
|
99
|
+
if (data) c.uint.encode(state, m.data ? m.data.byteLength : 0)
|
|
93
100
|
|
|
94
101
|
const end = state.start
|
|
95
102
|
|
|
96
103
|
state.start = frame
|
|
97
104
|
|
|
98
|
-
c.uint32.encode(state, end - start + (
|
|
105
|
+
c.uint32.encode(state, end - start + (data && m.data ? m.data.byteLength : 0))
|
|
99
106
|
|
|
100
107
|
state.start = end
|
|
101
108
|
}
|
package/lib/outgoing-request.js
CHANGED
|
@@ -46,6 +46,12 @@ module.exports = class RPCOutgoingRequest {
|
|
|
46
46
|
|
|
47
47
|
this.received = true
|
|
48
48
|
|
|
49
|
+
// If the channel was torn down before a response arrived there is nothing
|
|
50
|
+
// left to await, so reject with the teardown error rather than hang. This
|
|
51
|
+
// covers both requests sent after teardown and requests still awaiting a
|
|
52
|
+
// reply when teardown happens.
|
|
53
|
+
if (this.rpc._closed && !this._responded) this._reject(this.rpc._error)
|
|
54
|
+
|
|
49
55
|
return encoding ? this._promise.then((data) => c.decode(encoding, data)) : this._promise
|
|
50
56
|
}
|
|
51
57
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-rpc",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
4
4
|
"description": "librpc ABI compatible RPC for Bare",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"bare-buffer": "^3.0.1",
|
|
46
46
|
"bare-ipc": "^1.1.0",
|
|
47
|
+
"bare-rpc-test": "^0.1.0",
|
|
47
48
|
"brittle": "^3.2.1",
|
|
48
49
|
"lunte": "^1.7.0",
|
|
49
50
|
"prettier": "^3.4.2",
|