@dxos/network-manager 2.33.9-dev.7d11f506 → 2.33.9-dev.9bbef4e2
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/dist/src/network-manager.blueprint-test.d.ts +3 -1
- package/dist/src/network-manager.blueprint-test.d.ts.map +1 -1
- package/dist/src/network-manager.blueprint-test.js +46 -17
- package/dist/src/network-manager.blueprint-test.js.map +1 -1
- package/dist/src/network-manager.browser-test.js +1 -1
- package/dist/src/network-manager.browser-test.js.map +1 -1
- package/dist/src/network-manager.d.ts.map +1 -1
- package/dist/src/network-manager.js +13 -12
- package/dist/src/network-manager.js.map +1 -1
- package/dist/src/network-manager.test.js +5 -4
- package/dist/src/network-manager.test.js.map +1 -1
- package/dist/src/proto/gen/dxos/credentials.d.ts +39 -0
- package/dist/src/proto/gen/dxos/credentials.d.ts.map +1 -1
- package/dist/src/proto/gen/dxos/halo/keys.d.ts +44 -2
- package/dist/src/proto/gen/dxos/halo/keys.d.ts.map +1 -1
- package/dist/src/proto/gen/dxos/halo/keys.js +4 -0
- package/dist/src/proto/gen/dxos/halo/keys.js.map +1 -1
- package/dist/src/proto/gen/dxos/mesh/signal.d.ts +74 -16
- package/dist/src/proto/gen/dxos/mesh/signal.d.ts.map +1 -1
- package/dist/src/proto/gen/dxos/mesh/signalMessage.d.ts +79 -0
- package/dist/src/proto/gen/dxos/mesh/signalMessage.d.ts.map +1 -0
- package/dist/src/proto/gen/dxos/mesh/signalMessage.js +3 -0
- package/dist/src/proto/gen/dxos/mesh/signalMessage.js.map +1 -0
- package/dist/src/proto/gen/google/protobuf.d.ts +8 -2
- package/dist/src/proto/gen/google/protobuf.d.ts.map +1 -1
- package/dist/src/proto/gen/index.d.ts +17 -4
- package/dist/src/proto/gen/index.d.ts.map +1 -1
- package/dist/src/proto/gen/index.js +1 -1
- package/dist/src/proto/gen/index.js.map +1 -1
- package/dist/src/proto/substitutions.d.ts +4 -0
- package/dist/src/proto/substitutions.d.ts.map +1 -1
- package/dist/src/proto/substitutions.js +3 -1
- package/dist/src/proto/substitutions.js.map +1 -1
- package/dist/src/protocol-factory.js +3 -3
- package/dist/src/protocol-factory.js.map +1 -1
- package/dist/src/signal/in-memory-signal-manager.d.ts +7 -7
- package/dist/src/signal/in-memory-signal-manager.d.ts.map +1 -1
- package/dist/src/signal/in-memory-signal-manager.js +34 -13
- package/dist/src/signal/in-memory-signal-manager.js.map +1 -1
- package/dist/src/signal/index.d.ts +1 -2
- package/dist/src/signal/index.d.ts.map +1 -1
- package/dist/src/signal/index.js +1 -2
- package/dist/src/signal/index.js.map +1 -1
- package/dist/src/signal/integration.test.d.ts +2 -0
- package/dist/src/signal/integration.test.d.ts.map +1 -0
- package/dist/src/signal/integration.test.js +102 -0
- package/dist/src/signal/integration.test.js.map +1 -0
- package/dist/src/signal/message-router.d.ts +20 -8
- package/dist/src/signal/message-router.d.ts.map +1 -1
- package/dist/src/signal/message-router.js +96 -17
- package/dist/src/signal/message-router.js.map +1 -1
- package/dist/src/signal/message-router.test.js +125 -22
- package/dist/src/signal/message-router.test.js.map +1 -1
- package/dist/src/signal/signal-client.d.ts +33 -17
- package/dist/src/signal/signal-client.d.ts.map +1 -1
- package/dist/src/signal/signal-client.js +102 -82
- package/dist/src/signal/signal-client.js.map +1 -1
- package/dist/src/signal/signal-client.test.js +60 -75
- package/dist/src/signal/signal-client.test.js.map +1 -1
- package/dist/src/signal/{websocket-signal-manager.d.ts → signal-manager-impl.d.ts} +13 -11
- package/dist/src/signal/signal-manager-impl.d.ts.map +1 -0
- package/dist/src/signal/signal-manager-impl.js +151 -0
- package/dist/src/signal/signal-manager-impl.js.map +1 -0
- package/dist/src/signal/signal-manager.d.ts +12 -11
- package/dist/src/signal/signal-manager.d.ts.map +1 -1
- package/dist/src/signal/signal-rpc-client.d.ts +19 -0
- package/dist/src/signal/signal-rpc-client.d.ts.map +1 -0
- package/dist/src/signal/signal-rpc-client.js +108 -0
- package/dist/src/signal/signal-rpc-client.js.map +1 -0
- package/dist/src/signal/signal-rpc-client.test.d.ts +2 -0
- package/dist/src/signal/signal-rpc-client.test.d.ts.map +1 -0
- package/dist/src/signal/signal-rpc-client.test.js +74 -0
- package/dist/src/signal/signal-rpc-client.test.js.map +1 -0
- package/dist/src/swarm/connection.d.ts +3 -3
- package/dist/src/swarm/connection.d.ts.map +1 -1
- package/dist/src/swarm/connection.js +8 -11
- package/dist/src/swarm/connection.js.map +1 -1
- package/dist/src/swarm/swarm.d.ts +6 -7
- package/dist/src/swarm/swarm.d.ts.map +1 -1
- package/dist/src/swarm/swarm.js +29 -25
- package/dist/src/swarm/swarm.js.map +1 -1
- package/dist/src/swarm/swarm.test.js +156 -115
- package/dist/src/swarm/swarm.test.js.map +1 -1
- package/dist/src/testing/test-protocol.d.ts.map +1 -1
- package/dist/src/testing/test-protocol.js +3 -3
- package/dist/src/testing/test-protocol.js.map +1 -1
- package/dist/src/topology/fully-connected-topology.d.ts +0 -1
- package/dist/src/topology/fully-connected-topology.d.ts.map +1 -1
- package/dist/src/topology/fully-connected-topology.js +4 -9
- package/dist/src/topology/fully-connected-topology.js.map +1 -1
- package/dist/src/topology/mmst-topology.d.ts +0 -1
- package/dist/src/topology/mmst-topology.d.ts.map +1 -1
- package/dist/src/topology/mmst-topology.js +6 -11
- package/dist/src/topology/mmst-topology.js.map +1 -1
- package/dist/src/topology/star-topology.d.ts +0 -1
- package/dist/src/topology/star-topology.d.ts.map +1 -1
- package/dist/src/topology/star-topology.js +5 -10
- package/dist/src/topology/star-topology.js.map +1 -1
- package/dist/src/topology/topology.d.ts +0 -6
- package/dist/src/topology/topology.d.ts.map +1 -1
- package/dist/src/transport/in-memory-transport.d.ts +2 -2
- package/dist/src/transport/in-memory-transport.d.ts.map +1 -1
- package/dist/src/transport/in-memory-transport.js +2 -2
- package/dist/src/transport/in-memory-transport.js.map +1 -1
- package/dist/src/transport/transport.d.ts +3 -3
- package/dist/src/transport/transport.d.ts.map +1 -1
- package/dist/src/transport/webrtc-transport.d.ts +3 -3
- package/dist/src/transport/webrtc-transport.d.ts.map +1 -1
- package/dist/src/transport/webrtc-transport.js +3 -3
- package/dist/src/transport/webrtc-transport.js.map +1 -1
- package/dist/tests-setup.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -12
- package/src/network-manager.blueprint-test.ts +57 -22
- package/src/network-manager.browser-test.ts +1 -1
- package/src/network-manager.test.ts +8 -7
- package/src/network-manager.ts +10 -10
- package/src/proto/defs/dxos/mesh/signal.proto +54 -23
- package/src/proto/defs/dxos/mesh/signalMessage.proto +51 -0
- package/src/proto/gen/dxos/credentials.ts +40 -0
- package/src/proto/gen/dxos/halo/keys.ts +45 -2
- package/src/proto/gen/dxos/mesh/signal.ts +73 -16
- package/src/proto/gen/dxos/mesh/signalMessage.ts +83 -0
- package/src/proto/gen/google/protobuf.ts +9 -2
- package/src/proto/gen/index.ts +18 -5
- package/src/proto/substitutions.ts +3 -1
- package/src/protocol-factory.ts +1 -1
- package/src/signal/in-memory-signal-manager.ts +38 -13
- package/src/signal/index.ts +1 -2
- package/src/signal/integration.test.ts +117 -0
- package/src/signal/message-router.test.ts +169 -58
- package/src/signal/message-router.ts +120 -27
- package/src/signal/signal-client.test.ts +70 -90
- package/src/signal/signal-client.ts +120 -87
- package/src/signal/signal-manager-impl.ts +166 -0
- package/src/signal/signal-manager.ts +12 -12
- package/src/signal/signal-rpc-client.test.ts +86 -0
- package/src/signal/signal-rpc-client.ts +121 -0
- package/src/swarm/connection.ts +6 -9
- package/src/swarm/swarm.test.ts +208 -167
- package/src/swarm/swarm.ts +26 -22
- package/src/testing/test-protocol.ts +1 -1
- package/src/topology/fully-connected-topology.ts +2 -10
- package/src/topology/mmst-topology.ts +2 -10
- package/src/topology/star-topology.ts +2 -8
- package/src/topology/topology.ts +0 -7
- package/src/transport/in-memory-transport.ts +3 -3
- package/src/transport/transport.ts +3 -3
- package/src/transport/webrtc-transport.ts +4 -4
- package/dist/src/signal/websocket-rpc.d.ts +0 -30
- package/dist/src/signal/websocket-rpc.d.ts.map +0 -1
- package/dist/src/signal/websocket-rpc.js +0 -203
- package/dist/src/signal/websocket-rpc.js.map +0 -1
- package/dist/src/signal/websocket-signal-manager.d.ts.map +0 -1
- package/dist/src/signal/websocket-signal-manager.js +0 -134
- package/dist/src/signal/websocket-signal-manager.js.map +0 -1
- package/src/signal/websocket-rpc.ts +0 -208
- package/src/signal/websocket-signal-manager.ts +0 -158
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2020 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
|
+
|
|
8
|
+
import { Event, synchronized } from '@dxos/async';
|
|
9
|
+
import { PublicKey } from '@dxos/protocols';
|
|
10
|
+
import { ComplexMap } from '@dxos/util';
|
|
11
|
+
|
|
12
|
+
import { SwarmEvent } from '../proto/gen/dxos/mesh/signal';
|
|
13
|
+
import { SignalMessage } from '../proto/gen/dxos/mesh/signalMessage';
|
|
14
|
+
import { SignalApi } from './signal-api';
|
|
15
|
+
import { SignalClient } from './signal-client';
|
|
16
|
+
import { SignalManager } from './signal-manager';
|
|
17
|
+
|
|
18
|
+
const log = debug('dxos:network-manager:signal-manager-impl');
|
|
19
|
+
|
|
20
|
+
export class SignalManagerImpl implements SignalManager {
|
|
21
|
+
private readonly _servers = new Map<string, SignalClient>();
|
|
22
|
+
|
|
23
|
+
/** Topics joined: topic => peerId */
|
|
24
|
+
private readonly _topicsJoined = new ComplexMap<PublicKey, PublicKey>(topic => topic.toHex());
|
|
25
|
+
/** host => topic => peerId */
|
|
26
|
+
private readonly _topicsJoinedPerSignal = new Map<string, ComplexMap<PublicKey, PublicKey>>();
|
|
27
|
+
|
|
28
|
+
private _reconciling?: boolean = false;
|
|
29
|
+
private _reconcileTimeoutId?: NodeJS.Timeout;
|
|
30
|
+
private _destroyed = false;
|
|
31
|
+
|
|
32
|
+
readonly statusChanged = new Event<SignalApi.Status[]>();
|
|
33
|
+
readonly commandTrace = new Event<SignalApi.CommandTrace>();
|
|
34
|
+
readonly swarmEvent = new Event<[topic: PublicKey, swarmEvent: SwarmEvent]>();
|
|
35
|
+
readonly onMessage = new Event<SignalMessage>();
|
|
36
|
+
|
|
37
|
+
constructor (
|
|
38
|
+
private readonly _hosts: string[]
|
|
39
|
+
) {
|
|
40
|
+
log(`Created WebsocketSignalManager with signal servers: ${_hosts}`);
|
|
41
|
+
assert(_hosts.length === 1, 'Only a single signaling server connection is supported');
|
|
42
|
+
for (const host of this._hosts) {
|
|
43
|
+
const server = new SignalClient(
|
|
44
|
+
host,
|
|
45
|
+
async msg => this.onMessage.emit(msg)
|
|
46
|
+
);
|
|
47
|
+
// TODO(mykola): Add subscription group
|
|
48
|
+
server.swarmEvent.on(data => this.swarmEvent.emit(data));
|
|
49
|
+
server.statusChanged.on(() => this.statusChanged.emit(this.getStatus()));
|
|
50
|
+
|
|
51
|
+
this._servers.set(host, server);
|
|
52
|
+
server.commandTrace.on(trace => this.commandTrace.emit(trace));
|
|
53
|
+
this._topicsJoinedPerSignal.set(host, new ComplexMap(x => x.toHex()));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getStatus (): SignalApi.Status[] {
|
|
58
|
+
return Array.from(this._servers.values()).map(server => server.getStatus());
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
join (topic: PublicKey, peerId: PublicKey) {
|
|
62
|
+
assert(!this._topicsJoined.has(topic), `Topic ${topic} is already joined`);
|
|
63
|
+
log(`Join ${topic} ${peerId}`);
|
|
64
|
+
this._topicsJoined.set(topic, peerId);
|
|
65
|
+
this._scheduleReconcile();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
leave (topic: PublicKey, peerId: PublicKey) {
|
|
69
|
+
assert(!!this._topicsJoined.has(topic), `Topic ${topic} was not joined`);
|
|
70
|
+
log(`Leave ${topic} ${peerId}`);
|
|
71
|
+
this._topicsJoined.delete(topic);
|
|
72
|
+
this._scheduleReconcile();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private _scheduleReconcile () {
|
|
76
|
+
if (this._destroyed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!this._reconciling) {
|
|
81
|
+
this._reconciling = true;
|
|
82
|
+
this._reconcileJoinedTopics().then(
|
|
83
|
+
() => {
|
|
84
|
+
this._reconciling = false;
|
|
85
|
+
},
|
|
86
|
+
err => {
|
|
87
|
+
this._reconciling = false;
|
|
88
|
+
log(`Error while reconciling: ${err}`);
|
|
89
|
+
this._reconcileLater();
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
this._reconcileLater();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private _reconcileLater () {
|
|
98
|
+
if (this._destroyed) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!this._reconcileTimeoutId) {
|
|
103
|
+
this._reconcileTimeoutId = setTimeout(async () => this._scheduleReconcile(), 3000);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@synchronized
|
|
108
|
+
private async _reconcileJoinedTopics () {
|
|
109
|
+
// TODO(mykola): Handle reconnects to SS. Maybe move map of joined topics to signal-client.
|
|
110
|
+
log('Reconciling..');
|
|
111
|
+
for (const [host, server] of this._servers) {
|
|
112
|
+
const actualJoinedTopics = this._topicsJoinedPerSignal.get(host)!;
|
|
113
|
+
|
|
114
|
+
// Leave swarms
|
|
115
|
+
for (const [topic, actualPeerId] of actualJoinedTopics) {
|
|
116
|
+
try {
|
|
117
|
+
const desiredPeerId = this._topicsJoined.get(topic);
|
|
118
|
+
if (!desiredPeerId || !desiredPeerId.equals(actualPeerId)) {
|
|
119
|
+
await server.leave(topic, actualPeerId);
|
|
120
|
+
actualJoinedTopics.delete(topic);
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
log(`Error leaving swarm: ${err}`);
|
|
124
|
+
this._scheduleReconcile();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Join swarms
|
|
129
|
+
for (const [topic, desiredPeerId] of this._topicsJoined) {
|
|
130
|
+
try {
|
|
131
|
+
const actualPeerId = actualJoinedTopics.get(topic);
|
|
132
|
+
if (!actualPeerId) {
|
|
133
|
+
await server.join(topic, desiredPeerId);
|
|
134
|
+
actualJoinedTopics.set(topic, desiredPeerId);
|
|
135
|
+
} else {
|
|
136
|
+
if (!actualPeerId.equals(desiredPeerId)) {
|
|
137
|
+
throw new Error(`Joined with peerId different from desired: ${JSON.stringify({ actualPeerId, desiredPeerId })}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
log(`Error joining swarm: ${err}`);
|
|
142
|
+
this._scheduleReconcile();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
log('Done reconciling..');
|
|
147
|
+
this._reconciling = false;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async message (msg: SignalMessage) {
|
|
151
|
+
log(`Signal ${msg.remoteId}`);
|
|
152
|
+
for (const server of this._servers.values()) {
|
|
153
|
+
server.signal(msg).catch(err => {
|
|
154
|
+
log(`Error signaling: ${err}`);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async destroy () {
|
|
160
|
+
this._destroyed = true;
|
|
161
|
+
if (this._reconcileTimeoutId) {
|
|
162
|
+
clearTimeout(this._reconcileTimeoutId);
|
|
163
|
+
}
|
|
164
|
+
await Promise.all(Array.from(this._servers.values()).map(server => server.close()));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -5,18 +5,14 @@
|
|
|
5
5
|
import { Event } from '@dxos/async';
|
|
6
6
|
import { PublicKey } from '@dxos/protocols';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { SwarmEvent } from '../proto/gen/dxos/mesh/signal';
|
|
9
|
+
import { Answer, SignalMessage } from '../proto/gen/dxos/mesh/signalMessage';
|
|
9
10
|
import { SignalApi } from './signal-api';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Signal peer discovery interface.
|
|
13
14
|
*/
|
|
14
15
|
export interface SignalConnection {
|
|
15
|
-
/**
|
|
16
|
-
* Find peers (triggers async event).
|
|
17
|
-
*/
|
|
18
|
-
lookup (topic: PublicKey): void
|
|
19
|
-
|
|
20
16
|
/**
|
|
21
17
|
* Join topic on signal network, to be discoverable by other peers.
|
|
22
18
|
*/
|
|
@@ -35,20 +31,24 @@ export interface SignalMessaging {
|
|
|
35
31
|
/**
|
|
36
32
|
* Offer/answer RPC.
|
|
37
33
|
*/
|
|
38
|
-
offer (msg:
|
|
34
|
+
offer (msg: SignalMessage): Promise<Answer>
|
|
39
35
|
|
|
40
36
|
/**
|
|
41
|
-
*
|
|
37
|
+
* Reliably send a signal to a peer.
|
|
42
38
|
*/
|
|
43
|
-
signal (msg:
|
|
39
|
+
signal (msg: SignalMessage): Promise<void>
|
|
44
40
|
}
|
|
45
41
|
|
|
46
|
-
export interface SignalManager extends SignalConnection
|
|
42
|
+
export interface SignalManager extends SignalConnection {
|
|
47
43
|
statusChanged: Event<SignalApi.Status[]>
|
|
48
44
|
commandTrace: Event<SignalApi.CommandTrace>
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
swarmEvent: Event<[topic: PublicKey, swarmEvent: SwarmEvent]>
|
|
46
|
+
onMessage: Event<SignalMessage>
|
|
51
47
|
|
|
52
48
|
getStatus (): SignalApi.Status[]
|
|
53
49
|
destroy(): Promise<void>
|
|
50
|
+
/**
|
|
51
|
+
* Send message to peer.
|
|
52
|
+
*/
|
|
53
|
+
message (msg: SignalMessage): Promise<void>
|
|
54
54
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
import { expect } from 'earljs';
|
|
5
|
+
|
|
6
|
+
import { Any } from '@dxos/codec-protobuf';
|
|
7
|
+
import { PublicKey } from '@dxos/protocols';
|
|
8
|
+
import { createTestBroker, TestBroker } from '@dxos/signal';
|
|
9
|
+
|
|
10
|
+
import { Message, SwarmEvent } from '../proto/gen/dxos/mesh/signal';
|
|
11
|
+
import { SignalRPCClient } from './signal-rpc-client';
|
|
12
|
+
|
|
13
|
+
describe('SignalRPCClient', () => {
|
|
14
|
+
let broker: TestBroker;
|
|
15
|
+
|
|
16
|
+
before(async () => {
|
|
17
|
+
broker = await createTestBroker();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
after(() => {
|
|
21
|
+
broker.stop();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const setup = async () => {
|
|
25
|
+
const client = new SignalRPCClient(broker.url());
|
|
26
|
+
return client;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
it('signal between 2 peers', async () => {
|
|
30
|
+
const client1 = await setup();
|
|
31
|
+
const client2 = await setup();
|
|
32
|
+
|
|
33
|
+
const peerId1 = PublicKey.random();
|
|
34
|
+
const peerId2 = PublicKey.random();
|
|
35
|
+
|
|
36
|
+
const stream1 = await client1.receiveMessages(peerId1);
|
|
37
|
+
const message: Any = {
|
|
38
|
+
'type_url': 'test',
|
|
39
|
+
value: Uint8Array.from([1, 2, 3])
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
await client2.sendMessage(peerId2, peerId1, message);
|
|
43
|
+
|
|
44
|
+
const received: Message = await new Promise(resolve => {
|
|
45
|
+
stream1.subscribe(message => {
|
|
46
|
+
resolve(message);
|
|
47
|
+
}, (error) => {
|
|
48
|
+
if (error) {
|
|
49
|
+
console.log(error);
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
expect(received.author).toEqual(peerId2.asUint8Array());
|
|
55
|
+
stream1.close();
|
|
56
|
+
}).timeout(10000);
|
|
57
|
+
|
|
58
|
+
it('join', async () => {
|
|
59
|
+
const client1 = await setup();
|
|
60
|
+
const client2 = await setup();
|
|
61
|
+
|
|
62
|
+
const peerId1 = PublicKey.random();
|
|
63
|
+
const peerId2 = PublicKey.random();
|
|
64
|
+
const topic = PublicKey.random();
|
|
65
|
+
|
|
66
|
+
const stream1 = await client1.join(topic, peerId1);
|
|
67
|
+
const promise = new Promise<SwarmEvent>(resolve => {
|
|
68
|
+
stream1.subscribe((event: SwarmEvent) => {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
|
70
|
+
if (peerId2.equals(event.peerAvailable?.peer!)) {
|
|
71
|
+
resolve(event);
|
|
72
|
+
}
|
|
73
|
+
}, (error) => {
|
|
74
|
+
if (error) {
|
|
75
|
+
console.log(error);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
const stream2 = await client2.join(topic, peerId2);
|
|
81
|
+
|
|
82
|
+
expect((await promise).peerAvailable?.peer).toEqual(peerId2.asBuffer());
|
|
83
|
+
stream1.close();
|
|
84
|
+
stream2.close();
|
|
85
|
+
}).timeout(10000);
|
|
86
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2022 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import debug from 'debug';
|
|
6
|
+
import WebSocket from 'isomorphic-ws';
|
|
7
|
+
|
|
8
|
+
import { Trigger, Event } from '@dxos/async';
|
|
9
|
+
import { Any, Stream } from '@dxos/codec-protobuf';
|
|
10
|
+
import { PublicKey } from '@dxos/protocols';
|
|
11
|
+
import { createBundledRpcClient, ProtoRpcClient } from '@dxos/rpc';
|
|
12
|
+
|
|
13
|
+
import { schema } from '../proto/gen';
|
|
14
|
+
import { Message, Signal } from '../proto/gen/dxos/mesh/signal';
|
|
15
|
+
interface Services {
|
|
16
|
+
Signal: Signal
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const log = debug('dxos:network-manager:signal-rpc-client');
|
|
20
|
+
|
|
21
|
+
export class SignalRPCClient {
|
|
22
|
+
private readonly _socket: WebSocket
|
|
23
|
+
private readonly _rpc: ProtoRpcClient<Services>
|
|
24
|
+
private readonly _connectTrigger = new Trigger();
|
|
25
|
+
|
|
26
|
+
readonly connected = new Event();
|
|
27
|
+
readonly disconnected = new Event();
|
|
28
|
+
readonly error = new Event<Error>();
|
|
29
|
+
|
|
30
|
+
constructor (
|
|
31
|
+
private readonly _url: string
|
|
32
|
+
) {
|
|
33
|
+
this._socket = new WebSocket(this._url);
|
|
34
|
+
this._socket.onopen = async () => {
|
|
35
|
+
try {
|
|
36
|
+
await this._rpc.open();
|
|
37
|
+
log(`RPC open ${this._url}`);
|
|
38
|
+
this.connected.emit();
|
|
39
|
+
this._connectTrigger.wake();
|
|
40
|
+
} catch (err: any) {
|
|
41
|
+
this.error.emit(err);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
this._socket.onclose = async () => {
|
|
46
|
+
log(`Disconnected ${this._url}`);
|
|
47
|
+
this.disconnected.emit();
|
|
48
|
+
try {
|
|
49
|
+
await this._rpc.close();
|
|
50
|
+
} catch (err: any) {
|
|
51
|
+
this.error.emit(err);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
this._socket.onerror = (e: WebSocket.ErrorEvent) => {
|
|
56
|
+
log(`Signal socket error ${this._url} ${e.message}`);
|
|
57
|
+
this.error.emit(e.error ?? new Error(e.message));
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this._rpc = createBundledRpcClient(
|
|
61
|
+
{
|
|
62
|
+
Signal: schema.getService('dxos.mesh.signal.Signal')
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
noHandshake: true,
|
|
66
|
+
port: {
|
|
67
|
+
send: msg => {
|
|
68
|
+
this._socket.send(msg);
|
|
69
|
+
},
|
|
70
|
+
subscribe: cb => {
|
|
71
|
+
this._socket.onmessage = async (msg: WebSocket.MessageEvent) => {
|
|
72
|
+
if (typeof Blob !== 'undefined' && msg.data instanceof Blob) {
|
|
73
|
+
cb(Buffer.from(await msg.data.arrayBuffer()));
|
|
74
|
+
} else {
|
|
75
|
+
cb(msg.data as any);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async join (topic: PublicKey, peerId: PublicKey) {
|
|
85
|
+
log('join', topic, peerId);
|
|
86
|
+
await this._connectTrigger.wait();
|
|
87
|
+
const swarmStream = this._rpc.rpc.Signal.join({
|
|
88
|
+
swarm: topic.asUint8Array(),
|
|
89
|
+
peer: peerId.asUint8Array()
|
|
90
|
+
});
|
|
91
|
+
await swarmStream.waitUntilReady();
|
|
92
|
+
return swarmStream;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async receiveMessages (peerId: PublicKey): Promise<Stream<Message>> {
|
|
96
|
+
await this._connectTrigger.wait();
|
|
97
|
+
const messageStream = this._rpc.rpc.Signal.receiveMessages({
|
|
98
|
+
peer: peerId.asUint8Array()
|
|
99
|
+
});
|
|
100
|
+
await messageStream.waitUntilReady();
|
|
101
|
+
return messageStream;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async sendMessage (author: PublicKey, recipient: PublicKey, message: Any) {
|
|
105
|
+
log('sendMessage', author, recipient, message);
|
|
106
|
+
await this._connectTrigger.wait();
|
|
107
|
+
await this._rpc.rpc.Signal.sendMessage({
|
|
108
|
+
author: author.asUint8Array(),
|
|
109
|
+
recipient: recipient.asUint8Array(),
|
|
110
|
+
payload: message
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async close () {
|
|
115
|
+
try {
|
|
116
|
+
await this._rpc.close();
|
|
117
|
+
} finally {
|
|
118
|
+
this._socket.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/swarm/connection.ts
CHANGED
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
// Copyright 2021 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
import assert from 'assert';
|
|
6
5
|
import debug from 'debug';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
7
|
|
|
8
8
|
import { Event, synchronized } from '@dxos/async';
|
|
9
9
|
import { ErrorStream } from '@dxos/debug';
|
|
10
10
|
import { Protocol } from '@dxos/mesh-protocol';
|
|
11
11
|
import { PublicKey } from '@dxos/protocols';
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import { SignalMessage } from '../proto/gen/dxos/mesh/signalMessage';
|
|
14
14
|
import { Transport, TransportFactory } from '../transport';
|
|
15
15
|
|
|
16
16
|
const log = debug('dxos:network-manager:swarm:connection');
|
|
@@ -51,7 +51,7 @@ export enum ConnectionState {
|
|
|
51
51
|
export class Connection {
|
|
52
52
|
private _state: ConnectionState = ConnectionState.INITIAL;
|
|
53
53
|
private _transport: Transport | undefined;
|
|
54
|
-
private _bufferedSignals:
|
|
54
|
+
private _bufferedSignals: SignalMessage[] = [];
|
|
55
55
|
|
|
56
56
|
readonly stateChanged = new Event<ConnectionState>();
|
|
57
57
|
readonly errors = new ErrorStream();
|
|
@@ -62,7 +62,7 @@ export class Connection {
|
|
|
62
62
|
public readonly remoteId: PublicKey,
|
|
63
63
|
public readonly sessionId: PublicKey,
|
|
64
64
|
public readonly initiator: boolean,
|
|
65
|
-
private readonly _sendSignal: (msg:
|
|
65
|
+
private readonly _sendSignal: (msg: SignalMessage) => Promise<void>,
|
|
66
66
|
private readonly _protocol: Protocol,
|
|
67
67
|
private readonly _transportFactory: TransportFactory
|
|
68
68
|
) {}
|
|
@@ -116,16 +116,13 @@ export class Connection {
|
|
|
116
116
|
this._bufferedSignals = [];
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
async signal (msg:
|
|
119
|
+
async signal (msg: SignalMessage) {
|
|
120
120
|
assert(msg.sessionId);
|
|
121
121
|
if (!msg.sessionId.equals(this.sessionId)) {
|
|
122
122
|
log('Dropping signal for incorrect session id.');
|
|
123
123
|
return;
|
|
124
124
|
}
|
|
125
|
-
assert(msg.data);
|
|
126
|
-
if (msg.data.offer && this._state === ConnectionState.INITIATING_CONNECTION) {
|
|
127
|
-
throw new Error('Invalid state: Cannot send offer to an initiating peer.');
|
|
128
|
-
}
|
|
125
|
+
assert(msg.data.signal);
|
|
129
126
|
assert(msg.id?.equals(this.remoteId));
|
|
130
127
|
assert(msg.remoteId?.equals(this.ownId));
|
|
131
128
|
|