@dxos/teleport 0.1.54 → 0.1.55-main.0618d91

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.
Files changed (39) hide show
  1. package/dist/lib/browser/{chunk-7TGKNWZQ.mjs → chunk-GGVQ27WJ.mjs} +606 -138
  2. package/dist/lib/browser/chunk-GGVQ27WJ.mjs.map +7 -0
  3. package/dist/lib/browser/index.mjs +10 -200
  4. package/dist/lib/browser/index.mjs.map +4 -4
  5. package/dist/lib/browser/meta.json +1 -1
  6. package/dist/lib/browser/testing/index.mjs +7 -128
  7. package/dist/lib/browser/testing/index.mjs.map +4 -4
  8. package/dist/lib/node/index.cjs +434 -165
  9. package/dist/lib/node/index.cjs.map +4 -4
  10. package/dist/lib/node/meta.json +1 -1
  11. package/dist/lib/node/testing/index.cjs +484 -147
  12. package/dist/lib/node/testing/index.cjs.map +4 -4
  13. package/dist/types/src/index.d.ts +1 -2
  14. package/dist/types/src/index.d.ts.map +1 -1
  15. package/dist/types/src/muxing/balancer.d.ts +22 -8
  16. package/dist/types/src/muxing/balancer.d.ts.map +1 -1
  17. package/dist/types/src/muxing/balancer.test.d.ts +2 -0
  18. package/dist/types/src/muxing/balancer.test.d.ts.map +1 -0
  19. package/dist/types/src/muxing/framer.d.ts +5 -2
  20. package/dist/types/src/muxing/framer.d.ts.map +1 -1
  21. package/dist/types/src/muxing/muxer.d.ts +8 -4
  22. package/dist/types/src/muxing/muxer.d.ts.map +1 -1
  23. package/dist/types/src/teleport.d.ts +2 -3
  24. package/dist/types/src/teleport.d.ts.map +1 -1
  25. package/dist/types/src/testing/index.d.ts +1 -0
  26. package/dist/types/src/testing/index.d.ts.map +1 -1
  27. package/dist/types/src/testing/test-extension-with-streams.d.ts +4 -4
  28. package/dist/types/src/testing/test-extension-with-streams.d.ts.map +1 -1
  29. package/package.json +11 -11
  30. package/src/index.ts +1 -2
  31. package/src/muxing/balancer.test.ts +50 -0
  32. package/src/muxing/balancer.ts +127 -34
  33. package/src/muxing/framer.test.ts +1 -15
  34. package/src/muxing/framer.ts +43 -35
  35. package/src/muxing/muxer.ts +35 -24
  36. package/src/teleport.ts +22 -15
  37. package/src/testing/index.ts +1 -0
  38. package/src/testing/test-extension-with-streams.ts +31 -12
  39. package/dist/lib/browser/chunk-7TGKNWZQ.mjs.map +0 -7
@@ -1,18 +1,39 @@
1
1
  import "@dxos/node-std/globals";
2
2
 
3
+ // packages/core/mesh/teleport/src/testing/test-builder.ts
4
+ import { pipeline } from "@dxos/node-std/stream";
5
+ import invariant4 from "tiny-invariant";
6
+ import { PublicKey as PublicKey2 } from "@dxos/keys";
7
+ import { log as log4 } from "@dxos/log";
8
+
9
+ // packages/core/mesh/teleport/src/teleport.ts
10
+ import invariant3 from "tiny-invariant";
11
+ import { asyncTimeout, scheduleTaskInterval as scheduleTaskInterval2, runInContextAsync, synchronized, scheduleTask } from "@dxos/async";
12
+ import { Context as Context2 } from "@dxos/context";
13
+ import { failUndefined as failUndefined2 } from "@dxos/debug";
14
+ import { PublicKey } from "@dxos/keys";
15
+ import { log as log3 } from "@dxos/log";
16
+ import { schema as schema2, RpcClosedError } from "@dxos/protocols";
17
+ import { createProtoRpcPeer } from "@dxos/rpc";
18
+ import { Callback } from "@dxos/util";
19
+
3
20
  // packages/core/mesh/teleport/src/muxing/framer.ts
4
21
  import { Duplex } from "@dxos/node-std/stream";
5
22
  import invariant from "tiny-invariant";
6
- import * as varint from "varint";
23
+ var FRAME_LENGTH_SIZE = 2;
7
24
  var Framer = class {
8
25
  constructor() {
9
- this._responseQueue = [];
26
+ this._sendCallbacks = [];
27
+ this._bytesSent = 0;
28
+ this._bytesReceived = 0;
10
29
  this._stream = new Duplex({
11
30
  objectMode: false,
12
31
  read: () => {
32
+ this._processResponseQueue();
13
33
  },
14
34
  write: (chunk, encoding, callback) => {
15
35
  invariant(!this._subscribeCb, "Internal Framer bug. Concurrent writes detected.");
36
+ this._bytesReceived += chunk.length;
16
37
  if (this._buffer && this._buffer.length > 0) {
17
38
  this._buffer = Buffer.concat([
18
39
  this._buffer,
@@ -36,11 +57,13 @@ var Framer = class {
36
57
  this.port = {
37
58
  send: (message) => {
38
59
  return new Promise((resolve) => {
39
- const canContinue = this._stream.push(encodeFrame(message));
60
+ const frame = encodeFrame(message);
61
+ this._bytesSent += frame.length;
62
+ const canContinue = this._stream.push(frame);
40
63
  if (!canContinue) {
41
- this._responseQueue.push(resolve);
64
+ this._sendCallbacks.push(resolve);
42
65
  } else {
43
- process.nextTick(resolve);
66
+ resolve();
44
67
  }
45
68
  });
46
69
  },
@@ -54,14 +77,19 @@ var Framer = class {
54
77
  };
55
78
  }
56
79
  };
57
- this.stream.on("drain", this._processResponseQueue.bind(this));
58
80
  }
59
81
  get stream() {
60
82
  return this._stream;
61
83
  }
84
+ get bytesSent() {
85
+ return this._bytesSent;
86
+ }
87
+ get bytesReceived() {
88
+ return this._bytesReceived;
89
+ }
62
90
  _processResponseQueue() {
63
- const responseQueue = this._responseQueue;
64
- this._responseQueue = [];
91
+ const responseQueue = this._sendCallbacks;
92
+ this._sendCallbacks = [];
65
93
  responseQueue.forEach((cb) => cb());
66
94
  }
67
95
  /**
@@ -84,139 +112,214 @@ var Framer = class {
84
112
  }
85
113
  }
86
114
  destroy() {
87
- this._stream.removeAllListeners("drain");
88
115
  this._stream.destroy();
89
116
  }
90
117
  };
91
118
  var decodeFrame = (buffer, offset) => {
92
- try {
93
- const frameLength = varint.decode(buffer, offset);
94
- const tagLength = varint.decode.bytes;
95
- if (buffer.length < offset + tagLength + frameLength) {
96
- return void 0;
97
- }
98
- const payload = buffer.subarray(offset + tagLength, offset + tagLength + frameLength);
99
- return {
100
- payload,
101
- bytesConsumed: tagLength + frameLength
102
- };
103
- } catch (err) {
104
- if (err instanceof RangeError) {
105
- return void 0;
106
- } else {
107
- throw err;
108
- }
119
+ if (buffer.length < offset + FRAME_LENGTH_SIZE) {
120
+ return void 0;
109
121
  }
122
+ const frameLength = buffer.readUInt16BE(offset);
123
+ const bytesConsumed = FRAME_LENGTH_SIZE + frameLength;
124
+ if (buffer.length < offset + bytesConsumed) {
125
+ return void 0;
126
+ }
127
+ const payload = buffer.subarray(offset + FRAME_LENGTH_SIZE, offset + bytesConsumed);
128
+ return {
129
+ payload,
130
+ bytesConsumed
131
+ };
110
132
  };
111
133
  var encodeFrame = (payload) => {
112
- const tagLength = varint.encodingLength(payload.length);
113
- const frame = Buffer.allocUnsafe(tagLength + payload.length);
114
- varint.encode(payload.length, frame);
115
- frame.set(payload, tagLength);
134
+ const frame = Buffer.allocUnsafe(FRAME_LENGTH_SIZE + payload.length);
135
+ frame.writeUInt16BE(payload.length, 0);
136
+ frame.set(payload, FRAME_LENGTH_SIZE);
116
137
  return frame;
117
138
  };
118
139
 
119
140
  // packages/core/mesh/teleport/src/muxing/muxer.ts
120
141
  import { Duplex as Duplex2 } from "@dxos/node-std/stream";
121
142
  import invariant2 from "tiny-invariant";
122
- import { scheduleTaskInterval, Event, Trigger } from "@dxos/async";
143
+ import { scheduleTaskInterval, Event as Event2, Trigger } from "@dxos/async";
123
144
  import { Context } from "@dxos/context";
124
145
  import { failUndefined } from "@dxos/debug";
125
- import { log } from "@dxos/log";
146
+ import { log as log2 } from "@dxos/log";
126
147
  import { schema } from "@dxos/protocols";
127
148
 
128
149
  // packages/core/mesh/teleport/src/muxing/balancer.ts
150
+ import * as varint from "varint";
151
+ import { Event } from "@dxos/async";
152
+ import { log } from "@dxos/log";
153
+ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/muxing/balancer.ts";
154
+ var MAX_CHUNK_SIZE = 8192;
129
155
  var Balancer = class {
130
- constructor(_port, _sysChannelId) {
131
- this._port = _port;
156
+ constructor(_sysChannelId) {
132
157
  this._sysChannelId = _sysChannelId;
133
158
  this._lastCallerIndex = 0;
134
159
  this._channels = [];
135
- this._calls = /* @__PURE__ */ new Map();
160
+ this._framer = new Framer();
161
+ this._sendBuffers = /* @__PURE__ */ new Map();
162
+ this._receiveBuffers = /* @__PURE__ */ new Map();
163
+ this.incomingData = new Event();
164
+ this.stream = this._framer.stream;
136
165
  this._channels.push(_sysChannelId);
166
+ this._framer.port.subscribe(this._processIncomingMessage.bind(this));
167
+ }
168
+ get bytesSent() {
169
+ return this._framer.bytesSent;
170
+ }
171
+ get bytesReceived() {
172
+ return this._framer.bytesReceived;
137
173
  }
138
174
  addChannel(channel) {
139
175
  this._channels.push(channel);
140
176
  }
141
- pushChunk(msg, trigger, channelId) {
142
- const noCalls = this._calls.size === 0;
177
+ pushData(data, trigger, channelId) {
178
+ const noCalls = this._sendBuffers.size === 0;
143
179
  if (!this._channels.includes(channelId)) {
144
180
  throw new Error(`Unknown channel ${channelId}`);
145
181
  }
146
- if (!this._calls.has(channelId)) {
147
- this._calls.set(channelId, []);
182
+ if (!this._sendBuffers.has(channelId)) {
183
+ this._sendBuffers.set(channelId, []);
148
184
  }
149
- const channelCalls = this._calls.get(channelId);
150
- channelCalls.push({
151
- msg,
152
- trigger
185
+ const sendBuffer = this._sendBuffers.get(channelId);
186
+ const chunks = [];
187
+ for (let idx = 0; idx < data.length; idx += MAX_CHUNK_SIZE) {
188
+ chunks.push(data.subarray(idx, idx + MAX_CHUNK_SIZE));
189
+ }
190
+ chunks.forEach((chunk, index) => {
191
+ const msg = encodeChunk({
192
+ chunk,
193
+ channelId,
194
+ dataLength: index === 0 ? data.length : void 0
195
+ });
196
+ sendBuffer.push({
197
+ msg,
198
+ trigger: index === chunks.length - 1 ? trigger : void 0
199
+ });
153
200
  });
154
201
  if (noCalls) {
155
- process.nextTick(async () => {
156
- await this._processCalls();
157
- });
202
+ this._sendChunks().catch((err) => log.catch(err, void 0, {
203
+ F: __dxlog_file,
204
+ L: 96,
205
+ S: this,
206
+ C: (f, a) => f(...a)
207
+ }));
158
208
  }
159
209
  }
160
210
  destroy() {
161
- this._calls.clear();
211
+ this._sendBuffers.clear();
212
+ this._framer.destroy();
213
+ }
214
+ _processIncomingMessage(msg) {
215
+ const { channelId, dataLength, chunk } = decodeChunk(msg, (channelId2) => !this._receiveBuffers.has(channelId2));
216
+ if (!this._receiveBuffers.has(channelId)) {
217
+ if (chunk.length < dataLength) {
218
+ this._receiveBuffers.set(channelId, {
219
+ buffer: Buffer.from(chunk),
220
+ msgLength: dataLength
221
+ });
222
+ } else {
223
+ this.incomingData.emit(chunk);
224
+ }
225
+ } else {
226
+ const channelBuffer = this._receiveBuffers.get(channelId);
227
+ channelBuffer.buffer = Buffer.concat([
228
+ channelBuffer.buffer,
229
+ chunk
230
+ ]);
231
+ if (channelBuffer.buffer.length < channelBuffer.msgLength) {
232
+ return;
233
+ }
234
+ const msg2 = channelBuffer.buffer;
235
+ this._receiveBuffers.delete(channelId);
236
+ this.incomingData.emit(msg2);
237
+ }
162
238
  }
163
239
  _getNextCallerId() {
164
- if (this._calls.has(this._sysChannelId)) {
240
+ if (this._sendBuffers.has(this._sysChannelId)) {
165
241
  return this._sysChannelId;
166
242
  }
167
243
  const index = this._lastCallerIndex;
168
244
  this._lastCallerIndex = (this._lastCallerIndex + 1) % this._channels.length;
169
245
  return this._channels[index];
170
246
  }
171
- _getNextCall() {
172
- let call;
173
- while (!call) {
247
+ _getNextChunk() {
248
+ let chunk;
249
+ while (!chunk) {
174
250
  const channelId = this._getNextCallerId();
175
- const channelCalls = this._calls.get(channelId);
176
- if (!channelCalls) {
251
+ const sendBuffer = this._sendBuffers.get(channelId);
252
+ if (!sendBuffer) {
177
253
  continue;
178
254
  }
179
- call = channelCalls.shift();
180
- if (channelCalls.length === 0) {
181
- this._calls.delete(channelId);
255
+ chunk = sendBuffer.shift();
256
+ if (sendBuffer.length === 0) {
257
+ this._sendBuffers.delete(channelId);
182
258
  }
183
259
  }
184
- return call;
260
+ return chunk;
185
261
  }
186
- async _processCalls() {
187
- if (this._calls.size === 0) {
262
+ async _sendChunks() {
263
+ var _a, _b;
264
+ if (this._sendBuffers.size === 0) {
188
265
  return;
189
266
  }
190
- const call = this._getNextCall();
267
+ const chunk = this._getNextChunk();
191
268
  try {
192
- await this._port.send(call.msg);
193
- call.trigger.wake();
269
+ await this._framer.port.send(chunk.msg);
270
+ (_a = chunk.trigger) == null ? void 0 : _a.wake();
194
271
  } catch (err) {
195
- call.trigger.throw(err);
272
+ (_b = chunk.trigger) == null ? void 0 : _b.throw(err);
196
273
  }
197
- await this._processCalls();
274
+ await this._sendChunks();
275
+ }
276
+ };
277
+ var encodeChunk = ({ channelId, dataLength, chunk }) => {
278
+ const channelTagLength = varint.encodingLength(channelId);
279
+ const dataLengthLength = dataLength ? varint.encodingLength(dataLength) : 0;
280
+ const message = Buffer.allocUnsafe(channelTagLength + dataLengthLength + chunk.length);
281
+ varint.encode(channelId, message);
282
+ if (dataLength) {
283
+ varint.encode(dataLength, message, channelTagLength);
284
+ }
285
+ message.set(chunk, channelTagLength + dataLengthLength);
286
+ return message;
287
+ };
288
+ var decodeChunk = (data, withLength) => {
289
+ const channelId = varint.decode(data);
290
+ let dataLength;
291
+ let offset = varint.decode.bytes;
292
+ if (withLength(channelId)) {
293
+ dataLength = varint.decode(data, offset);
294
+ offset += varint.decode.bytes;
198
295
  }
296
+ const chunk = data.subarray(offset);
297
+ return {
298
+ channelId,
299
+ dataLength,
300
+ chunk
301
+ };
199
302
  };
200
303
 
201
304
  // packages/core/mesh/teleport/src/muxing/muxer.ts
202
- var __dxlog_file = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/muxing/muxer.ts";
305
+ var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/muxing/muxer.ts";
203
306
  var Command = schema.getCodecForType("dxos.mesh.muxer.Command");
204
307
  var STATS_INTERVAL = 1e3;
205
- var SYSTEM_CHANNEL_ID = -1;
308
+ var MAX_SAFE_FRAME_SIZE = 1e6;
309
+ var SYSTEM_CHANNEL_ID = 0;
206
310
  var Muxer = class {
207
311
  constructor() {
208
- this._framer = new Framer();
209
- this._balancer = new Balancer(this._framer.port, SYSTEM_CHANNEL_ID);
210
- this.stream = this._framer.stream;
312
+ this._balancer = new Balancer(SYSTEM_CHANNEL_ID);
211
313
  this._channelsByLocalId = /* @__PURE__ */ new Map();
212
314
  this._channelsByTag = /* @__PURE__ */ new Map();
213
- this._nextId = 0;
315
+ this._ctx = new Context();
316
+ this._nextId = 1;
214
317
  this._destroyed = false;
215
318
  this._destroying = false;
216
- this.close = new Event();
217
- this.statsUpdated = new Event();
218
- this._ctx = new Context();
219
- this._framer.port.subscribe(async (msg) => {
319
+ this.close = new Event2();
320
+ this.statsUpdated = new Event2();
321
+ this.stream = this._balancer.stream;
322
+ this._balancer.incomingData.on(async (msg) => {
220
323
  await this._handleCommand(Command.decode(msg));
221
324
  });
222
325
  scheduleTaskInterval(this._ctx, async () => this._emitStats(), STATS_INTERVAL);
@@ -318,11 +421,11 @@ var Muxer = class {
318
421
  return;
319
422
  }
320
423
  this._destroying = true;
321
- Promise.resolve(this._sendCommand({
424
+ this._sendCommand({
322
425
  destroy: {
323
426
  error: err == null ? void 0 : err.message
324
427
  }
325
- }, SYSTEM_CHANNEL_ID)).then(() => {
428
+ }, SYSTEM_CHANNEL_ID).then(() => {
326
429
  this._dispose();
327
430
  }).catch((err2) => {
328
431
  this._dispose(err2);
@@ -336,7 +439,6 @@ var Muxer = class {
336
439
  }
337
440
  this._destroyed = true;
338
441
  this._balancer.destroy();
339
- this._framer.destroy();
340
442
  for (const channel of this._channelsByTag.values()) {
341
443
  (_a = channel.destroy) == null ? void 0 : _a.call(channel, err);
342
444
  }
@@ -346,20 +448,20 @@ var Muxer = class {
346
448
  }
347
449
  async _handleCommand(cmd) {
348
450
  var _a;
349
- log("Received command", {
451
+ log2("Received command", {
350
452
  cmd
351
453
  }, {
352
- F: __dxlog_file,
353
- L: 239,
454
+ F: __dxlog_file2,
455
+ L: 240,
354
456
  S: this,
355
457
  C: (f, a) => f(...a)
356
458
  });
357
459
  if (this._destroyed || this._destroying) {
358
- log.warn("Received command after destroy", {
460
+ log2.warn("Received command after destroy", {
359
461
  cmd
360
462
  }, {
361
- F: __dxlog_file,
362
- L: 242,
463
+ F: __dxlog_file2,
464
+ L: 243,
363
465
  S: this,
364
466
  C: (f, a) => f(...a)
365
467
  });
@@ -383,11 +485,11 @@ var Muxer = class {
383
485
  } else if (cmd.data) {
384
486
  const stream = (_a = this._channelsByLocalId.get(cmd.data.channelId)) != null ? _a : failUndefined();
385
487
  if (!stream.push) {
386
- log.warn("Received data for channel before it was opened", {
488
+ log2.warn("Received data for channel before it was opened", {
387
489
  tag: stream.tag
388
490
  }, {
389
- F: __dxlog_file,
390
- L: 269,
491
+ F: __dxlog_file2,
492
+ L: 270,
391
493
  S: this,
392
494
  C: (f, a) => f(...a)
393
495
  });
@@ -401,7 +503,7 @@ var Muxer = class {
401
503
  async _sendCommand(cmd, channelId = -1) {
402
504
  try {
403
505
  const trigger = new Trigger();
404
- this._balancer.pushChunk(Command.encode(cmd), trigger, channelId);
506
+ this._balancer.pushData(Command.encode(cmd), trigger, channelId);
405
507
  await trigger.wait();
406
508
  } catch (err) {
407
509
  this.destroy(err);
@@ -430,6 +532,17 @@ var Muxer = class {
430
532
  return channel;
431
533
  }
432
534
  async _sendData(channel, data) {
535
+ if (data.length > MAX_SAFE_FRAME_SIZE) {
536
+ log2.warn("frame size exceeds maximum safe value", {
537
+ size: data.length,
538
+ threshold: MAX_SAFE_FRAME_SIZE
539
+ }, {
540
+ F: __dxlog_file2,
541
+ L: 314,
542
+ S: this,
543
+ C: (f, a) => f(...a)
544
+ });
545
+ }
433
546
  channel.stats.bytesSent += data.length;
434
547
  if (channel.remoteId === null) {
435
548
  channel.buffer.push(data);
@@ -453,25 +566,23 @@ var Muxer = class {
453
566
  if (this._destroyed || this._destroying) {
454
567
  return;
455
568
  }
456
- this.statsUpdated.emit(Array.from(this._channelsByTag.values()).map((channel) => ({
457
- id: channel.id,
458
- tag: channel.tag,
459
- bytesSent: channel.stats.bytesSent,
460
- bytesReceived: channel.stats.bytesReceived
461
- })));
569
+ const bytesSent = this._balancer.bytesSent;
570
+ const bytesReceived = this._balancer.bytesReceived;
571
+ this.statsUpdated.emit({
572
+ channels: Array.from(this._channelsByTag.values()).map((channel) => ({
573
+ id: channel.id,
574
+ tag: channel.tag,
575
+ contentType: channel.contentType,
576
+ bytesSent: channel.stats.bytesSent,
577
+ bytesReceived: channel.stats.bytesReceived
578
+ })),
579
+ bytesSent,
580
+ bytesReceived
581
+ });
462
582
  }
463
583
  };
464
584
 
465
585
  // packages/core/mesh/teleport/src/teleport.ts
466
- import invariant3 from "tiny-invariant";
467
- import { asyncTimeout, scheduleTaskInterval as scheduleTaskInterval2, runInContextAsync, synchronized, scheduleTask } from "@dxos/async";
468
- import { Context as Context2 } from "@dxos/context";
469
- import { failUndefined as failUndefined2 } from "@dxos/debug";
470
- import { PublicKey } from "@dxos/keys";
471
- import { log as log2 } from "@dxos/log";
472
- import { schema as schema2, RpcClosedError } from "@dxos/protocols";
473
- import { createProtoRpcPeer } from "@dxos/rpc";
474
- import { Callback } from "@dxos/util";
475
586
  function _ts_decorate(decorators, target, key, desc) {
476
587
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
477
588
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
@@ -482,15 +593,15 @@ function _ts_decorate(decorators, target, key, desc) {
482
593
  r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
483
594
  return c > 3 && r && Object.defineProperty(target, key, r), r;
484
595
  }
485
- var __dxlog_file2 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/teleport.ts";
596
+ var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/teleport.ts";
486
597
  var Teleport = class {
487
598
  constructor({ initiator, localPeerId, remotePeerId }) {
488
599
  this._ctx = new Context2({
489
600
  onError: (err) => {
490
601
  void this.destroy(err).catch(() => {
491
- log2.error("Error during destroy", err, {
492
- F: __dxlog_file2,
493
- L: 35,
602
+ log3.error("Error during destroy", err, {
603
+ F: __dxlog_file3,
604
+ L: 34,
494
605
  S: this,
495
606
  C: (f, a) => f(...a)
496
607
  });
@@ -502,9 +613,9 @@ var Teleport = class {
502
613
  heartbeatInterval: 1e4,
503
614
  heartbeatTimeout: 1e4,
504
615
  onTimeout: () => {
505
- this.destroy(new Error("Connection timed out")).catch((err) => log2.catch(err, void 0, {
506
- F: __dxlog_file2,
507
- L: 46,
616
+ this.destroy(new Error("Connection timed out")).catch((err) => log3.catch(err, void 0, {
617
+ F: __dxlog_file3,
618
+ L: 45,
508
619
  S: this,
509
620
  C: (f, a) => f(...a)
510
621
  }));
@@ -516,16 +627,15 @@ var Teleport = class {
516
627
  invariant3(typeof initiator === "boolean");
517
628
  invariant3(PublicKey.isPublicKey(localPeerId));
518
629
  invariant3(PublicKey.isPublicKey(remotePeerId));
519
- invariant3(typeof initiator === "boolean");
520
630
  this.initiator = initiator;
521
631
  this.localPeerId = localPeerId;
522
632
  this.remotePeerId = remotePeerId;
523
633
  this._control.onExtensionRegistered.set(async (name) => {
524
- log2("remote extension", {
634
+ log3("remote extension", {
525
635
  name
526
636
  }, {
527
- F: __dxlog_file2,
528
- L: 65,
637
+ F: __dxlog_file3,
638
+ L: 63,
529
639
  S: this,
530
640
  C: (f, a) => f(...a)
531
641
  });
@@ -547,6 +657,20 @@ var Teleport = class {
547
657
  await this.destroy(err);
548
658
  });
549
659
  }
660
+ this._muxer.statsUpdated.on((stats) => {
661
+ log3.trace("dxos.mesh.teleport.stats", {
662
+ localPeerId,
663
+ remotePeerId,
664
+ bytesSent: stats.bytesSent,
665
+ bytesReceived: stats.bytesReceived,
666
+ channels: stats.channels
667
+ }, {
668
+ F: __dxlog_file3,
669
+ L: 88,
670
+ S: this,
671
+ C: (f, a) => f(...a)
672
+ });
673
+ });
550
674
  }
551
675
  get stream() {
552
676
  return this._muxer.stream;
@@ -574,9 +698,9 @@ var Teleport = class {
574
698
  try {
575
699
  await extension.onClose(err);
576
700
  } catch (err2) {
577
- log2.catch(err2, void 0, {
578
- F: __dxlog_file2,
579
- L: 125,
701
+ log3.catch(err2, void 0, {
702
+ F: __dxlog_file3,
703
+ L: 132,
580
704
  S: this,
581
705
  C: (f, a) => f(...a)
582
706
  });
@@ -588,11 +712,11 @@ var Teleport = class {
588
712
  if (!this._open) {
589
713
  throw new Error("Not open");
590
714
  }
591
- log2("addExtension", {
715
+ log3("addExtension", {
592
716
  name
593
717
  }, {
594
- F: __dxlog_file2,
595
- L: 137,
718
+ F: __dxlog_file3,
719
+ L: 144,
596
720
  S: this,
597
721
  C: (f, a) => f(...a)
598
722
  });
@@ -620,11 +744,11 @@ var Teleport = class {
620
744
  }
621
745
  async _openExtension(extensionName) {
622
746
  var _a;
623
- log2("open extension", {
747
+ log3("open extension", {
624
748
  extensionName
625
749
  }, {
626
- F: __dxlog_file2,
627
- L: 167,
750
+ F: __dxlog_file3,
751
+ L: 174,
628
752
  S: this,
629
753
  C: (f, a) => f(...a)
630
754
  });
@@ -648,11 +772,11 @@ var Teleport = class {
648
772
  }
649
773
  };
650
774
  await extension.onOpen(context);
651
- log2("extension opened", {
775
+ log3("extension opened", {
652
776
  extensionName
653
777
  }, {
654
- F: __dxlog_file2,
655
- L: 190,
778
+ F: __dxlog_file3,
779
+ L: 197,
656
780
  S: this,
657
781
  C: (f, a) => f(...a)
658
782
  });
@@ -671,6 +795,11 @@ var ControlExtension = class {
671
795
  });
672
796
  this.onExtensionRegistered = new Callback();
673
797
  }
798
+ async registerExtension(name) {
799
+ await this._rpc.rpc.Control.registerExtension({
800
+ name
801
+ });
802
+ }
674
803
  async onOpen(extensionContext) {
675
804
  this._extensionContext = extensionContext;
676
805
  this._rpc = createProtoRpcPeer({
@@ -706,20 +835,136 @@ var ControlExtension = class {
706
835
  await this._ctx.dispose();
707
836
  await this._rpc.close();
708
837
  }
709
- async registerExtension(name) {
710
- await this._rpc.rpc.Control.registerExtension({
711
- name
838
+ };
839
+
840
+ // packages/core/mesh/teleport/src/testing/test-builder.ts
841
+ var __dxlog_file4 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/testing/test-builder.ts";
842
+ var TestBuilder = class {
843
+ constructor() {
844
+ this._peers = /* @__PURE__ */ new Set();
845
+ }
846
+ createPeer(opts) {
847
+ const peer = opts.factory();
848
+ this._peers.add(peer);
849
+ return peer;
850
+ }
851
+ *createPeers(opts) {
852
+ while (true) {
853
+ yield this.createPeer(opts);
854
+ }
855
+ }
856
+ async destroy() {
857
+ await Promise.all(Array.from(this._peers).map((agent) => agent.destroy()));
858
+ }
859
+ async connect(peer1, peer2) {
860
+ invariant4(peer1 !== peer2);
861
+ invariant4(this._peers.has(peer1));
862
+ invariant4(this._peers.has(peer1));
863
+ const connection1 = peer1.createConnection({
864
+ initiator: true,
865
+ remotePeerId: peer2.peerId
866
+ });
867
+ const connection2 = peer2.createConnection({
868
+ initiator: false,
869
+ remotePeerId: peer1.peerId
870
+ });
871
+ pipeStreams(connection1.teleport.stream, connection2.teleport.stream);
872
+ await Promise.all([
873
+ peer1.openConnection(connection1),
874
+ peer2.openConnection(connection2)
875
+ ]);
876
+ return [
877
+ connection1,
878
+ connection2
879
+ ];
880
+ }
881
+ async disconnect(peer1, peer2) {
882
+ invariant4(peer1 !== peer2);
883
+ invariant4(this._peers.has(peer1));
884
+ invariant4(this._peers.has(peer1));
885
+ const connection1 = Array.from(peer1.connections).find((connection) => connection.remotePeerId.equals(peer2.peerId));
886
+ const connection2 = Array.from(peer2.connections).find((connection) => connection.remotePeerId.equals(peer1.peerId));
887
+ invariant4(connection1);
888
+ invariant4(connection2);
889
+ await Promise.all([
890
+ peer1.closeConnection(connection1),
891
+ peer2.closeConnection(connection2)
892
+ ]);
893
+ }
894
+ };
895
+ var TestPeer = class {
896
+ constructor(peerId = PublicKey2.random()) {
897
+ this.peerId = peerId;
898
+ this.connections = /* @__PURE__ */ new Set();
899
+ }
900
+ async onOpen(connection) {
901
+ }
902
+ async onClose(connection) {
903
+ }
904
+ createConnection({ initiator, remotePeerId }) {
905
+ const connection = new TestConnection(this.peerId, remotePeerId, initiator);
906
+ this.connections.add(connection);
907
+ return connection;
908
+ }
909
+ async openConnection(connection) {
910
+ invariant4(this.connections.has(connection));
911
+ await connection.teleport.open();
912
+ await this.onOpen(connection);
913
+ }
914
+ async closeConnection(connection) {
915
+ invariant4(this.connections.has(connection));
916
+ await this.onClose(connection);
917
+ await connection.teleport.close();
918
+ this.connections.delete(connection);
919
+ }
920
+ async destroy() {
921
+ for (const teleport of this.connections) {
922
+ await this.closeConnection(teleport);
923
+ }
924
+ }
925
+ };
926
+ var pipeStreams = (stream1, stream2) => {
927
+ pipeline(stream1, stream2, (err) => {
928
+ if (err && err.code !== "ERR_STREAM_PREMATURE_CLOSE") {
929
+ log4.catch(err, void 0, {
930
+ F: __dxlog_file4,
931
+ L: 106,
932
+ S: void 0,
933
+ C: (f, a) => f(...a)
934
+ });
935
+ }
936
+ });
937
+ pipeline(stream2, stream1, (err) => {
938
+ if (err && err.code !== "ERR_STREAM_PREMATURE_CLOSE") {
939
+ log4.catch(err, void 0, {
940
+ F: __dxlog_file4,
941
+ L: 111,
942
+ S: void 0,
943
+ C: (f, a) => f(...a)
944
+ });
945
+ }
946
+ });
947
+ };
948
+ var TestConnection = class {
949
+ constructor(localPeerId, remotePeerId, initiator) {
950
+ this.localPeerId = localPeerId;
951
+ this.remotePeerId = remotePeerId;
952
+ this.initiator = initiator;
953
+ this.teleport = new Teleport({
954
+ initiator,
955
+ localPeerId,
956
+ remotePeerId
712
957
  });
713
958
  }
714
959
  };
715
960
 
716
961
  // packages/core/mesh/teleport/src/testing/test-extension.ts
717
- import invariant4 from "tiny-invariant";
962
+ import invariant5 from "tiny-invariant";
718
963
  import { asyncTimeout as asyncTimeout2, Trigger as Trigger2 } from "@dxos/async";
719
- import { log as log3 } from "@dxos/log";
964
+ import { log as log5 } from "@dxos/log";
720
965
  import { schema as schema3 } from "@dxos/protocols";
721
966
  import { createProtoRpcPeer as createProtoRpcPeer2 } from "@dxos/rpc";
722
- var __dxlog_file3 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/testing/test-extension.ts";
967
+ var __dxlog_file5 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/testing/test-extension.ts";
723
968
  var TestExtension = class {
724
969
  constructor(callbacks = {}) {
725
970
  this.callbacks = callbacks;
@@ -732,11 +977,11 @@ var TestExtension = class {
732
977
  }
733
978
  async onOpen(context) {
734
979
  var _a, _b;
735
- log3("onOpen", {
980
+ log5("onOpen", {
736
981
  localPeerId: context.localPeerId,
737
982
  remotePeerId: context.remotePeerId
738
983
  }, {
739
- F: __dxlog_file3,
984
+ F: __dxlog_file5,
740
985
  L: 33,
741
986
  S: this,
742
987
  C: (f, a) => f(...a)
@@ -771,10 +1016,10 @@ var TestExtension = class {
771
1016
  }
772
1017
  async onClose(err) {
773
1018
  var _a, _b, _c;
774
- log3("onClose", {
1019
+ log5("onClose", {
775
1020
  err
776
1021
  }, {
777
- F: __dxlog_file3,
1022
+ F: __dxlog_file5,
778
1023
  L: 67,
779
1024
  S: this,
780
1025
  C: (f, a) => f(...a)
@@ -790,7 +1035,226 @@ var TestExtension = class {
790
1035
  const res = await asyncTimeout2(this._rpc.rpc.TestService.testCall({
791
1036
  data: message
792
1037
  }), 1500);
793
- invariant4(res.data === message);
1038
+ invariant5(res.data === message);
1039
+ }
1040
+ /**
1041
+ * Force-close the connection.
1042
+ */
1043
+ async closeConnection(err) {
1044
+ var _a;
1045
+ (_a = this.extensionContext) == null ? void 0 : _a.close(err);
1046
+ }
1047
+ };
1048
+
1049
+ // packages/core/mesh/teleport/src/testing/test-extension-with-streams.ts
1050
+ import assert from "@dxos/node-std/assert";
1051
+ import { randomBytes } from "@dxos/node-std/crypto";
1052
+ import { Trigger as Trigger3 } from "@dxos/async";
1053
+ import { log as log6 } from "@dxos/log";
1054
+ import { schema as schema4 } from "@dxos/protocols";
1055
+ import { createProtoRpcPeer as createProtoRpcPeer3 } from "@dxos/rpc";
1056
+ var __dxlog_file6 = "/home/runner/work/dxos/dxos/packages/core/mesh/teleport/src/testing/test-extension-with-streams.ts";
1057
+ var TestExtensionWithStreams = class {
1058
+ constructor(callbacks = {}) {
1059
+ this.callbacks = callbacks;
1060
+ this.open = new Trigger3();
1061
+ this.closed = new Trigger3();
1062
+ this._streams = /* @__PURE__ */ new Map();
1063
+ }
1064
+ get remotePeerId() {
1065
+ var _a;
1066
+ return (_a = this.extensionContext) == null ? void 0 : _a.remotePeerId;
1067
+ }
1068
+ async _openStream(streamTag, interval = 5, chunkSize = 2048) {
1069
+ assert(!this._streams.has(streamTag), `Stream already exists: ${streamTag}`);
1070
+ const networkStream = await this.extensionContext.createStream(streamTag, {
1071
+ contentType: "application/x-test-stream"
1072
+ });
1073
+ const streamEntry = {
1074
+ networkStream,
1075
+ bytesSent: 0,
1076
+ bytesReceived: 0,
1077
+ sendErrors: 0,
1078
+ receiveErrors: 0,
1079
+ startTimestamp: Date.now()
1080
+ };
1081
+ const pushChunk = () => {
1082
+ streamEntry.timer = setTimeout(() => {
1083
+ const chunk = randomBytes(chunkSize);
1084
+ if (!networkStream.write(chunk, "binary", (err) => {
1085
+ if (!err) {
1086
+ streamEntry.bytesSent += chunk.length;
1087
+ } else {
1088
+ streamEntry.sendErrors += 1;
1089
+ }
1090
+ })) {
1091
+ networkStream.once("drain", pushChunk);
1092
+ } else {
1093
+ process.nextTick(pushChunk);
1094
+ }
1095
+ }, interval);
1096
+ };
1097
+ pushChunk();
1098
+ this._streams.set(streamTag, streamEntry);
1099
+ networkStream.on("data", (data) => {
1100
+ streamEntry.bytesReceived += data.length;
1101
+ });
1102
+ networkStream.on("error", (err) => {
1103
+ streamEntry.receiveErrors += 1;
1104
+ });
1105
+ networkStream.on("close", () => {
1106
+ networkStream.removeAllListeners();
1107
+ });
1108
+ streamEntry.reportingTimer = setInterval(() => {
1109
+ var _a, _b;
1110
+ const { bytesSent, bytesReceived, sendErrors, receiveErrors } = streamEntry;
1111
+ log6.trace("dxos.test.stream-stats", {
1112
+ streamTag,
1113
+ bytesSent,
1114
+ bytesReceived,
1115
+ sendErrors,
1116
+ receiveErrors,
1117
+ from: (_a = this.extensionContext) == null ? void 0 : _a.localPeerId,
1118
+ to: (_b = this.extensionContext) == null ? void 0 : _b.remotePeerId
1119
+ }, {
1120
+ F: __dxlog_file6,
1121
+ L: 91,
1122
+ S: this,
1123
+ C: (f, a) => f(...a)
1124
+ });
1125
+ }, 100);
1126
+ }
1127
+ _closeStream(streamTag) {
1128
+ assert(this._streams.has(streamTag), `Stream does not exist: ${streamTag}`);
1129
+ const stream = this._streams.get(streamTag);
1130
+ clearTimeout(stream.timer);
1131
+ clearTimeout(stream.reportingTimer);
1132
+ const { bytesSent, bytesReceived, sendErrors, receiveErrors, startTimestamp } = stream;
1133
+ stream.networkStream.destroy();
1134
+ this._streams.delete(streamTag);
1135
+ return {
1136
+ bytesSent,
1137
+ bytesReceived,
1138
+ sendErrors,
1139
+ receiveErrors,
1140
+ runningTime: Date.now() - (startTimestamp != null ? startTimestamp : 0)
1141
+ };
1142
+ }
1143
+ async onOpen(context) {
1144
+ var _a, _b;
1145
+ log6("onOpen", {
1146
+ localPeerId: context.localPeerId,
1147
+ remotePeerId: context.remotePeerId
1148
+ }, {
1149
+ F: __dxlog_file6,
1150
+ L: 126,
1151
+ S: this,
1152
+ C: (f, a) => f(...a)
1153
+ });
1154
+ this.extensionContext = context;
1155
+ this._rpc = createProtoRpcPeer3({
1156
+ port: await context.createPort("rpc", {
1157
+ contentType: 'application/x-protobuf; messageType="dxos.rpc.Message"'
1158
+ }),
1159
+ requested: {
1160
+ TestServiceWithStreams: schema4.getService("example.testing.rpc.TestServiceWithStreams")
1161
+ },
1162
+ exposed: {
1163
+ TestServiceWithStreams: schema4.getService("example.testing.rpc.TestServiceWithStreams")
1164
+ },
1165
+ handlers: {
1166
+ TestServiceWithStreams: {
1167
+ requestTestStream: async (request) => {
1168
+ const { data: streamTag, streamLoadInterval, streamLoadChunkSize } = request;
1169
+ await this._openStream(streamTag, streamLoadInterval, streamLoadChunkSize);
1170
+ return {
1171
+ data: streamTag
1172
+ };
1173
+ },
1174
+ closeTestStream: async (request) => {
1175
+ const streamTag = request.data;
1176
+ const { bytesSent, bytesReceived, sendErrors, receiveErrors, runningTime } = this._closeStream(streamTag);
1177
+ return {
1178
+ data: streamTag,
1179
+ bytesSent,
1180
+ bytesReceived,
1181
+ sendErrors,
1182
+ receiveErrors,
1183
+ runningTime
1184
+ };
1185
+ }
1186
+ }
1187
+ },
1188
+ timeout: 2e3
1189
+ });
1190
+ await this._rpc.open();
1191
+ await ((_b = (_a = this.callbacks).onOpen) == null ? void 0 : _b.call(_a));
1192
+ this.open.wake();
1193
+ }
1194
+ async onClose(err) {
1195
+ var _a, _b, _c;
1196
+ log6("onClose", {
1197
+ err
1198
+ }, {
1199
+ F: __dxlog_file6,
1200
+ L: 177,
1201
+ S: this,
1202
+ C: (f, a) => f(...a)
1203
+ });
1204
+ await ((_b = (_a = this.callbacks).onClose) == null ? void 0 : _b.call(_a));
1205
+ this.closed.wake();
1206
+ for (const [streamTag, stream] of Object.entries(this._streams)) {
1207
+ log6("closing stream", {
1208
+ streamTag
1209
+ }, {
1210
+ F: __dxlog_file6,
1211
+ L: 181,
1212
+ S: this,
1213
+ C: (f, a) => f(...a)
1214
+ });
1215
+ clearTimeout(stream.interval);
1216
+ stream.networkStream.destroy();
1217
+ }
1218
+ await ((_c = this._rpc) == null ? void 0 : _c.close());
1219
+ }
1220
+ async addNewStream(streamLoadInterval, streamLoadChunkSize, streamTag) {
1221
+ await this.open.wait({
1222
+ timeout: 1500
1223
+ });
1224
+ if (!streamTag) {
1225
+ streamTag = `stream-${randomBytes(4).toString("hex")}`;
1226
+ }
1227
+ const { data } = await this._rpc.rpc.TestServiceWithStreams.requestTestStream({
1228
+ data: streamTag,
1229
+ streamLoadInterval,
1230
+ streamLoadChunkSize
1231
+ });
1232
+ assert(data === streamTag);
1233
+ await this._openStream(streamTag, streamLoadInterval, streamLoadChunkSize);
1234
+ return streamTag;
1235
+ }
1236
+ async closeStream(streamTag) {
1237
+ await this.open.wait({
1238
+ timeout: 1500
1239
+ });
1240
+ const { data, bytesSent, bytesReceived, sendErrors, receiveErrors, runningTime } = await this._rpc.rpc.TestServiceWithStreams.closeTestStream({
1241
+ data: streamTag
1242
+ });
1243
+ assert(data === streamTag);
1244
+ const local = this._closeStream(streamTag);
1245
+ return {
1246
+ streamTag,
1247
+ stats: {
1248
+ local,
1249
+ remote: {
1250
+ bytesSent,
1251
+ bytesReceived,
1252
+ sendErrors,
1253
+ receiveErrors,
1254
+ runningTime
1255
+ }
1256
+ }
1257
+ };
794
1258
  }
795
1259
  /**
796
1260
  * Force-close the connection.
@@ -807,6 +1271,10 @@ export {
807
1271
  encodeFrame,
808
1272
  Muxer,
809
1273
  Teleport,
810
- TestExtension
1274
+ TestBuilder,
1275
+ TestPeer,
1276
+ TestConnection,
1277
+ TestExtension,
1278
+ TestExtensionWithStreams
811
1279
  };
812
- //# sourceMappingURL=chunk-7TGKNWZQ.mjs.map
1280
+ //# sourceMappingURL=chunk-GGVQ27WJ.mjs.map