@dxos/teleport 0.1.23 → 0.1.24

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.
@@ -2,74 +2,129 @@
2
2
  // Copyright 2022 DXOS.org
3
3
  //
4
4
 
5
- import { pipeline } from 'node:stream';
5
+ import assert from 'node:assert';
6
+ import { Duplex, pipeline } from 'node:stream';
6
7
 
7
8
  import { PublicKey } from '@dxos/keys';
8
9
  import { log } from '@dxos/log';
9
10
 
10
11
  import { Teleport } from '../teleport';
11
12
 
13
+ type CreatePeerOpts<T extends TestPeer> = {
14
+ factory: () => T;
15
+ };
16
+
12
17
  export class TestBuilder {
13
- private readonly _peers = new Array<TestPeer>();
18
+ private readonly _peers = new Set<TestPeer>();
14
19
 
15
- createPeer(peerId?: PublicKey): TestPeer {
16
- const peer = new TestPeer(peerId);
17
- this._peers.push(peer);
20
+ createPeer<T extends TestPeer>(opts: CreatePeerOpts<T>): T {
21
+ const peer = opts.factory();
22
+ this._peers.add(peer);
18
23
  return peer;
19
24
  }
20
25
 
26
+ *createPeers<T extends TestPeer>(opts: CreatePeerOpts<T>): Generator<T> {
27
+ while (true) {
28
+ yield this.createPeer(opts);
29
+ }
30
+ }
31
+
21
32
  async destroy() {
22
- await Promise.all(this._peers.map((agent) => agent.destroy()));
33
+ await Promise.all(Array.from(this._peers).map((agent) => agent.destroy()));
23
34
  }
24
35
 
25
- /**
26
- * Simulates two peers connected via P2P network.
27
- */
28
- async createPipedPeers({ peerId1, peerId2 }: { peerId1?: PublicKey; peerId2?: PublicKey } = {}) {
29
- const peer1 = this.createPeer(peerId1);
30
- const peer2 = this.createPeer(peerId2);
36
+ async connect(peer1: TestPeer, peer2: TestPeer) {
37
+ assert(peer1 !== peer2);
38
+ assert(this._peers.has(peer1));
39
+ assert(this._peers.has(peer1));
40
+
41
+ const connection1 = peer1.createConnection({ initiator: true, remotePeerId: peer2.peerId });
42
+ const connection2 = peer2.createConnection({ initiator: false, remotePeerId: peer1.peerId });
43
+
44
+ pipeStreams(connection1.teleport.stream, connection2.teleport.stream);
45
+ await Promise.all([peer1.openConnection(connection1), peer2.openConnection(connection2)]);
46
+
47
+ return [connection1, connection2];
48
+ }
31
49
 
32
- peer1.initializeTeleport({ initiator: true, remotePeerId: peer2.peerId });
33
- peer2.initializeTeleport({ initiator: false, remotePeerId: peer1.peerId });
50
+ async disconnect(peer1: TestPeer, peer2: TestPeer) {
51
+ assert(peer1 !== peer2);
52
+ assert(this._peers.has(peer1));
53
+ assert(this._peers.has(peer1));
34
54
 
35
- peer1.pipeline(peer2);
36
- peer2.pipeline(peer1);
55
+ const connection1 = Array.from(peer1.connections).find((connection) =>
56
+ connection.remotePeerId.equals(peer2.peerId)
57
+ );
58
+ const connection2 = Array.from(peer2.connections).find((connection) =>
59
+ connection.remotePeerId.equals(peer1.peerId)
60
+ );
37
61
 
38
- await Promise.all([peer1.teleport!.open(), peer2.teleport!.open()]);
62
+ assert(connection1);
63
+ assert(connection2);
39
64
 
40
- return { peer1, peer2 };
65
+ await Promise.all([peer1.closeConnection(connection1), peer2.closeConnection(connection2)]);
41
66
  }
42
67
  }
43
68
 
44
69
  export class TestPeer {
45
- public teleport?: Teleport;
70
+ public readonly connections = new Set<TestConnection>();
46
71
 
47
72
  constructor(public readonly peerId: PublicKey = PublicKey.random()) {}
48
73
 
49
- initializeTeleport({ initiator, remotePeerId }: { initiator: boolean; remotePeerId: PublicKey }) {
50
- if (this.teleport) {
51
- return this;
52
- }
53
- this.teleport = new Teleport({
54
- initiator,
55
- localPeerId: this.peerId,
56
- remotePeerId
57
- });
58
- return this;
74
+ protected async onOpen(connection: TestConnection) {}
75
+ protected async onClose(connection: TestConnection) {}
76
+
77
+ createConnection({ initiator, remotePeerId }: { initiator: boolean; remotePeerId: PublicKey }) {
78
+ const connection = new TestConnection(this.peerId, remotePeerId, initiator);
79
+ this.connections.add(connection);
80
+ return connection;
59
81
  }
60
82
 
61
- pipeline(peer: TestPeer) {
62
- if (!this.teleport || !peer.teleport) {
63
- throw new Error('Teleport not initialized');
64
- }
65
- pipeline(this.teleport.stream, peer.teleport.stream, (err) => {
66
- if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
67
- log.catch(err);
68
- }
69
- });
83
+ async openConnection(connection: TestConnection) {
84
+ assert(this.connections.has(connection));
85
+ await connection.teleport.open();
86
+ await this.onOpen(connection);
87
+ }
88
+
89
+ async closeConnection(connection: TestConnection) {
90
+ assert(this.connections.has(connection));
91
+ await this.onClose(connection);
92
+ await connection.teleport.close();
93
+ this.connections.delete(connection);
70
94
  }
71
95
 
72
96
  async destroy() {
73
- await this.teleport?.destroy();
97
+ for (const teleport of this.connections) {
98
+ await this.closeConnection(teleport);
99
+ }
100
+ }
101
+ }
102
+
103
+ const pipeStreams = (stream1: Duplex, stream2: Duplex) => {
104
+ pipeline(stream1, stream2, (err) => {
105
+ if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
106
+ log.catch(err);
107
+ }
108
+ });
109
+ pipeline(stream2, stream1, (err) => {
110
+ if (err && err.code !== 'ERR_STREAM_PREMATURE_CLOSE') {
111
+ log.catch(err);
112
+ }
113
+ });
114
+ };
115
+
116
+ export class TestConnection {
117
+ public teleport: Teleport;
118
+
119
+ constructor(
120
+ public readonly localPeerId: PublicKey,
121
+ public readonly remotePeerId: PublicKey,
122
+ public readonly initiator: boolean
123
+ ) {
124
+ this.teleport = new Teleport({
125
+ initiator,
126
+ localPeerId,
127
+ remotePeerId
128
+ });
74
129
  }
75
130
  }