@dxos/rpc 2.33.9-dev.7c8c6a09 → 2.33.9-dev.9246a07b

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/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,9 +5,10 @@
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
+ import { exponentialBackoffInterval } from '@dxos/util';
11
12
 
12
13
  import { RpcClosedError, RpcNotOpenError, SerializedRpcError } from './errors';
13
14
  import { schema } from './proto/gen';
@@ -142,7 +143,12 @@ export class RpcPeer {
142
143
  if (decoded.request) {
143
144
  if (!this._open) {
144
145
  log('Received request while not open.');
145
- 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
+ });
146
152
  return;
147
153
  }
148
154
 
@@ -239,20 +245,18 @@ export class RpcPeer {
239
245
  // Here we're attaching a dummy handler that will not interfere with error handling to avoid that warning.
240
246
  promise.catch(() => {});
241
247
 
242
- await this._sendMessage({
248
+ void this._sendMessage({
243
249
  request: {
244
250
  id,
245
251
  method,
246
- payload: request
252
+ payload: request,
253
+ stream: false
247
254
  }
248
255
  });
249
256
 
250
- const timeoutPromise = sleep(this._options.timeout ?? DEFAULT_TIMEOUT).then(() => Promise.reject(new Error('Timeout')));
251
- timeoutPromise.catch(() => {}); // Mute the promise.
252
-
253
257
  let response: Response;
254
258
  try {
255
- 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}`))]);
256
260
  } catch (err) {
257
261
  if (err instanceof RpcClosedError) {
258
262
  // Rethrow the error here to have the correct stack-trace.
@@ -285,9 +289,11 @@ export class RpcPeer {
285
289
 
286
290
  const id = this._nextId++;
287
291
 
288
- return new Stream(({ next, close }) => {
292
+ return new Stream(({ ready, next, close }) => {
289
293
  const onResponse = (response: Response) => {
290
- if (response.close) {
294
+ if (response.streamReady) {
295
+ ready();
296
+ } else if (response.close) {
291
297
  close();
292
298
  } else if (response.error) {
293
299
  assert(response.error.name);
@@ -361,6 +367,12 @@ export class RpcPeer {
361
367
  assert(req.payload);
362
368
  assert(req.method);
363
369
  const responseStream = this._options.streamHandler(req.method, req.payload);
370
+ responseStream.onReady(() => {
371
+ callback({
372
+ id: req.id,
373
+ streamReady: true
374
+ });
375
+ });
364
376
  responseStream.subscribe(
365
377
  msg => {
366
378
  callback({
@@ -411,16 +423,22 @@ const encodeError = (err: any): ErrorResponse => {
411
423
  };
412
424
 
413
425
  /**
414
- * Runs the callback in an exponentially increasing interval
415
- * @returns Callback to clear the interval.
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.
416
429
  */
417
- const exponentialBackoffInterval = (cb: () => void, initialInterval: number): () => void => {
418
- let interval = initialInterval;
419
- const repeat = () => {
420
- cb();
421
- interval *= 2;
422
- timeoutId = setTimeout(repeat, interval);
423
- };
424
- let timeoutId = setTimeout(repeat, interval);
425
- return () => clearTimeout(timeoutId);
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;
426
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' } },