@dxos/rpc 2.33.9-dev.d9963f00 → 2.33.9-dev.eb69ac10

Sign up to get free protection for your applications and to get access to all the features.
package/src/rpc.test.ts CHANGED
@@ -13,17 +13,22 @@ import { Any } from './proto/gen/google/protobuf';
13
13
  import { RpcPeer } from './rpc';
14
14
  import { createLinkedPorts } from './testutil';
15
15
 
16
+ const createPayload = (value = ''): Any => ({
17
+ type_url: '',
18
+ value: Buffer.from(value)
19
+ });
20
+
16
21
  describe('RpcPeer', () => {
17
22
  describe('handshake', () => {
18
23
  test('can open', async () => {
19
24
  const [alicePort, bobPort] = createLinkedPorts();
20
25
 
21
26
  const alice = new RpcPeer({
22
- messageHandler: async msg => ({}),
27
+ messageHandler: async msg => createPayload(),
23
28
  port: alicePort
24
29
  });
25
30
  const bob = new RpcPeer({
26
- messageHandler: async msg => ({}),
31
+ messageHandler: async msg => createPayload(),
27
32
  port: bobPort
28
33
  });
29
34
 
@@ -36,11 +41,11 @@ describe('RpcPeer', () => {
36
41
  test('open waits for the other peer to call open', async () => {
37
42
  const [alicePort, bobPort] = createLinkedPorts();
38
43
  const alice: RpcPeer = new RpcPeer({
39
- messageHandler: async msg => ({}),
44
+ messageHandler: async msg => createPayload(),
40
45
  port: alicePort
41
46
  });
42
47
  const bob = new RpcPeer({
43
- messageHandler: async msg => ({}),
48
+ messageHandler: async msg => createPayload(),
44
49
  port: bobPort
45
50
  });
46
51
 
@@ -64,7 +69,7 @@ describe('RpcPeer', () => {
64
69
  const [alicePort, bobPort] = createLinkedPorts();
65
70
 
66
71
  const alice: RpcPeer = new RpcPeer({
67
- messageHandler: async msg => ({}),
72
+ messageHandler: async msg => createPayload(),
68
73
  port: alicePort
69
74
  });
70
75
  const aliceOpen = alice.open();
@@ -72,7 +77,7 @@ describe('RpcPeer', () => {
72
77
  await sleep(5);
73
78
 
74
79
  const bob = new RpcPeer({
75
- messageHandler: async msg => ({}),
80
+ messageHandler: async msg => createPayload(),
76
81
  port: bobPort
77
82
  });
78
83
 
@@ -88,7 +93,7 @@ describe('RpcPeer', () => {
88
93
  let portOpen = false;
89
94
 
90
95
  const alice: RpcPeer = new RpcPeer({
91
- messageHandler: async msg => ({}),
96
+ messageHandler: async msg => createPayload(),
92
97
  port: {
93
98
  send: msg => {
94
99
  portOpen && alicePort.send(msg);
@@ -98,7 +103,7 @@ describe('RpcPeer', () => {
98
103
  });
99
104
 
100
105
  const bob = new RpcPeer({
101
- messageHandler: async msg => ({}),
106
+ messageHandler: async msg => createPayload(),
102
107
  port: {
103
108
  send: msg => {
104
109
  portOpen && bobPort.send(msg);
@@ -123,7 +128,7 @@ describe('RpcPeer', () => {
123
128
  const [alicePort, bobPort] = createLinkedPorts();
124
129
 
125
130
  const alice: RpcPeer = new RpcPeer({
126
- messageHandler: async msg => ({}),
131
+ messageHandler: async msg => createPayload(),
127
132
  port: {
128
133
  send: msg => { },
129
134
  subscribe: alicePort.subscribe
@@ -131,7 +136,7 @@ describe('RpcPeer', () => {
131
136
  });
132
137
 
133
138
  const bob = new RpcPeer({
134
- messageHandler: async msg => ({}),
139
+ messageHandler: async msg => createPayload(),
135
140
  port: bobPort
136
141
  });
137
142
 
@@ -160,12 +165,12 @@ describe('RpcPeer', () => {
160
165
  messageHandler: async (method, msg) => {
161
166
  expect(method).toEqual('method');
162
167
  expect(msg.value).toEqual(Buffer.from('request'));
163
- return { value: Buffer.from('response') };
168
+ return createPayload('response');
164
169
  },
165
170
  port: alicePort
166
171
  });
167
172
  const bob = new RpcPeer({
168
- messageHandler: async (method, msg) => ({}),
173
+ messageHandler: async (method, msg) => createPayload(),
169
174
  port: bobPort
170
175
  });
171
176
 
@@ -174,8 +179,8 @@ describe('RpcPeer', () => {
174
179
  bob.open()
175
180
  ]);
176
181
 
177
- const response = await bob.call('method', { value: Buffer.from('request') });
178
- expect(response).toEqual({ value: Buffer.from('response') });
182
+ const response = await bob.call('method', createPayload('request'));
183
+ expect(response).toEqual(createPayload('response'));
179
184
  });
180
185
 
181
186
  test('can send multiple requests', async () => {
@@ -197,7 +202,7 @@ describe('RpcPeer', () => {
197
202
  port: alicePort
198
203
  });
199
204
  const bob = new RpcPeer({
200
- messageHandler: async msg => ({}),
205
+ messageHandler: async msg => createPayload(),
201
206
  port: bobPort
202
207
  });
203
208
 
@@ -206,14 +211,14 @@ describe('RpcPeer', () => {
206
211
  bob.open()
207
212
  ]);
208
213
 
209
- expect((await bob.call('method', { value: Buffer.from('request') })).value).toEqual(Buffer.from('request'));
214
+ expect((await bob.call('method', createPayload('request'))).value).toEqual(Buffer.from('request'));
210
215
 
211
- const parallel1 = bob.call('method', { value: Buffer.from('p1') });
212
- const parallel2 = bob.call('method', { value: Buffer.from('p2') });
213
- const error = bob.call('method', { value: Buffer.from('error') });
216
+ const parallel1 = bob.call('method', createPayload('p1'));
217
+ const parallel2 = bob.call('method', createPayload('p2'));
218
+ const error = bob.call('method', createPayload('error'));
214
219
 
215
- await expect(await parallel1).toEqual({ value: Buffer.from('p1') });
216
- await expect(await parallel2).toEqual({ value: Buffer.from('p2') });
220
+ await expect(await parallel1).toEqual(createPayload('p1'));
221
+ await expect(await parallel2).toEqual(createPayload('p2'));
217
222
  await expect(error).toBeRejected();
218
223
  });
219
224
 
@@ -232,7 +237,7 @@ describe('RpcPeer', () => {
232
237
  port: alicePort
233
238
  });
234
239
  const bob = new RpcPeer({
235
- messageHandler: async msg => ({}),
240
+ messageHandler: async msg => createPayload(),
236
241
  port: bobPort
237
242
  });
238
243
 
@@ -243,7 +248,7 @@ describe('RpcPeer', () => {
243
248
 
244
249
  let error!: Error;
245
250
  try {
246
- await bob.call('RpcMethodName', { value: Buffer.from('request') });
251
+ await bob.call('RpcMethodName', createPayload('request'));
247
252
  } catch (err: any) {
248
253
  error = err;
249
254
  }
@@ -266,7 +271,7 @@ describe('RpcPeer', () => {
266
271
  port: alicePort
267
272
  });
268
273
  const bob = new RpcPeer({
269
- messageHandler: async msg => ({}),
274
+ messageHandler: async msg => createPayload(),
270
275
  port: bobPort
271
276
  });
272
277
 
@@ -275,7 +280,7 @@ describe('RpcPeer', () => {
275
280
  bob.open()
276
281
  ]);
277
282
 
278
- const req = bob.call('method', { value: Buffer.from('request') });
283
+ const req = bob.call('method', createPayload('request'));
279
284
  bob.close();
280
285
 
281
286
  await expect(req).toBeRejected();
@@ -293,7 +298,7 @@ describe('RpcPeer', () => {
293
298
  port: alicePort
294
299
  });
295
300
  const bob = new RpcPeer({
296
- messageHandler: async msg => ({}),
301
+ messageHandler: async msg => createPayload(),
297
302
  port: bobPort,
298
303
  timeout: 50
299
304
  });
@@ -304,8 +309,34 @@ describe('RpcPeer', () => {
304
309
  ]);
305
310
 
306
311
  alice.close();
307
- const req = bob.call('method', { value: Buffer.from('request') });
312
+ const req = bob.call('method', createPayload('request'));
313
+
314
+ await expect(req).toBeRejected();
315
+ });
316
+
317
+ test('requests failing on timeout', async () => {
318
+ const [alicePort, bobPort] = createLinkedPorts();
319
+
320
+ const alice: RpcPeer = new RpcPeer({
321
+ messageHandler: async (method, msg) => {
322
+ expect(method).toEqual('method');
323
+ await sleep(50);
324
+ return msg;
325
+ },
326
+ port: alicePort
327
+ });
328
+ const bob = new RpcPeer({
329
+ messageHandler: async msg => createPayload(),
330
+ port: bobPort,
331
+ timeout: 10
332
+ });
308
333
 
334
+ await Promise.all([
335
+ alice.open(),
336
+ bob.open()
337
+ ]);
338
+
339
+ const req = bob.call('method', createPayload('request'));
309
340
  await expect(req).toBeRejected();
310
341
  });
311
342
 
@@ -316,20 +347,20 @@ describe('RpcPeer', () => {
316
347
  const [alicePort, bobPort] = createLinkedPorts();
317
348
 
318
349
  const alice = new RpcPeer({
319
- messageHandler: async msg => ({}),
350
+ messageHandler: async msg => createPayload(),
320
351
  streamHandler: (method, msg) => {
321
352
  expect(method).toEqual('method');
322
353
  expect(msg.value!).toEqual(Buffer.from('request'));
323
354
  return new Stream<Any>(({ next, close }) => {
324
- next({ value: Buffer.from('res1') });
325
- next({ value: Buffer.from('res2') });
355
+ next(createPayload('res1'));
356
+ next(createPayload('res2'));
326
357
  close();
327
358
  });
328
359
  },
329
360
  port: alicePort
330
361
  });
331
362
  const bob = new RpcPeer({
332
- messageHandler: async msg => ({}),
363
+ messageHandler: async msg => createPayload(),
333
364
  port: bobPort
334
365
  });
335
366
 
@@ -338,12 +369,13 @@ describe('RpcPeer', () => {
338
369
  bob.open()
339
370
  ]);
340
371
 
341
- const stream = await bob.callStream('method', { value: Buffer.from('request') });
372
+ const stream = await bob.callStream('method', createPayload('request'));
342
373
  expect(stream).toBeA(Stream);
343
374
 
344
375
  expect(await Stream.consume(stream)).toEqual([
345
- { data: { value: Buffer.from('res1') } },
346
- { data: { value: Buffer.from('res2') } },
376
+ { ready: true },
377
+ { data: createPayload('res1') },
378
+ { data: createPayload('res2') },
347
379
  { closed: true }
348
380
  ]);
349
381
  });
@@ -352,7 +384,7 @@ describe('RpcPeer', () => {
352
384
  const [alicePort, bobPort] = createLinkedPorts();
353
385
 
354
386
  const alice = new RpcPeer({
355
- messageHandler: async msg => ({}),
387
+ messageHandler: async msg => createPayload(),
356
388
  streamHandler: (method, msg) => {
357
389
  expect(method).toEqual('method');
358
390
  expect(msg.value).toEqual(Buffer.from('request'));
@@ -363,7 +395,7 @@ describe('RpcPeer', () => {
363
395
  port: alicePort
364
396
  });
365
397
  const bob = new RpcPeer({
366
- messageHandler: async msg => ({}),
398
+ messageHandler: async msg => createPayload(),
367
399
  port: bobPort
368
400
  });
369
401
 
@@ -372,7 +404,7 @@ describe('RpcPeer', () => {
372
404
  bob.open()
373
405
  ]);
374
406
 
375
- const stream = await bob.callStream('method', { value: Buffer.from('request') });
407
+ const stream = await bob.callStream('method', createPayload('request'));
376
408
  expect(stream).toBeA(Stream);
377
409
 
378
410
  const msgs = await Stream.consume(stream);
@@ -388,7 +420,7 @@ describe('RpcPeer', () => {
388
420
 
389
421
  let closeCalled = false;
390
422
  const alice = new RpcPeer({
391
- messageHandler: async msg => ({}),
423
+ messageHandler: async msg => createPayload(),
392
424
  streamHandler: (method, msg) => new Stream<Any>(({ next, close }) => () => {
393
425
  closeCalled = true;
394
426
  }),
@@ -396,7 +428,7 @@ describe('RpcPeer', () => {
396
428
  });
397
429
 
398
430
  const bob = new RpcPeer({
399
- messageHandler: async msg => ({}),
431
+ messageHandler: async msg => createPayload(),
400
432
  port: bobPort
401
433
  });
402
434
 
@@ -405,13 +437,49 @@ describe('RpcPeer', () => {
405
437
  bob.open()
406
438
  ]);
407
439
 
408
- const stream = bob.callStream('method', { value: Buffer.from('request') });
440
+ const stream = bob.callStream('method', createPayload('request'));
409
441
  stream.close();
410
442
 
411
443
  await sleep(1);
412
444
 
413
445
  expect(closeCalled).toEqual(true);
414
446
  });
447
+
448
+ test('reports stream being ready', async () => {
449
+ const [alicePort, bobPort] = createLinkedPorts();
450
+
451
+ const alice = new RpcPeer({
452
+ messageHandler: async msg => createPayload(),
453
+ streamHandler: (method, msg) => {
454
+ expect(method).toEqual('method');
455
+ expect(msg.value!).toEqual(Buffer.from('request'));
456
+ return new Stream<Any>(({ ready, close }) => {
457
+ ready();
458
+ close();
459
+ });
460
+ },
461
+ port: alicePort
462
+ });
463
+ const bob = new RpcPeer({
464
+ messageHandler: async msg => createPayload(),
465
+ port: bobPort
466
+ });
467
+
468
+ await Promise.all([
469
+ alice.open(),
470
+ bob.open()
471
+ ]);
472
+
473
+ const stream = await bob.callStream('method', createPayload('request'));
474
+ expect(stream).toBeA(Stream);
475
+
476
+ await stream.waitUntilReady();
477
+
478
+ expect(await Stream.consume(stream)).toEqual([
479
+ { ready: true },
480
+ { closed: true }
481
+ ]);
482
+ });
415
483
  });
416
484
 
417
485
  describe('with disabled handshake', () => {
@@ -419,7 +487,7 @@ describe('RpcPeer', () => {
419
487
  const [alicePort] = createLinkedPorts();
420
488
 
421
489
  const alice = new RpcPeer({
422
- messageHandler: async msg => ({}),
490
+ messageHandler: async msg => createPayload(),
423
491
  port: alicePort,
424
492
  noHandshake: true
425
493
  });
@@ -434,13 +502,13 @@ describe('RpcPeer', () => {
434
502
  messageHandler: async (method, msg) => {
435
503
  expect(method).toEqual('method');
436
504
  expect(msg.value).toEqual(Buffer.from('request'));
437
- return { value: Buffer.from('response') };
505
+ return createPayload('response');
438
506
  },
439
507
  port: alicePort,
440
508
  noHandshake: true
441
509
  });
442
510
  const bob = new RpcPeer({
443
- messageHandler: async (method, msg) => ({}),
511
+ messageHandler: async (method, msg) => createPayload(),
444
512
  port: bobPort,
445
513
  noHandshake: true
446
514
  });
@@ -448,8 +516,8 @@ describe('RpcPeer', () => {
448
516
  await alice.open();
449
517
  await bob.open();
450
518
 
451
- const response = await bob.call('method', { value: Buffer.from('request') });
452
- expect(response).toEqual({ value: Buffer.from('response') });
519
+ const response = await bob.call('method', createPayload('request'));
520
+ expect(response).toEqual(createPayload('response'));
453
521
  });
454
522
  });
455
523
  });
package/src/rpc.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import debug from 'debug';
6
6
  import assert from 'node:assert';
7
7
 
8
- import { sleep, synchronized, Trigger } from '@dxos/async';
8
+ import { synchronized, Trigger } from '@dxos/async';
9
9
  import { Stream } from '@dxos/codec-protobuf';
10
10
  import { StackTrace } from '@dxos/debug';
11
11
  import { exponentialBackoffInterval } from '@dxos/util';
@@ -143,7 +143,12 @@ export class RpcPeer {
143
143
  if (decoded.request) {
144
144
  if (!this._open) {
145
145
  log('Received request while not open.');
146
- await this._sendMessage({ response: { error: encodeError(new RpcClosedError()) } });
146
+ await this._sendMessage({
147
+ response: {
148
+ id: decoded.request.id,
149
+ error: encodeError(new RpcClosedError())
150
+ }
151
+ });
147
152
  return;
148
153
  }
149
154
 
@@ -240,20 +245,18 @@ export class RpcPeer {
240
245
  // Here we're attaching a dummy handler that will not interfere with error handling to avoid that warning.
241
246
  promise.catch(() => {});
242
247
 
243
- await this._sendMessage({
248
+ void this._sendMessage({
244
249
  request: {
245
250
  id,
246
251
  method,
247
- payload: request
252
+ payload: request,
253
+ stream: false
248
254
  }
249
255
  });
250
256
 
251
- const timeoutPromise = sleep(this._options.timeout ?? DEFAULT_TIMEOUT).then(() => Promise.reject(new Error('Timeout')));
252
- timeoutPromise.catch(() => {}); // Mute the promise.
253
-
254
257
  let response: Response;
255
258
  try {
256
- response = await Promise.race([promise, timeoutPromise]);
259
+ response = await Promise.race([promise, createTimeoutPromise(this._options.timeout ?? DEFAULT_TIMEOUT, new Error(`RPC call timed out: ${method}`))]);
257
260
  } catch (err) {
258
261
  if (err instanceof RpcClosedError) {
259
262
  // Rethrow the error here to have the correct stack-trace.
@@ -286,9 +289,11 @@ export class RpcPeer {
286
289
 
287
290
  const id = this._nextId++;
288
291
 
289
- return new Stream(({ next, close }) => {
292
+ return new Stream(({ ready, next, close }) => {
290
293
  const onResponse = (response: Response) => {
291
- if (response.close) {
294
+ if (response.streamReady) {
295
+ ready();
296
+ } else if (response.close) {
292
297
  close();
293
298
  } else if (response.error) {
294
299
  assert(response.error.name);
@@ -362,6 +367,12 @@ export class RpcPeer {
362
367
  assert(req.payload);
363
368
  assert(req.method);
364
369
  const responseStream = this._options.streamHandler(req.method, req.payload);
370
+ responseStream.onReady(() => {
371
+ callback({
372
+ id: req.id,
373
+ streamReady: true
374
+ });
375
+ });
365
376
  responseStream.subscribe(
366
377
  msg => {
367
378
  callback({
@@ -410,3 +421,24 @@ const encodeError = (err: any): ErrorResponse => {
410
421
  };
411
422
  }
412
423
  };
424
+
425
+ /**
426
+ * Creates a promise that will be rejected after a certain timeout.
427
+ * The promise will never cause unhandledPromiseRejection.
428
+ * The timeout will not block the Node.JS process from exiting.
429
+ */
430
+ const createTimeoutPromise = (timeout: number, error: Error) => {
431
+ const timeoutPromise = new Promise<any>((resolve, reject) => {
432
+ const timeoutId = setTimeout(
433
+ () => reject(error),
434
+ timeout
435
+ );
436
+
437
+ // `unref` prevents the timeout from blocking Node.JS process from exiting. Not available in browsers.
438
+ if ('unref' in timeoutId) {
439
+ timeoutId.unref();
440
+ }
441
+ });
442
+ timeoutPromise.catch(() => {}); // Mute the promise.
443
+ return timeoutPromise;
444
+ };
@@ -161,6 +161,7 @@ describe('Protobuf service', () => {
161
161
  const stream = client.rpc.testCall({ data: 'requestData' });
162
162
 
163
163
  expect(await Stream.consume(stream)).toEqual([
164
+ { ready: true },
164
165
  { data: { data: 'foo' } },
165
166
  { data: { data: 'bar' } },
166
167
  { data: { data: 'baz' } },