@dxos/network-manager 0.10.0 → 0.11.1
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/lib/chunk-wire-protocol.mjs +3554 -0
- package/dist/lib/chunk-wire-protocol.mjs.map +1 -0
- package/dist/lib/index.mjs +294 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing.mjs +237 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/lib/transport/tcp/tcp-transport.browser.mjs +34 -0
- package/dist/lib/transport/tcp/tcp-transport.browser.mjs.map +1 -0
- package/dist/lib/transport/tcp/tcp-transport.mjs +140 -0
- package/dist/lib/transport/tcp/tcp-transport.mjs.map +1 -0
- package/dist/lib/transport/tcp.mjs +1 -0
- package/dist/types/src/network-manager.d.ts +9 -0
- package/dist/types/src/network-manager.d.ts.map +1 -1
- package/dist/types/src/signal/swarm-messenger.d.ts +2 -7
- package/dist/types/src/signal/swarm-messenger.d.ts.map +1 -1
- package/dist/types/src/testing/test-builder.d.ts +2 -6
- package/dist/types/src/testing/test-builder.d.ts.map +1 -1
- package/dist/types/src/transport/webrtc/rtc-peer-connection.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +28 -31
- package/src/network-manager.ts +17 -4
- package/src/signal/integration.node.test.ts +7 -11
- package/src/signal/swarm-messenger.node.test.ts +7 -29
- package/src/signal/swarm-messenger.ts +3 -13
- package/src/swarm/swarm.test.ts +2 -2
- package/src/testing/test-builder.ts +48 -75
- package/src/tests/webrtc-transport.test.ts +4 -15
- package/src/transport/webrtc/rtc-peer-connection.ts +0 -3
- package/src/transport/webrtc/rtc-transport-proxy.test.ts +2 -1
- package/src/transport/webrtc/rtc-transport.test.ts +2 -1
- package/dist/lib/browser/chunk-ERB3AQAF.mjs +0 -3095
- package/dist/lib/browser/chunk-ERB3AQAF.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -244
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/testing/index.mjs +0 -280
- package/dist/lib/browser/testing/index.mjs.map +0 -7
- package/dist/lib/browser/transport/tcp/index.mjs +0 -5
- package/dist/lib/browser/transport/tcp/index.mjs.map +0 -7
- package/dist/lib/browser/transport/tcp/tcp-transport.browser.mjs +0 -36
- package/dist/lib/browser/transport/tcp/tcp-transport.browser.mjs.map +0 -7
- package/dist/lib/browser/transport/tcp/tcp-transport.mjs +0 -125
- package/dist/lib/browser/transport/tcp/tcp-transport.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-KYLPLEZQ.mjs +0 -3095
- package/dist/lib/node-esm/chunk-KYLPLEZQ.mjs.map +0 -7
- package/dist/lib/node-esm/index.mjs +0 -244
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
- package/dist/lib/node-esm/testing/index.mjs +0 -280
- package/dist/lib/node-esm/testing/index.mjs.map +0 -7
- package/dist/lib/node-esm/transport/tcp/index.mjs +0 -5
- package/dist/lib/node-esm/transport/tcp/index.mjs.map +0 -7
- package/dist/lib/node-esm/transport/tcp/tcp-transport.browser.mjs +0 -36
- package/dist/lib/node-esm/transport/tcp/tcp-transport.browser.mjs.map +0 -7
- package/dist/lib/node-esm/transport/tcp/tcp-transport.mjs +0 -125
- package/dist/lib/node-esm/transport/tcp/tcp-transport.mjs.map +0 -7
|
@@ -0,0 +1,3554 @@
|
|
|
1
|
+
import { DeferredTask, Event, Mutex, SubscriptionList, Trigger, scheduleTask, scheduleTaskInterval, sleep, synchronized } from "@dxos/async";
|
|
2
|
+
import { ErrorStream, raise } from "@dxos/debug";
|
|
3
|
+
import { PublicKey } from "@dxos/keys";
|
|
4
|
+
import { ComplexMap, arrayToBuffer, isNonNullable } from "@dxos/util";
|
|
5
|
+
import { Context, ContextDisposedError, Resource, cancelWithContext } from "@dxos/context";
|
|
6
|
+
import { assertArgument, invariant } from "@dxos/invariant";
|
|
7
|
+
import { log, logInfo } from "@dxos/log";
|
|
8
|
+
import { CancelledError, ConnectionResetError, ConnectivityError, ProtocolError, SystemError, TimeoutError } from "@dxos/protocols";
|
|
9
|
+
import { Messenger, PeerInfoHash } from "@dxos/messaging";
|
|
10
|
+
import { schema } from "@dxos/protocols/proto";
|
|
11
|
+
import * as EffectContext from "effect/Context";
|
|
12
|
+
import { ConnectionState } from "@dxos/protocols/proto/dxos/client/services";
|
|
13
|
+
import { Duplex, Transform, Writable } from "@dxos/node-std/stream";
|
|
14
|
+
import { ConnectionState as ConnectionState$1 } from "@dxos/protocols/proto/dxos/mesh/bridge";
|
|
15
|
+
import { Stream } from "@dxos/codec-protobuf/stream";
|
|
16
|
+
import { Teleport } from "@dxos/teleport";
|
|
17
|
+
//#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
|
|
18
|
+
function __decorate(decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/swarm/connection.ts
|
|
26
|
+
var __dxlog_file$12 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/connection.ts";
|
|
27
|
+
/**
|
|
28
|
+
* How long to wait before sending the signal in case we receive another signal.
|
|
29
|
+
* This value is increased exponentially.
|
|
30
|
+
*/
|
|
31
|
+
var STARTING_SIGNALLING_DELAY = 10;
|
|
32
|
+
/**
|
|
33
|
+
* How long to wait for the transport to establish connectivity, i.e. for the connection to move between CONNECTING and CONNECTED.
|
|
34
|
+
*/
|
|
35
|
+
var TRANSPORT_CONNECTION_TIMEOUT = 1e4;
|
|
36
|
+
var TRANSPORT_STATS_INTERVAL = 5e3;
|
|
37
|
+
/**
|
|
38
|
+
* Maximum delay between signal batches.
|
|
39
|
+
*/
|
|
40
|
+
var MAX_SIGNALLING_DELAY = 300;
|
|
41
|
+
/**
|
|
42
|
+
* State machine for each connection.
|
|
43
|
+
*/
|
|
44
|
+
var ConnectionState$2 = /* @__PURE__ */ function(ConnectionState) {
|
|
45
|
+
/**
|
|
46
|
+
* Connection is created, but not yet passed through the connection limiter.
|
|
47
|
+
*/
|
|
48
|
+
ConnectionState["CREATED"] = "CREATED";
|
|
49
|
+
/**
|
|
50
|
+
* Initial state. Connection is registered but no attempt to connect to the remote peer has been performed.
|
|
51
|
+
* Might mean that we are waiting for the answer signal from the remote peer.
|
|
52
|
+
*/
|
|
53
|
+
ConnectionState["INITIAL"] = "INITIAL";
|
|
54
|
+
/**
|
|
55
|
+
* Trying to establish connection.
|
|
56
|
+
*/
|
|
57
|
+
ConnectionState["CONNECTING"] = "CONNECTING";
|
|
58
|
+
/**
|
|
59
|
+
* Connection is established.
|
|
60
|
+
*/
|
|
61
|
+
ConnectionState["CONNECTED"] = "CONNECTED";
|
|
62
|
+
/**
|
|
63
|
+
* Connection is being closed.
|
|
64
|
+
*/
|
|
65
|
+
ConnectionState["CLOSING"] = "CLOSING";
|
|
66
|
+
/**
|
|
67
|
+
* Connection closed.
|
|
68
|
+
*/
|
|
69
|
+
ConnectionState["CLOSED"] = "CLOSED";
|
|
70
|
+
ConnectionState["ABORTING"] = "ABORTING";
|
|
71
|
+
ConnectionState["ABORTED"] = "ABORTED";
|
|
72
|
+
return ConnectionState;
|
|
73
|
+
}({});
|
|
74
|
+
/**
|
|
75
|
+
* Represents a connection to a remote peer.
|
|
76
|
+
* Owns a transport paired together with a wire-protocol.
|
|
77
|
+
*/
|
|
78
|
+
var Connection = class {
|
|
79
|
+
topic;
|
|
80
|
+
localInfo;
|
|
81
|
+
remoteInfo;
|
|
82
|
+
sessionId;
|
|
83
|
+
initiator;
|
|
84
|
+
_signalMessaging;
|
|
85
|
+
_protocol;
|
|
86
|
+
_transportFactory;
|
|
87
|
+
_callbacks;
|
|
88
|
+
_ctx = new Context(void 0, {
|
|
89
|
+
"~LogMeta": "~LogMeta",
|
|
90
|
+
F: __dxlog_file$12,
|
|
91
|
+
L: 93
|
|
92
|
+
});
|
|
93
|
+
connectedTimeoutContext = new Context(void 0, {
|
|
94
|
+
"~LogMeta": "~LogMeta",
|
|
95
|
+
F: __dxlog_file$12,
|
|
96
|
+
L: 94
|
|
97
|
+
});
|
|
98
|
+
_protocolClosed = new Trigger();
|
|
99
|
+
_transportClosed = new Trigger();
|
|
100
|
+
_state = "CREATED";
|
|
101
|
+
_transport;
|
|
102
|
+
closeReason;
|
|
103
|
+
_incomingSignalBuffer = [];
|
|
104
|
+
_outgoingSignalBuffer = [];
|
|
105
|
+
stateChanged = new Event();
|
|
106
|
+
errors = new ErrorStream();
|
|
107
|
+
transportStats = new Event();
|
|
108
|
+
_signalSendTask = new DeferredTask(this._ctx, async () => {
|
|
109
|
+
await this._flushSignalBuffer(this._ctx);
|
|
110
|
+
});
|
|
111
|
+
_signallingDelay = STARTING_SIGNALLING_DELAY;
|
|
112
|
+
constructor(topic, localInfo, remoteInfo, sessionId, initiator, _signalMessaging, _protocol, _transportFactory, _callbacks) {
|
|
113
|
+
this.topic = topic;
|
|
114
|
+
this.localInfo = localInfo;
|
|
115
|
+
this.remoteInfo = remoteInfo;
|
|
116
|
+
this.sessionId = sessionId;
|
|
117
|
+
this.initiator = initiator;
|
|
118
|
+
this._signalMessaging = _signalMessaging;
|
|
119
|
+
this._protocol = _protocol;
|
|
120
|
+
this._transportFactory = _transportFactory;
|
|
121
|
+
this._callbacks = _callbacks;
|
|
122
|
+
log.trace("dxos.mesh.connection.construct", {
|
|
123
|
+
sessionId: this.sessionId,
|
|
124
|
+
topic: this.topic,
|
|
125
|
+
localPeer: this.localInfo,
|
|
126
|
+
remotePeer: this.remoteInfo,
|
|
127
|
+
initiator: this.initiator
|
|
128
|
+
}, {
|
|
129
|
+
"~LogMeta": "~LogMeta",
|
|
130
|
+
F: __dxlog_file$12,
|
|
131
|
+
L: 128,
|
|
132
|
+
S: this
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
get sessionIdString() {
|
|
136
|
+
return this.sessionId.truncate();
|
|
137
|
+
}
|
|
138
|
+
get state() {
|
|
139
|
+
return this._state;
|
|
140
|
+
}
|
|
141
|
+
get transport() {
|
|
142
|
+
return this._transport;
|
|
143
|
+
}
|
|
144
|
+
get protocol() {
|
|
145
|
+
return this._protocol;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create an underlying transport and prepares it for the connection.
|
|
149
|
+
*/
|
|
150
|
+
async openConnection() {
|
|
151
|
+
invariant(this._state === "INITIAL", "Invalid state.", {
|
|
152
|
+
"~LogMeta": "~LogMeta",
|
|
153
|
+
F: __dxlog_file$12,
|
|
154
|
+
L: 158,
|
|
155
|
+
S: this,
|
|
156
|
+
A: ["this._state === ConnectionState.INITIAL", "'Invalid state.'"]
|
|
157
|
+
});
|
|
158
|
+
log("opening connection", void 0, {
|
|
159
|
+
"~LogMeta": "~LogMeta",
|
|
160
|
+
F: __dxlog_file$12,
|
|
161
|
+
L: 159,
|
|
162
|
+
S: this
|
|
163
|
+
});
|
|
164
|
+
log.trace("dxos.mesh.connection.open", {
|
|
165
|
+
sessionId: this.sessionId,
|
|
166
|
+
topic: this.topic,
|
|
167
|
+
localPeerId: this.localInfo,
|
|
168
|
+
remotePeerId: this.remoteInfo,
|
|
169
|
+
initiator: this.initiator
|
|
170
|
+
}, {
|
|
171
|
+
"~LogMeta": "~LogMeta",
|
|
172
|
+
F: __dxlog_file$12,
|
|
173
|
+
L: 160,
|
|
174
|
+
S: this
|
|
175
|
+
});
|
|
176
|
+
this._changeState("CONNECTING");
|
|
177
|
+
this._protocol.open(this.sessionId).catch((err) => {
|
|
178
|
+
this.errors.raise(err);
|
|
179
|
+
});
|
|
180
|
+
this._protocol.stream.on("close", () => {
|
|
181
|
+
log("protocol stream closed", void 0, {
|
|
182
|
+
"~LogMeta": "~LogMeta",
|
|
183
|
+
F: __dxlog_file$12,
|
|
184
|
+
L: 177,
|
|
185
|
+
S: this
|
|
186
|
+
});
|
|
187
|
+
this._protocolClosed.wake();
|
|
188
|
+
this.close({ error: new ProtocolError({ message: "protocol stream closed" }) }).catch((err) => this.errors.raise(err));
|
|
189
|
+
});
|
|
190
|
+
scheduleTask(this.connectedTimeoutContext, async () => {
|
|
191
|
+
log.info(`timeout waiting ${TRANSPORT_CONNECTION_TIMEOUT / 1e3}s for transport to connect, aborting`, void 0, {
|
|
192
|
+
"~LogMeta": "~LogMeta",
|
|
193
|
+
F: __dxlog_file$12,
|
|
194
|
+
L: 187,
|
|
195
|
+
S: this
|
|
196
|
+
});
|
|
197
|
+
await this.abort(new TimeoutError({ message: `${TRANSPORT_CONNECTION_TIMEOUT / 1e3}s for transport to connect` })).catch((err) => this.errors.raise(err));
|
|
198
|
+
}, TRANSPORT_CONNECTION_TIMEOUT);
|
|
199
|
+
invariant(!this._transport, void 0, {
|
|
200
|
+
"~LogMeta": "~LogMeta",
|
|
201
|
+
F: __dxlog_file$12,
|
|
202
|
+
L: 195,
|
|
203
|
+
S: this,
|
|
204
|
+
A: ["!this._transport", ""]
|
|
205
|
+
});
|
|
206
|
+
this._transport = this._transportFactory.createTransport({
|
|
207
|
+
ownPeerKey: this.localInfo.peerKey,
|
|
208
|
+
remotePeerKey: this.remoteInfo.peerKey,
|
|
209
|
+
topic: this.topic.toHex(),
|
|
210
|
+
initiator: this.initiator,
|
|
211
|
+
stream: this._protocol.stream,
|
|
212
|
+
sendSignal: async (signal) => this._sendSignal(signal),
|
|
213
|
+
sessionId: this.sessionId
|
|
214
|
+
});
|
|
215
|
+
this._transport.connected.once(async () => {
|
|
216
|
+
this._changeState("CONNECTED");
|
|
217
|
+
await this.connectedTimeoutContext.dispose();
|
|
218
|
+
this._callbacks?.onConnected?.();
|
|
219
|
+
scheduleTaskInterval(this._ctx, async () => this._emitTransportStats(), TRANSPORT_STATS_INTERVAL);
|
|
220
|
+
});
|
|
221
|
+
this._transport.closed.once(() => {
|
|
222
|
+
this._transport = void 0;
|
|
223
|
+
this._transportClosed.wake();
|
|
224
|
+
log("abort triggered by transport close", void 0, {
|
|
225
|
+
"~LogMeta": "~LogMeta",
|
|
226
|
+
F: __dxlog_file$12,
|
|
227
|
+
L: 217,
|
|
228
|
+
S: this
|
|
229
|
+
});
|
|
230
|
+
this.abort().catch((err) => this.errors.raise(err));
|
|
231
|
+
});
|
|
232
|
+
this._transport.errors.handle(async (err) => {
|
|
233
|
+
log("transport error:", { err }, {
|
|
234
|
+
"~LogMeta": "~LogMeta",
|
|
235
|
+
F: __dxlog_file$12,
|
|
236
|
+
L: 222,
|
|
237
|
+
S: this
|
|
238
|
+
});
|
|
239
|
+
if (!this.closeReason) this.closeReason = err?.message;
|
|
240
|
+
if (err instanceof ConnectionResetError) {
|
|
241
|
+
log.info("aborting due to transport ConnectionResetError", void 0, {
|
|
242
|
+
"~LogMeta": "~LogMeta",
|
|
243
|
+
F: __dxlog_file$12,
|
|
244
|
+
L: 229,
|
|
245
|
+
S: this
|
|
246
|
+
});
|
|
247
|
+
this.abort(err).catch((err) => this.errors.raise(err));
|
|
248
|
+
} else if (err instanceof ConnectivityError) {
|
|
249
|
+
log.info("aborting due to transport ConnectivityError", void 0, {
|
|
250
|
+
"~LogMeta": "~LogMeta",
|
|
251
|
+
F: __dxlog_file$12,
|
|
252
|
+
L: 232,
|
|
253
|
+
S: this
|
|
254
|
+
});
|
|
255
|
+
this.abort(err).catch((err) => this.errors.raise(err));
|
|
256
|
+
}
|
|
257
|
+
if (this._state !== "CLOSED" && this._state !== "CLOSING") {
|
|
258
|
+
await this.connectedTimeoutContext.dispose();
|
|
259
|
+
this.errors.raise(err);
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
await this._transport.open();
|
|
263
|
+
for (const signal of this._incomingSignalBuffer) this._transport.onSignal(signal);
|
|
264
|
+
this._incomingSignalBuffer = [];
|
|
265
|
+
log("opened connection", void 0, {
|
|
266
|
+
"~LogMeta": "~LogMeta",
|
|
267
|
+
F: __dxlog_file$12,
|
|
268
|
+
L: 251,
|
|
269
|
+
S: this
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
async abort(err) {
|
|
273
|
+
log("abort", { err }, {
|
|
274
|
+
"~LogMeta": "~LogMeta",
|
|
275
|
+
F: __dxlog_file$12,
|
|
276
|
+
L: 258,
|
|
277
|
+
S: this
|
|
278
|
+
});
|
|
279
|
+
if (this._state === "CLOSED" || this._state === "ABORTED") {
|
|
280
|
+
log(`abort ignored: already ${this._state}`, this.closeReason, {
|
|
281
|
+
"~LogMeta": "~LogMeta",
|
|
282
|
+
F: __dxlog_file$12,
|
|
283
|
+
L: 260,
|
|
284
|
+
S: this
|
|
285
|
+
});
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
await this.connectedTimeoutContext.dispose();
|
|
289
|
+
this._changeState("ABORTING");
|
|
290
|
+
if (!this.closeReason) this.closeReason = err?.message;
|
|
291
|
+
await this._ctx.dispose();
|
|
292
|
+
log("aborting...", {
|
|
293
|
+
peerId: this.localInfo,
|
|
294
|
+
err
|
|
295
|
+
}, {
|
|
296
|
+
"~LogMeta": "~LogMeta",
|
|
297
|
+
F: __dxlog_file$12,
|
|
298
|
+
L: 272,
|
|
299
|
+
S: this
|
|
300
|
+
});
|
|
301
|
+
try {
|
|
302
|
+
await this._closeProtocol({ abort: true });
|
|
303
|
+
} catch (err) {
|
|
304
|
+
log.catch(err, void 0, {
|
|
305
|
+
"~LogMeta": "~LogMeta",
|
|
306
|
+
F: __dxlog_file$12,
|
|
307
|
+
L: 278,
|
|
308
|
+
S: this
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
await this._closeTransport();
|
|
313
|
+
} catch (err) {
|
|
314
|
+
log.catch(err, void 0, {
|
|
315
|
+
"~LogMeta": "~LogMeta",
|
|
316
|
+
F: __dxlog_file$12,
|
|
317
|
+
L: 285,
|
|
318
|
+
S: this
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
try {
|
|
322
|
+
this._callbacks?.onClosed?.(err);
|
|
323
|
+
} catch (err) {
|
|
324
|
+
log.catch(err, void 0, {
|
|
325
|
+
"~LogMeta": "~LogMeta",
|
|
326
|
+
F: __dxlog_file$12,
|
|
327
|
+
L: 291,
|
|
328
|
+
S: this
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
this._changeState("ABORTED");
|
|
332
|
+
}
|
|
333
|
+
async close({ error, reason } = {}) {
|
|
334
|
+
log("close", { error }, {
|
|
335
|
+
"~LogMeta": "~LogMeta",
|
|
336
|
+
F: __dxlog_file$12,
|
|
337
|
+
L: 298,
|
|
338
|
+
S: this
|
|
339
|
+
});
|
|
340
|
+
if (!this.closeReason) this.closeReason = reason ?? error?.message;
|
|
341
|
+
else this.closeReason += `; ${reason ?? error?.message}`;
|
|
342
|
+
if (this._state === "CLOSED" || this._state === "ABORTING" || this._state === "ABORTED") {
|
|
343
|
+
log("close ignored: already in progress", {
|
|
344
|
+
state: this._state,
|
|
345
|
+
error
|
|
346
|
+
}, {
|
|
347
|
+
"~LogMeta": "~LogMeta",
|
|
348
|
+
F: __dxlog_file$12,
|
|
349
|
+
L: 309,
|
|
350
|
+
S: this
|
|
351
|
+
});
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const lastState = this._state;
|
|
355
|
+
this._changeState("CLOSING");
|
|
356
|
+
await this.connectedTimeoutContext.dispose();
|
|
357
|
+
await this._ctx.dispose();
|
|
358
|
+
let abortProtocol = false;
|
|
359
|
+
if (lastState !== "CONNECTED" || error != null) {
|
|
360
|
+
log(`graceful close requested when we were in ${lastState} state? aborting`, void 0, {
|
|
361
|
+
"~LogMeta": "~LogMeta",
|
|
362
|
+
F: __dxlog_file$12,
|
|
363
|
+
L: 320,
|
|
364
|
+
S: this
|
|
365
|
+
});
|
|
366
|
+
abortProtocol = true;
|
|
367
|
+
}
|
|
368
|
+
log("closing...", {
|
|
369
|
+
peerId: this.localInfo,
|
|
370
|
+
abortProtocol,
|
|
371
|
+
error
|
|
372
|
+
}, {
|
|
373
|
+
"~LogMeta": "~LogMeta",
|
|
374
|
+
F: __dxlog_file$12,
|
|
375
|
+
L: 324,
|
|
376
|
+
S: this
|
|
377
|
+
});
|
|
378
|
+
try {
|
|
379
|
+
await this._closeProtocol({ abort: abortProtocol });
|
|
380
|
+
} catch (err) {
|
|
381
|
+
log.catch(err, void 0, {
|
|
382
|
+
"~LogMeta": "~LogMeta",
|
|
383
|
+
F: __dxlog_file$12,
|
|
384
|
+
L: 329,
|
|
385
|
+
S: this
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
try {
|
|
389
|
+
await this._closeTransport();
|
|
390
|
+
} catch (err) {
|
|
391
|
+
log.catch(err, void 0, {
|
|
392
|
+
"~LogMeta": "~LogMeta",
|
|
393
|
+
F: __dxlog_file$12,
|
|
394
|
+
L: 335,
|
|
395
|
+
S: this
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
log("closed", { peerId: this.localInfo }, {
|
|
399
|
+
"~LogMeta": "~LogMeta",
|
|
400
|
+
F: __dxlog_file$12,
|
|
401
|
+
L: 338,
|
|
402
|
+
S: this
|
|
403
|
+
});
|
|
404
|
+
this._changeState("CLOSED");
|
|
405
|
+
this._callbacks?.onClosed?.(error);
|
|
406
|
+
}
|
|
407
|
+
async _closeProtocol(options) {
|
|
408
|
+
log("closing protocol", options, {
|
|
409
|
+
"~LogMeta": "~LogMeta",
|
|
410
|
+
F: __dxlog_file$12,
|
|
411
|
+
L: 344,
|
|
412
|
+
S: this
|
|
413
|
+
});
|
|
414
|
+
await Promise.race([options?.abort ? this._protocol.abort() : this._protocol.close(), this._protocolClosed.wait()]);
|
|
415
|
+
log("protocol closed", options, {
|
|
416
|
+
"~LogMeta": "~LogMeta",
|
|
417
|
+
F: __dxlog_file$12,
|
|
418
|
+
L: 346,
|
|
419
|
+
S: this
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
async _closeTransport() {
|
|
423
|
+
log("closing transport", void 0, {
|
|
424
|
+
"~LogMeta": "~LogMeta",
|
|
425
|
+
F: __dxlog_file$12,
|
|
426
|
+
L: 350,
|
|
427
|
+
S: this
|
|
428
|
+
});
|
|
429
|
+
await Promise.race([this._transport?.close(), this._transportClosed.wait()]);
|
|
430
|
+
log("transport closed", void 0, {
|
|
431
|
+
"~LogMeta": "~LogMeta",
|
|
432
|
+
F: __dxlog_file$12,
|
|
433
|
+
L: 352,
|
|
434
|
+
S: this
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
_sendSignal(signal) {
|
|
438
|
+
this._outgoingSignalBuffer.push(signal);
|
|
439
|
+
this._signalSendTask.schedule();
|
|
440
|
+
}
|
|
441
|
+
async _flushSignalBuffer(ctx) {
|
|
442
|
+
if (this._outgoingSignalBuffer.length === 0) return;
|
|
443
|
+
try {
|
|
444
|
+
if (process.env.NODE_ENV !== "test") {
|
|
445
|
+
await cancelWithContext(ctx, sleep(this._signallingDelay));
|
|
446
|
+
this._signallingDelay = Math.min(this._signallingDelay * 2, MAX_SIGNALLING_DELAY);
|
|
447
|
+
}
|
|
448
|
+
const signals = [...this._outgoingSignalBuffer];
|
|
449
|
+
this._outgoingSignalBuffer.length = 0;
|
|
450
|
+
await this._signalMessaging.signal(ctx, {
|
|
451
|
+
author: this.localInfo,
|
|
452
|
+
recipient: this.remoteInfo,
|
|
453
|
+
sessionId: this.sessionId,
|
|
454
|
+
topic: this.topic,
|
|
455
|
+
data: { signalBatch: { signals } }
|
|
456
|
+
});
|
|
457
|
+
} catch (err) {
|
|
458
|
+
if (err instanceof CancelledError || err instanceof ContextDisposedError || err instanceof Error && err.message?.includes("CANCELLED")) return;
|
|
459
|
+
log.info("signal message failed to deliver", { err }, {
|
|
460
|
+
"~LogMeta": "~LogMeta",
|
|
461
|
+
F: __dxlog_file$12,
|
|
462
|
+
L: 392,
|
|
463
|
+
S: this
|
|
464
|
+
});
|
|
465
|
+
await this.close({ error: new ConnectivityError({
|
|
466
|
+
message: "signal message failed to deliver",
|
|
467
|
+
cause: err
|
|
468
|
+
}) });
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Receive a signal from the remote peer.
|
|
473
|
+
*/
|
|
474
|
+
async signal(_ctx, msg) {
|
|
475
|
+
invariant(msg.sessionId, void 0, {
|
|
476
|
+
"~LogMeta": "~LogMeta",
|
|
477
|
+
F: __dxlog_file$12,
|
|
478
|
+
L: 401,
|
|
479
|
+
S: this,
|
|
480
|
+
A: ["msg.sessionId", ""]
|
|
481
|
+
});
|
|
482
|
+
if (!msg.sessionId.equals(this.sessionId)) {
|
|
483
|
+
log("dropping signal for incorrect session id", void 0, {
|
|
484
|
+
"~LogMeta": "~LogMeta",
|
|
485
|
+
F: __dxlog_file$12,
|
|
486
|
+
L: 403,
|
|
487
|
+
S: this
|
|
488
|
+
});
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
invariant(msg.data.signal || msg.data.signalBatch, void 0, {
|
|
492
|
+
"~LogMeta": "~LogMeta",
|
|
493
|
+
F: __dxlog_file$12,
|
|
494
|
+
L: 406,
|
|
495
|
+
S: this,
|
|
496
|
+
A: ["msg.data.signal || msg.data.signalBatch", ""]
|
|
497
|
+
});
|
|
498
|
+
invariant(msg.author.peerKey === this.remoteInfo.peerKey, void 0, {
|
|
499
|
+
"~LogMeta": "~LogMeta",
|
|
500
|
+
F: __dxlog_file$12,
|
|
501
|
+
L: 407,
|
|
502
|
+
S: this,
|
|
503
|
+
A: ["msg.author.peerKey === this.remoteInfo.peerKey", ""]
|
|
504
|
+
});
|
|
505
|
+
invariant(msg.recipient.peerKey === this.localInfo.peerKey, void 0, {
|
|
506
|
+
"~LogMeta": "~LogMeta",
|
|
507
|
+
F: __dxlog_file$12,
|
|
508
|
+
L: 408,
|
|
509
|
+
S: this,
|
|
510
|
+
A: ["msg.recipient.peerKey === this.localInfo.peerKey", ""]
|
|
511
|
+
});
|
|
512
|
+
const signals = msg.data.signalBatch ? msg.data.signalBatch.signals ?? [] : [msg.data.signal];
|
|
513
|
+
for (const signal of signals) {
|
|
514
|
+
if (!signal) continue;
|
|
515
|
+
if (["CREATED", "INITIAL"].includes(this.state)) {
|
|
516
|
+
log("buffered signal", {
|
|
517
|
+
peerId: this.localInfo,
|
|
518
|
+
remoteId: this.remoteInfo,
|
|
519
|
+
msg: msg.data
|
|
520
|
+
}, {
|
|
521
|
+
"~LogMeta": "~LogMeta",
|
|
522
|
+
F: __dxlog_file$12,
|
|
523
|
+
L: 417,
|
|
524
|
+
S: this
|
|
525
|
+
});
|
|
526
|
+
this._incomingSignalBuffer.push(signal);
|
|
527
|
+
} else {
|
|
528
|
+
invariant(this._transport, "Connection not ready to accept signals.", {
|
|
529
|
+
"~LogMeta": "~LogMeta",
|
|
530
|
+
F: __dxlog_file$12,
|
|
531
|
+
L: 420,
|
|
532
|
+
S: this,
|
|
533
|
+
A: ["this._transport", "'Connection not ready to accept signals.'"]
|
|
534
|
+
});
|
|
535
|
+
log("received signal", {
|
|
536
|
+
peerId: this.localInfo,
|
|
537
|
+
remoteId: this.remoteInfo,
|
|
538
|
+
msg: msg.data
|
|
539
|
+
}, {
|
|
540
|
+
"~LogMeta": "~LogMeta",
|
|
541
|
+
F: __dxlog_file$12,
|
|
542
|
+
L: 421,
|
|
543
|
+
S: this
|
|
544
|
+
});
|
|
545
|
+
await this._transport.onSignal(signal);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
initiate() {
|
|
550
|
+
this._changeState("INITIAL");
|
|
551
|
+
}
|
|
552
|
+
_changeState(state) {
|
|
553
|
+
log("stateChanged", {
|
|
554
|
+
from: this._state,
|
|
555
|
+
to: state,
|
|
556
|
+
peerId: this.localInfo
|
|
557
|
+
}, {
|
|
558
|
+
"~LogMeta": "~LogMeta",
|
|
559
|
+
F: __dxlog_file$12,
|
|
560
|
+
L: 432,
|
|
561
|
+
S: this
|
|
562
|
+
});
|
|
563
|
+
invariant(state !== this._state, "Already in this state.", {
|
|
564
|
+
"~LogMeta": "~LogMeta",
|
|
565
|
+
F: __dxlog_file$12,
|
|
566
|
+
L: 433,
|
|
567
|
+
S: this,
|
|
568
|
+
A: ["state !== this._state", "'Already in this state.'"]
|
|
569
|
+
});
|
|
570
|
+
this._state = state;
|
|
571
|
+
this.stateChanged.emit(state);
|
|
572
|
+
}
|
|
573
|
+
async _emitTransportStats() {
|
|
574
|
+
const stats = await this.transport?.getStats();
|
|
575
|
+
if (stats) this.transportStats.emit(stats);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
__decorate([logInfo], Connection.prototype, "sessionIdString", null);
|
|
579
|
+
__decorate([synchronized], Connection.prototype, "abort", null);
|
|
580
|
+
__decorate([synchronized], Connection.prototype, "close", null);
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/signal/swarm-messenger.ts
|
|
583
|
+
var __dxlog_file$11 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/signal/swarm-messenger.ts";
|
|
584
|
+
var SwarmMessage = schema.getCodecForType("dxos.mesh.swarm.SwarmMessage");
|
|
585
|
+
/**
|
|
586
|
+
* Adds offer/answer and signal interfaces.
|
|
587
|
+
*/
|
|
588
|
+
var SwarmMessenger = class {
|
|
589
|
+
_sendMessage;
|
|
590
|
+
_onSignal;
|
|
591
|
+
_onOffer;
|
|
592
|
+
_topic;
|
|
593
|
+
_offerRecords = new ComplexMap((key) => key.toHex());
|
|
594
|
+
constructor({ sendMessage, onSignal, onOffer, topic }) {
|
|
595
|
+
this._sendMessage = sendMessage;
|
|
596
|
+
this._onSignal = onSignal;
|
|
597
|
+
this._onOffer = onOffer;
|
|
598
|
+
this._topic = topic;
|
|
599
|
+
}
|
|
600
|
+
async receiveMessage(ctx, { author, recipient, payload }) {
|
|
601
|
+
if (payload.type_url !== "dxos.mesh.swarm.SwarmMessage") return;
|
|
602
|
+
invariant(recipient, "Recipient is required", {
|
|
603
|
+
"~LogMeta": "~LogMeta",
|
|
604
|
+
F: __dxlog_file$11,
|
|
605
|
+
L: 54,
|
|
606
|
+
S: this,
|
|
607
|
+
A: ["recipient", "'Recipient is required'"]
|
|
608
|
+
});
|
|
609
|
+
const message = SwarmMessage.decode(payload.value);
|
|
610
|
+
if (!this._topic.equals(message.topic)) return;
|
|
611
|
+
log("received", {
|
|
612
|
+
from: author,
|
|
613
|
+
to: recipient,
|
|
614
|
+
msg: message
|
|
615
|
+
}, {
|
|
616
|
+
"~LogMeta": "~LogMeta",
|
|
617
|
+
F: __dxlog_file$11,
|
|
618
|
+
L: 62,
|
|
619
|
+
S: this
|
|
620
|
+
});
|
|
621
|
+
if (message.data?.offer) await this._handleOffer(ctx, {
|
|
622
|
+
author,
|
|
623
|
+
recipient,
|
|
624
|
+
message
|
|
625
|
+
});
|
|
626
|
+
else if (message.data?.answer) await this._resolveAnswers(message);
|
|
627
|
+
else if (message.data?.signal) await this._handleSignal(ctx, {
|
|
628
|
+
author,
|
|
629
|
+
recipient,
|
|
630
|
+
message
|
|
631
|
+
});
|
|
632
|
+
else if (message.data?.signalBatch) await this._handleSignal(ctx, {
|
|
633
|
+
author,
|
|
634
|
+
recipient,
|
|
635
|
+
message
|
|
636
|
+
});
|
|
637
|
+
else log.warn("unknown message", { message }, {
|
|
638
|
+
"~LogMeta": "~LogMeta",
|
|
639
|
+
F: __dxlog_file$11,
|
|
640
|
+
L: 73,
|
|
641
|
+
S: this
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
async signal(ctx, message) {
|
|
645
|
+
invariant(message.data?.signal || message.data?.signalBatch, "Invalid message", {
|
|
646
|
+
"~LogMeta": "~LogMeta",
|
|
647
|
+
F: __dxlog_file$11,
|
|
648
|
+
L: 78,
|
|
649
|
+
S: this,
|
|
650
|
+
A: ["message.data?.signal || message.data?.signalBatch", "'Invalid message'"]
|
|
651
|
+
});
|
|
652
|
+
await this._sendReliableMessage(ctx, {
|
|
653
|
+
author: message.author,
|
|
654
|
+
recipient: message.recipient,
|
|
655
|
+
message
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
async offer(ctx, message) {
|
|
659
|
+
const networkMessage = {
|
|
660
|
+
...message,
|
|
661
|
+
messageId: PublicKey.random()
|
|
662
|
+
};
|
|
663
|
+
return new Promise((resolve, reject) => {
|
|
664
|
+
this._offerRecords.set(networkMessage.messageId, { resolve });
|
|
665
|
+
this._sendReliableMessage(ctx, {
|
|
666
|
+
author: message.author,
|
|
667
|
+
recipient: message.recipient,
|
|
668
|
+
message: networkMessage
|
|
669
|
+
}).catch((err) => reject(err));
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
async _sendReliableMessage(ctx, { author, recipient, message }) {
|
|
673
|
+
const networkMessage = {
|
|
674
|
+
...message,
|
|
675
|
+
messageId: message.messageId ?? PublicKey.random()
|
|
676
|
+
};
|
|
677
|
+
log("sending", {
|
|
678
|
+
from: author,
|
|
679
|
+
to: recipient,
|
|
680
|
+
msg: networkMessage
|
|
681
|
+
}, {
|
|
682
|
+
"~LogMeta": "~LogMeta",
|
|
683
|
+
F: __dxlog_file$11,
|
|
684
|
+
L: 119,
|
|
685
|
+
S: this
|
|
686
|
+
});
|
|
687
|
+
await this._sendMessage(ctx, {
|
|
688
|
+
author,
|
|
689
|
+
recipient,
|
|
690
|
+
payload: {
|
|
691
|
+
type_url: "dxos.mesh.swarm.SwarmMessage",
|
|
692
|
+
value: SwarmMessage.encode(networkMessage)
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
async _resolveAnswers(message) {
|
|
697
|
+
invariant(message.data?.answer?.offerMessageId, "No offerMessageId", {
|
|
698
|
+
"~LogMeta": "~LogMeta",
|
|
699
|
+
F: __dxlog_file$11,
|
|
700
|
+
L: 131,
|
|
701
|
+
S: this,
|
|
702
|
+
A: ["message.data?.answer?.offerMessageId", "'No offerMessageId'"]
|
|
703
|
+
});
|
|
704
|
+
const offerRecord = this._offerRecords.get(message.data.answer.offerMessageId);
|
|
705
|
+
if (offerRecord) {
|
|
706
|
+
this._offerRecords.delete(message.data.answer.offerMessageId);
|
|
707
|
+
invariant(message.data?.answer, "No answer", {
|
|
708
|
+
"~LogMeta": "~LogMeta",
|
|
709
|
+
F: __dxlog_file$11,
|
|
710
|
+
L: 135,
|
|
711
|
+
S: this,
|
|
712
|
+
A: ["message.data?.answer", "'No answer'"]
|
|
713
|
+
});
|
|
714
|
+
log("resolving", { answer: message.data.answer }, {
|
|
715
|
+
"~LogMeta": "~LogMeta",
|
|
716
|
+
F: __dxlog_file$11,
|
|
717
|
+
L: 136,
|
|
718
|
+
S: this
|
|
719
|
+
});
|
|
720
|
+
offerRecord.resolve(message.data.answer);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
async _handleOffer(ctx, { author, recipient, message }) {
|
|
724
|
+
invariant(message.data.offer, "No offer", {
|
|
725
|
+
"~LogMeta": "~LogMeta",
|
|
726
|
+
F: __dxlog_file$11,
|
|
727
|
+
L: 153,
|
|
728
|
+
S: this,
|
|
729
|
+
A: ["message.data.offer", "'No offer'"]
|
|
730
|
+
});
|
|
731
|
+
const offerMessage = {
|
|
732
|
+
author,
|
|
733
|
+
recipient,
|
|
734
|
+
...message,
|
|
735
|
+
data: { offer: message.data.offer }
|
|
736
|
+
};
|
|
737
|
+
const answer = await this._onOffer(ctx, offerMessage);
|
|
738
|
+
answer.offerMessageId = message.messageId;
|
|
739
|
+
try {
|
|
740
|
+
await this._sendReliableMessage(ctx, {
|
|
741
|
+
author: recipient,
|
|
742
|
+
recipient: author,
|
|
743
|
+
message: {
|
|
744
|
+
topic: message.topic,
|
|
745
|
+
sessionId: message.sessionId,
|
|
746
|
+
data: { answer }
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
} catch (err) {
|
|
750
|
+
if (err instanceof TimeoutError) log.info("timeout sending answer to offer", { err }, {
|
|
751
|
+
"~LogMeta": "~LogMeta",
|
|
752
|
+
F: __dxlog_file$11,
|
|
753
|
+
L: 174,
|
|
754
|
+
S: this
|
|
755
|
+
});
|
|
756
|
+
else log.info("error sending answer to offer", { err }, {
|
|
757
|
+
"~LogMeta": "~LogMeta",
|
|
758
|
+
F: __dxlog_file$11,
|
|
759
|
+
L: 176,
|
|
760
|
+
S: this
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
async _handleSignal(ctx, { author, recipient, message }) {
|
|
765
|
+
invariant(message.messageId, void 0, {
|
|
766
|
+
"~LogMeta": "~LogMeta",
|
|
767
|
+
F: __dxlog_file$11,
|
|
768
|
+
L: 193,
|
|
769
|
+
S: this,
|
|
770
|
+
A: ["message.messageId", ""]
|
|
771
|
+
});
|
|
772
|
+
invariant(message.data.signal || message.data.signalBatch, "Invalid message", {
|
|
773
|
+
"~LogMeta": "~LogMeta",
|
|
774
|
+
F: __dxlog_file$11,
|
|
775
|
+
L: 194,
|
|
776
|
+
S: this,
|
|
777
|
+
A: ["message.data.signal || message.data.signalBatch", "'Invalid message'"]
|
|
778
|
+
});
|
|
779
|
+
const signalMessage = {
|
|
780
|
+
author,
|
|
781
|
+
recipient,
|
|
782
|
+
...message,
|
|
783
|
+
data: {
|
|
784
|
+
signal: message.data.signal,
|
|
785
|
+
signalBatch: message.data.signalBatch
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
await this._onSignal(ctx, signalMessage);
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
//#endregion
|
|
792
|
+
//#region src/swarm/peer.ts
|
|
793
|
+
var __dxlog_file$10 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/peer.ts";
|
|
794
|
+
var ConnectionDisplacedError = class extends SystemError {
|
|
795
|
+
constructor() {
|
|
796
|
+
super({ message: "Connection displaced by remote initiator." });
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
/**
|
|
800
|
+
* Minimum time the connection needs to be open to not incur a cool-down timer for this peer after the connection closes.
|
|
801
|
+
*/
|
|
802
|
+
var CONNECTION_COUNTS_STABLE_AFTER = 5e3;
|
|
803
|
+
/**
|
|
804
|
+
* State of remote peer during the lifetime of a swarm connection.
|
|
805
|
+
* Can open and close multiple connections to the remote peer.
|
|
806
|
+
*/
|
|
807
|
+
var Peer = class {
|
|
808
|
+
remoteInfo;
|
|
809
|
+
topic;
|
|
810
|
+
localInfo;
|
|
811
|
+
_signalMessaging;
|
|
812
|
+
_protocolProvider;
|
|
813
|
+
_transportFactory;
|
|
814
|
+
_connectionLimiter;
|
|
815
|
+
_callbacks;
|
|
816
|
+
/**
|
|
817
|
+
* Will be available to connect after this time.
|
|
818
|
+
*/
|
|
819
|
+
_availableAfter = 0;
|
|
820
|
+
availableToConnect = true;
|
|
821
|
+
_lastConnectionTime;
|
|
822
|
+
_ctx = new Context(void 0, {
|
|
823
|
+
"~LogMeta": "~LogMeta",
|
|
824
|
+
F: __dxlog_file$10,
|
|
825
|
+
L: 80
|
|
826
|
+
});
|
|
827
|
+
_connectionCtx;
|
|
828
|
+
connection;
|
|
829
|
+
/**
|
|
830
|
+
* Whether the peer is currently advertizing itself on the signal-network.
|
|
831
|
+
*/
|
|
832
|
+
advertizing = false;
|
|
833
|
+
initiating = false;
|
|
834
|
+
connectionDisplaced = new Event();
|
|
835
|
+
constructor(remoteInfo, topic, localInfo, _signalMessaging, _protocolProvider, _transportFactory, _connectionLimiter, _callbacks) {
|
|
836
|
+
this.remoteInfo = remoteInfo;
|
|
837
|
+
this.topic = topic;
|
|
838
|
+
this.localInfo = localInfo;
|
|
839
|
+
this._signalMessaging = _signalMessaging;
|
|
840
|
+
this._protocolProvider = _protocolProvider;
|
|
841
|
+
this._transportFactory = _transportFactory;
|
|
842
|
+
this._connectionLimiter = _connectionLimiter;
|
|
843
|
+
this._callbacks = _callbacks;
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* Respond to remote offer.
|
|
847
|
+
*/
|
|
848
|
+
async onOffer(_ctx, message) {
|
|
849
|
+
const remote = message.author;
|
|
850
|
+
if (this.connection && ![
|
|
851
|
+
ConnectionState$2.CREATED,
|
|
852
|
+
ConnectionState$2.INITIAL,
|
|
853
|
+
ConnectionState$2.CONNECTING
|
|
854
|
+
].includes(this.connection.state)) {
|
|
855
|
+
log.info(`received offer when connection already in ${this.connection.state} state`, void 0, {
|
|
856
|
+
"~LogMeta": "~LogMeta",
|
|
857
|
+
F: __dxlog_file$10,
|
|
858
|
+
L: 115,
|
|
859
|
+
S: this
|
|
860
|
+
});
|
|
861
|
+
return { accept: false };
|
|
862
|
+
}
|
|
863
|
+
if (this.connection || this.initiating) if (remote.peerKey < this.localInfo.peerKey) {
|
|
864
|
+
log("close local connection", {
|
|
865
|
+
localPeer: this.localInfo,
|
|
866
|
+
topic: this.topic,
|
|
867
|
+
remotePeer: this.remoteInfo,
|
|
868
|
+
sessionId: this.connection?.sessionId
|
|
869
|
+
}, {
|
|
870
|
+
"~LogMeta": "~LogMeta",
|
|
871
|
+
F: __dxlog_file$10,
|
|
872
|
+
L: 124,
|
|
873
|
+
S: this
|
|
874
|
+
});
|
|
875
|
+
if (this.connection) await this.closeConnection(new ConnectionDisplacedError());
|
|
876
|
+
} else return { accept: false };
|
|
877
|
+
if (await this._callbacks.onOffer(remote)) {
|
|
878
|
+
if (!this.connection) {
|
|
879
|
+
invariant(message.sessionId, void 0, {
|
|
880
|
+
"~LogMeta": "~LogMeta",
|
|
881
|
+
F: __dxlog_file$10,
|
|
882
|
+
L: 144,
|
|
883
|
+
S: this,
|
|
884
|
+
A: ["message.sessionId", ""]
|
|
885
|
+
});
|
|
886
|
+
const connection = this._createConnection(false, message.sessionId);
|
|
887
|
+
try {
|
|
888
|
+
await this._connectionLimiter.connecting(message.sessionId);
|
|
889
|
+
connection.initiate();
|
|
890
|
+
await connection.openConnection();
|
|
891
|
+
} catch (err) {
|
|
892
|
+
if (!(err instanceof CancelledError)) log.info("connection error", {
|
|
893
|
+
topic: this.topic,
|
|
894
|
+
peerId: this.localInfo,
|
|
895
|
+
remoteId: this.remoteInfo,
|
|
896
|
+
err
|
|
897
|
+
}, {
|
|
898
|
+
"~LogMeta": "~LogMeta",
|
|
899
|
+
F: __dxlog_file$10,
|
|
900
|
+
L: 154,
|
|
901
|
+
S: this
|
|
902
|
+
});
|
|
903
|
+
await this.closeConnection(err);
|
|
904
|
+
}
|
|
905
|
+
return { accept: true };
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
return { accept: false };
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Initiate a connection to the remote peer.
|
|
912
|
+
*/
|
|
913
|
+
async initiateConnection(ctx) {
|
|
914
|
+
invariant(!this.initiating, "Initiation in progress.", {
|
|
915
|
+
"~LogMeta": "~LogMeta",
|
|
916
|
+
F: __dxlog_file$10,
|
|
917
|
+
L: 172,
|
|
918
|
+
S: this,
|
|
919
|
+
A: ["!this.initiating", "'Initiation in progress.'"]
|
|
920
|
+
});
|
|
921
|
+
invariant(!this.connection, "Already connected.", {
|
|
922
|
+
"~LogMeta": "~LogMeta",
|
|
923
|
+
F: __dxlog_file$10,
|
|
924
|
+
L: 173,
|
|
925
|
+
S: this,
|
|
926
|
+
A: ["!this.connection", "'Already connected.'"]
|
|
927
|
+
});
|
|
928
|
+
const sessionId = PublicKey.random();
|
|
929
|
+
log("initiating...", {
|
|
930
|
+
local: this.localInfo,
|
|
931
|
+
topic: this.topic,
|
|
932
|
+
remote: this.remoteInfo,
|
|
933
|
+
sessionId
|
|
934
|
+
}, {
|
|
935
|
+
"~LogMeta": "~LogMeta",
|
|
936
|
+
F: __dxlog_file$10,
|
|
937
|
+
L: 175,
|
|
938
|
+
S: this
|
|
939
|
+
});
|
|
940
|
+
const connection = this._createConnection(true, sessionId);
|
|
941
|
+
this.initiating = true;
|
|
942
|
+
let answer;
|
|
943
|
+
try {
|
|
944
|
+
await this._connectionLimiter.connecting(sessionId);
|
|
945
|
+
connection.initiate();
|
|
946
|
+
answer = await this._signalMessaging.offer(ctx, {
|
|
947
|
+
author: this.localInfo,
|
|
948
|
+
recipient: this.remoteInfo,
|
|
949
|
+
sessionId,
|
|
950
|
+
topic: this.topic,
|
|
951
|
+
data: { offer: {} }
|
|
952
|
+
});
|
|
953
|
+
log("received", {
|
|
954
|
+
answer,
|
|
955
|
+
topic: this.topic,
|
|
956
|
+
local: this.localInfo,
|
|
957
|
+
remote: this.remoteInfo
|
|
958
|
+
}, {
|
|
959
|
+
"~LogMeta": "~LogMeta",
|
|
960
|
+
F: __dxlog_file$10,
|
|
961
|
+
L: 192,
|
|
962
|
+
S: this
|
|
963
|
+
});
|
|
964
|
+
if (connection.state !== ConnectionState$2.INITIAL) {
|
|
965
|
+
log("ignoring response", void 0, {
|
|
966
|
+
"~LogMeta": "~LogMeta",
|
|
967
|
+
F: __dxlog_file$10,
|
|
968
|
+
L: 194,
|
|
969
|
+
S: this
|
|
970
|
+
});
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
} catch (err) {
|
|
974
|
+
log("initiation error: send offer", {
|
|
975
|
+
err,
|
|
976
|
+
topic: this.topic,
|
|
977
|
+
local: this.localInfo,
|
|
978
|
+
remote: this.remoteInfo
|
|
979
|
+
}, {
|
|
980
|
+
"~LogMeta": "~LogMeta",
|
|
981
|
+
F: __dxlog_file$10,
|
|
982
|
+
L: 198,
|
|
983
|
+
S: this
|
|
984
|
+
});
|
|
985
|
+
await connection.abort(err);
|
|
986
|
+
throw err;
|
|
987
|
+
} finally {
|
|
988
|
+
this.initiating = false;
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
if (!answer.accept) {
|
|
992
|
+
this._callbacks.onRejected();
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
} catch (err) {
|
|
996
|
+
log("initiation error: accept answer", {
|
|
997
|
+
err,
|
|
998
|
+
topic: this.topic,
|
|
999
|
+
local: this.localInfo,
|
|
1000
|
+
remote: this.remoteInfo
|
|
1001
|
+
}, {
|
|
1002
|
+
"~LogMeta": "~LogMeta",
|
|
1003
|
+
F: __dxlog_file$10,
|
|
1004
|
+
L: 211,
|
|
1005
|
+
S: this
|
|
1006
|
+
});
|
|
1007
|
+
await connection.abort(err);
|
|
1008
|
+
throw err;
|
|
1009
|
+
} finally {
|
|
1010
|
+
this.initiating = false;
|
|
1011
|
+
}
|
|
1012
|
+
try {
|
|
1013
|
+
log("opening connection as initiator", void 0, {
|
|
1014
|
+
"~LogMeta": "~LogMeta",
|
|
1015
|
+
F: __dxlog_file$10,
|
|
1016
|
+
L: 224,
|
|
1017
|
+
S: this
|
|
1018
|
+
});
|
|
1019
|
+
await connection.openConnection();
|
|
1020
|
+
this._callbacks.onAccepted();
|
|
1021
|
+
} catch (err) {
|
|
1022
|
+
log("initiation error: open connection", {
|
|
1023
|
+
err,
|
|
1024
|
+
topic: this.topic,
|
|
1025
|
+
local: this.localInfo,
|
|
1026
|
+
remote: this.remoteInfo
|
|
1027
|
+
}, {
|
|
1028
|
+
"~LogMeta": "~LogMeta",
|
|
1029
|
+
F: __dxlog_file$10,
|
|
1030
|
+
L: 228,
|
|
1031
|
+
S: this
|
|
1032
|
+
});
|
|
1033
|
+
log.warn("closing connection due to unhandled error on openConnection", { err }, {
|
|
1034
|
+
"~LogMeta": "~LogMeta",
|
|
1035
|
+
F: __dxlog_file$10,
|
|
1036
|
+
L: 235,
|
|
1037
|
+
S: this
|
|
1038
|
+
});
|
|
1039
|
+
await this.closeConnection(err);
|
|
1040
|
+
throw err;
|
|
1041
|
+
} finally {
|
|
1042
|
+
this.initiating = false;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Create new connection.
|
|
1047
|
+
* Either we're initiating a connection or creating one in response to an offer from the other peer.
|
|
1048
|
+
*/
|
|
1049
|
+
_createConnection(initiator, sessionId) {
|
|
1050
|
+
log("creating connection", {
|
|
1051
|
+
topic: this.topic,
|
|
1052
|
+
peerId: this.localInfo,
|
|
1053
|
+
remoteId: this.remoteInfo,
|
|
1054
|
+
initiator,
|
|
1055
|
+
sessionId
|
|
1056
|
+
}, {
|
|
1057
|
+
"~LogMeta": "~LogMeta",
|
|
1058
|
+
F: __dxlog_file$10,
|
|
1059
|
+
L: 249,
|
|
1060
|
+
S: this
|
|
1061
|
+
});
|
|
1062
|
+
invariant(!this.connection, "Already connected.", {
|
|
1063
|
+
"~LogMeta": "~LogMeta",
|
|
1064
|
+
F: __dxlog_file$10,
|
|
1065
|
+
L: 256,
|
|
1066
|
+
S: this,
|
|
1067
|
+
A: ["!this.connection", "'Already connected.'"]
|
|
1068
|
+
});
|
|
1069
|
+
const connection = new Connection(this.topic, this.localInfo, this.remoteInfo, sessionId, initiator, this._signalMessaging, this._protocolProvider({
|
|
1070
|
+
initiator,
|
|
1071
|
+
localPeerId: PublicKey.from(this.localInfo.peerKey),
|
|
1072
|
+
remotePeerId: PublicKey.from(this.remoteInfo.peerKey),
|
|
1073
|
+
topic: this.topic
|
|
1074
|
+
}), this._transportFactory, {
|
|
1075
|
+
onConnected: () => {
|
|
1076
|
+
this.availableToConnect = true;
|
|
1077
|
+
this._lastConnectionTime = Date.now();
|
|
1078
|
+
this._callbacks.onConnected();
|
|
1079
|
+
this._connectionLimiter.doneConnecting(sessionId);
|
|
1080
|
+
log.trace("dxos.mesh.connection.connected", {
|
|
1081
|
+
topic: this.topic,
|
|
1082
|
+
localPeerId: this.localInfo,
|
|
1083
|
+
remotePeerId: this.remoteInfo,
|
|
1084
|
+
sessionId,
|
|
1085
|
+
initiator
|
|
1086
|
+
}, {
|
|
1087
|
+
"~LogMeta": "~LogMeta",
|
|
1088
|
+
F: __dxlog_file$10,
|
|
1089
|
+
L: 280,
|
|
1090
|
+
S: this
|
|
1091
|
+
});
|
|
1092
|
+
},
|
|
1093
|
+
onClosed: (err) => {
|
|
1094
|
+
const logMeta = {
|
|
1095
|
+
topic: this.topic,
|
|
1096
|
+
peerId: this.localInfo,
|
|
1097
|
+
remoteId: this.remoteInfo,
|
|
1098
|
+
initiator
|
|
1099
|
+
};
|
|
1100
|
+
log("connection closed", logMeta, {
|
|
1101
|
+
"~LogMeta": "~LogMeta",
|
|
1102
|
+
F: __dxlog_file$10,
|
|
1103
|
+
L: 290,
|
|
1104
|
+
S: this
|
|
1105
|
+
});
|
|
1106
|
+
this._connectionLimiter.doneConnecting(sessionId);
|
|
1107
|
+
invariant(this.connection === connection, "Connection mismatch (race condition).", {
|
|
1108
|
+
"~LogMeta": "~LogMeta",
|
|
1109
|
+
F: __dxlog_file$10,
|
|
1110
|
+
L: 295,
|
|
1111
|
+
S: this,
|
|
1112
|
+
A: ["this.connection === connection", "'Connection mismatch (race condition).'"]
|
|
1113
|
+
});
|
|
1114
|
+
log.trace("dxos.mesh.connection.closed", {
|
|
1115
|
+
topic: this.topic,
|
|
1116
|
+
localPeerId: this.localInfo,
|
|
1117
|
+
remotePeerId: this.remoteInfo,
|
|
1118
|
+
sessionId,
|
|
1119
|
+
initiator
|
|
1120
|
+
}, {
|
|
1121
|
+
"~LogMeta": "~LogMeta",
|
|
1122
|
+
F: __dxlog_file$10,
|
|
1123
|
+
L: 297,
|
|
1124
|
+
S: this
|
|
1125
|
+
});
|
|
1126
|
+
if (err instanceof ConnectionDisplacedError) this.connectionDisplaced.emit(this.connection);
|
|
1127
|
+
else {
|
|
1128
|
+
if (this._lastConnectionTime && this._lastConnectionTime + CONNECTION_COUNTS_STABLE_AFTER < Date.now()) this._availableAfter = 0;
|
|
1129
|
+
else {
|
|
1130
|
+
this.availableToConnect = false;
|
|
1131
|
+
this._availableAfter = increaseInterval(this._availableAfter);
|
|
1132
|
+
}
|
|
1133
|
+
this._callbacks.onDisconnected();
|
|
1134
|
+
scheduleTask(this._connectionCtx, () => {
|
|
1135
|
+
log("peer became available", logMeta, {
|
|
1136
|
+
"~LogMeta": "~LogMeta",
|
|
1137
|
+
F: __dxlog_file$10,
|
|
1138
|
+
L: 321,
|
|
1139
|
+
S: this
|
|
1140
|
+
});
|
|
1141
|
+
this.availableToConnect = true;
|
|
1142
|
+
this._callbacks.onPeerAvailable();
|
|
1143
|
+
}, this._availableAfter);
|
|
1144
|
+
}
|
|
1145
|
+
this.connection = void 0;
|
|
1146
|
+
}
|
|
1147
|
+
});
|
|
1148
|
+
this._callbacks.onInitiated(connection);
|
|
1149
|
+
this._connectionCtx?.dispose();
|
|
1150
|
+
this._connectionCtx = this._ctx.derive();
|
|
1151
|
+
connection.errors.handle((err) => {
|
|
1152
|
+
log.info("connection error, closing", {
|
|
1153
|
+
topic: this.topic,
|
|
1154
|
+
peerId: this.localInfo,
|
|
1155
|
+
remoteId: this.remoteInfo,
|
|
1156
|
+
initiator,
|
|
1157
|
+
err
|
|
1158
|
+
}, {
|
|
1159
|
+
"~LogMeta": "~LogMeta",
|
|
1160
|
+
F: __dxlog_file$10,
|
|
1161
|
+
L: 339,
|
|
1162
|
+
S: this
|
|
1163
|
+
});
|
|
1164
|
+
log.trace("dxos.mesh.connection.error", {
|
|
1165
|
+
topic: this.topic,
|
|
1166
|
+
localPeerId: this.localInfo,
|
|
1167
|
+
remotePeerId: this.remoteInfo,
|
|
1168
|
+
sessionId,
|
|
1169
|
+
initiator,
|
|
1170
|
+
err
|
|
1171
|
+
}, {
|
|
1172
|
+
"~LogMeta": "~LogMeta",
|
|
1173
|
+
F: __dxlog_file$10,
|
|
1174
|
+
L: 346,
|
|
1175
|
+
S: this
|
|
1176
|
+
});
|
|
1177
|
+
this.closeConnection(err);
|
|
1178
|
+
});
|
|
1179
|
+
this.connection = connection;
|
|
1180
|
+
return connection;
|
|
1181
|
+
}
|
|
1182
|
+
async closeConnection(err) {
|
|
1183
|
+
if (!this.connection) return;
|
|
1184
|
+
const connection = this.connection;
|
|
1185
|
+
log("closing...", {
|
|
1186
|
+
peerId: this.remoteInfo,
|
|
1187
|
+
sessionId: connection.sessionId
|
|
1188
|
+
}, {
|
|
1189
|
+
"~LogMeta": "~LogMeta",
|
|
1190
|
+
F: __dxlog_file$10,
|
|
1191
|
+
L: 371,
|
|
1192
|
+
S: this
|
|
1193
|
+
});
|
|
1194
|
+
await connection.close({ error: err });
|
|
1195
|
+
log("closed", {
|
|
1196
|
+
peerId: this.remoteInfo,
|
|
1197
|
+
sessionId: connection.sessionId
|
|
1198
|
+
}, {
|
|
1199
|
+
"~LogMeta": "~LogMeta",
|
|
1200
|
+
F: __dxlog_file$10,
|
|
1201
|
+
L: 377,
|
|
1202
|
+
S: this
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
async onSignal(ctx, message) {
|
|
1206
|
+
if (!this.connection) {
|
|
1207
|
+
log("dropping signal message for non-existent connection", { message }, {
|
|
1208
|
+
"~LogMeta": "~LogMeta",
|
|
1209
|
+
F: __dxlog_file$10,
|
|
1210
|
+
L: 382,
|
|
1211
|
+
S: this
|
|
1212
|
+
});
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
await this.connection.signal(ctx, message);
|
|
1216
|
+
}
|
|
1217
|
+
async safeDestroy(reason) {
|
|
1218
|
+
await this._ctx.dispose();
|
|
1219
|
+
log("Destroying peer", {
|
|
1220
|
+
peerId: this.remoteInfo,
|
|
1221
|
+
topic: this.topic
|
|
1222
|
+
}, {
|
|
1223
|
+
"~LogMeta": "~LogMeta",
|
|
1224
|
+
F: __dxlog_file$10,
|
|
1225
|
+
L: 392,
|
|
1226
|
+
S: this
|
|
1227
|
+
});
|
|
1228
|
+
await this?.connection?.close({ reason });
|
|
1229
|
+
}
|
|
1230
|
+
};
|
|
1231
|
+
__decorate([synchronized], Peer.prototype, "safeDestroy", null);
|
|
1232
|
+
var increaseInterval = (interval) => {
|
|
1233
|
+
if (interval === 0) return 50;
|
|
1234
|
+
else if (interval < 500) return 500;
|
|
1235
|
+
else if (interval < 1e3) return 1e3;
|
|
1236
|
+
else if (interval < 5e3) return 5e3;
|
|
1237
|
+
return 1e4;
|
|
1238
|
+
};
|
|
1239
|
+
//#endregion
|
|
1240
|
+
//#region src/swarm/swarm.ts
|
|
1241
|
+
var __dxlog_file$9 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/swarm.ts";
|
|
1242
|
+
var INITIATION_DELAY = 100;
|
|
1243
|
+
var getClassName = (obj) => Object.getPrototypeOf(obj).constructor.name;
|
|
1244
|
+
/**
|
|
1245
|
+
* A single peer's view of the swarm.
|
|
1246
|
+
* Manages a set of peers subscribed on the same topic.
|
|
1247
|
+
* Routes signal events and maintains swarm topology.
|
|
1248
|
+
*/
|
|
1249
|
+
var Swarm = class {
|
|
1250
|
+
_topic;
|
|
1251
|
+
_ownPeer;
|
|
1252
|
+
_topology;
|
|
1253
|
+
_protocolProvider;
|
|
1254
|
+
_messenger;
|
|
1255
|
+
_transportFactory;
|
|
1256
|
+
_label;
|
|
1257
|
+
_connectionLimiter;
|
|
1258
|
+
_initiationDelay;
|
|
1259
|
+
_swarmMessenger;
|
|
1260
|
+
_ctx = new Context(void 0, {
|
|
1261
|
+
"~LogMeta": "~LogMeta",
|
|
1262
|
+
F: __dxlog_file$9,
|
|
1263
|
+
L: 37
|
|
1264
|
+
});
|
|
1265
|
+
_listeningHandle = void 0;
|
|
1266
|
+
/**
|
|
1267
|
+
* PeerInfo -> Peer.
|
|
1268
|
+
* @internal
|
|
1269
|
+
*/
|
|
1270
|
+
_peers = new ComplexMap(PeerInfoHash);
|
|
1271
|
+
/**
|
|
1272
|
+
* Unique id of the swarm, local to the current peer, generated when swarm is joined.
|
|
1273
|
+
*/
|
|
1274
|
+
_instanceId = PublicKey.random().toHex();
|
|
1275
|
+
/**
|
|
1276
|
+
* New connection to a peer is started.
|
|
1277
|
+
* @internal
|
|
1278
|
+
*/
|
|
1279
|
+
connectionAdded = new Event();
|
|
1280
|
+
/**
|
|
1281
|
+
* Connection to a peer is dropped.
|
|
1282
|
+
* @internal
|
|
1283
|
+
*/
|
|
1284
|
+
disconnected = new Event();
|
|
1285
|
+
/**
|
|
1286
|
+
* Connection is established to a new peer.
|
|
1287
|
+
* @internal
|
|
1288
|
+
*/
|
|
1289
|
+
connected = new Event();
|
|
1290
|
+
errors = new ErrorStream();
|
|
1291
|
+
constructor(_topic, _ownPeer, _topology, _protocolProvider, _messenger, _transportFactory, _label, _connectionLimiter, _initiationDelay = INITIATION_DELAY) {
|
|
1292
|
+
this._topic = _topic;
|
|
1293
|
+
this._ownPeer = _ownPeer;
|
|
1294
|
+
this._topology = _topology;
|
|
1295
|
+
this._protocolProvider = _protocolProvider;
|
|
1296
|
+
this._messenger = _messenger;
|
|
1297
|
+
this._transportFactory = _transportFactory;
|
|
1298
|
+
this._label = _label;
|
|
1299
|
+
this._connectionLimiter = _connectionLimiter;
|
|
1300
|
+
this._initiationDelay = _initiationDelay;
|
|
1301
|
+
log("creating swarm", {
|
|
1302
|
+
topic: this._topic.toHex(),
|
|
1303
|
+
peer: this._ownPeer
|
|
1304
|
+
}, {
|
|
1305
|
+
"~LogMeta": "~LogMeta",
|
|
1306
|
+
F: __dxlog_file$9,
|
|
1307
|
+
L: 87,
|
|
1308
|
+
S: this
|
|
1309
|
+
});
|
|
1310
|
+
_topology.init(this._getSwarmController());
|
|
1311
|
+
this._swarmMessenger = new SwarmMessenger({
|
|
1312
|
+
sendMessage: async (ctx, msg) => await this._messenger.sendMessage(ctx, msg),
|
|
1313
|
+
onSignal: async (ctx, msg) => await this.onSignal(ctx, msg),
|
|
1314
|
+
onOffer: async (ctx, msg) => await this.onOffer(ctx, msg),
|
|
1315
|
+
topic: this._topic
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
get connections() {
|
|
1319
|
+
return Array.from(this._peers.values()).map((peer) => peer.connection).filter(isNonNullable);
|
|
1320
|
+
}
|
|
1321
|
+
get ownPeerId() {
|
|
1322
|
+
return PublicKey.from(this._ownPeer.peerKey);
|
|
1323
|
+
}
|
|
1324
|
+
get ownPeer() {
|
|
1325
|
+
return this._ownPeer;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Custom label assigned to this swarm. Used in devtools to display human-readable names for swarms.
|
|
1329
|
+
*/
|
|
1330
|
+
get label() {
|
|
1331
|
+
return this._label;
|
|
1332
|
+
}
|
|
1333
|
+
get topic() {
|
|
1334
|
+
return this._topic;
|
|
1335
|
+
}
|
|
1336
|
+
async open() {
|
|
1337
|
+
invariant(!this._listeningHandle, void 0, {
|
|
1338
|
+
"~LogMeta": "~LogMeta",
|
|
1339
|
+
F: __dxlog_file$9,
|
|
1340
|
+
L: 126,
|
|
1341
|
+
S: this,
|
|
1342
|
+
A: ["!this._listeningHandle", ""]
|
|
1343
|
+
});
|
|
1344
|
+
this._listeningHandle = await this._messenger.listen({
|
|
1345
|
+
peer: this._ownPeer,
|
|
1346
|
+
payloadType: "dxos.mesh.swarm.SwarmMessage",
|
|
1347
|
+
onMessage: async (message) => {
|
|
1348
|
+
await this._swarmMessenger.receiveMessage(this._ctx, message).catch((err) => log.info("Error while receiving message", { err }, {
|
|
1349
|
+
"~LogMeta": "~LogMeta",
|
|
1350
|
+
F: __dxlog_file$9,
|
|
1351
|
+
L: 134,
|
|
1352
|
+
S: this
|
|
1353
|
+
}));
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
async destroy() {
|
|
1358
|
+
log("destroying...", void 0, {
|
|
1359
|
+
"~LogMeta": "~LogMeta",
|
|
1360
|
+
F: __dxlog_file$9,
|
|
1361
|
+
L: 140,
|
|
1362
|
+
S: this
|
|
1363
|
+
});
|
|
1364
|
+
await this._listeningHandle?.unsubscribe();
|
|
1365
|
+
this._listeningHandle = void 0;
|
|
1366
|
+
await this._ctx.dispose();
|
|
1367
|
+
await this._topology.destroy();
|
|
1368
|
+
await Promise.all(Array.from(this._peers.keys()).map((key) => this._destroyPeer(key, "swarm destroyed")));
|
|
1369
|
+
log("destroyed", void 0, {
|
|
1370
|
+
"~LogMeta": "~LogMeta",
|
|
1371
|
+
F: __dxlog_file$9,
|
|
1372
|
+
L: 147,
|
|
1373
|
+
S: this
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
async setTopology(topology) {
|
|
1377
|
+
invariant(!this._ctx.disposed, "Swarm is offline", {
|
|
1378
|
+
"~LogMeta": "~LogMeta",
|
|
1379
|
+
F: __dxlog_file$9,
|
|
1380
|
+
L: 151,
|
|
1381
|
+
S: this,
|
|
1382
|
+
A: ["!this._ctx.disposed", "'Swarm is offline'"]
|
|
1383
|
+
});
|
|
1384
|
+
if (topology === this._topology) return;
|
|
1385
|
+
log("setting topology", {
|
|
1386
|
+
previous: getClassName(this._topology),
|
|
1387
|
+
topology: getClassName(topology)
|
|
1388
|
+
}, {
|
|
1389
|
+
"~LogMeta": "~LogMeta",
|
|
1390
|
+
F: __dxlog_file$9,
|
|
1391
|
+
L: 155,
|
|
1392
|
+
S: this
|
|
1393
|
+
});
|
|
1394
|
+
await this._topology.destroy();
|
|
1395
|
+
this._topology = topology;
|
|
1396
|
+
this._topology.init(this._getSwarmController());
|
|
1397
|
+
this._topology.update();
|
|
1398
|
+
}
|
|
1399
|
+
async onSwarmEvent(swarmEvent) {
|
|
1400
|
+
log("swarm event", { swarmEvent }, {
|
|
1401
|
+
"~LogMeta": "~LogMeta",
|
|
1402
|
+
F: __dxlog_file$9,
|
|
1403
|
+
L: 168,
|
|
1404
|
+
S: this
|
|
1405
|
+
});
|
|
1406
|
+
if (this._ctx.disposed) {
|
|
1407
|
+
log("swarm event ignored for disposed swarm", void 0, {
|
|
1408
|
+
"~LogMeta": "~LogMeta",
|
|
1409
|
+
F: __dxlog_file$9,
|
|
1410
|
+
L: 171,
|
|
1411
|
+
S: this
|
|
1412
|
+
});
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
if (swarmEvent.peerAvailable) {
|
|
1416
|
+
const peerId = swarmEvent.peerAvailable.peer.peerKey;
|
|
1417
|
+
if (peerId !== this._ownPeer.peerKey) {
|
|
1418
|
+
log("new peer", { peerId }, {
|
|
1419
|
+
"~LogMeta": "~LogMeta",
|
|
1420
|
+
F: __dxlog_file$9,
|
|
1421
|
+
L: 178,
|
|
1422
|
+
S: this
|
|
1423
|
+
});
|
|
1424
|
+
const peer = this._getOrCreatePeer(swarmEvent.peerAvailable.peer);
|
|
1425
|
+
peer.advertizing = true;
|
|
1426
|
+
}
|
|
1427
|
+
} else if (swarmEvent.peerLeft) {
|
|
1428
|
+
const peer = this._peers.get(swarmEvent.peerLeft.peer);
|
|
1429
|
+
if (peer) {
|
|
1430
|
+
peer.advertizing = false;
|
|
1431
|
+
if (this._isConnectionEstablishmentInProgress(peer)) {
|
|
1432
|
+
log(`destroying peer, state: ${peer.connection?.state}`, void 0, {
|
|
1433
|
+
"~LogMeta": "~LogMeta",
|
|
1434
|
+
F: __dxlog_file$9,
|
|
1435
|
+
L: 189,
|
|
1436
|
+
S: this
|
|
1437
|
+
});
|
|
1438
|
+
this._destroyPeer(swarmEvent.peerLeft.peer, "peer left").catch((err) => log.catch(err, void 0, {
|
|
1439
|
+
"~LogMeta": "~LogMeta",
|
|
1440
|
+
F: __dxlog_file$9,
|
|
1441
|
+
L: 190,
|
|
1442
|
+
S: this
|
|
1443
|
+
}));
|
|
1444
|
+
}
|
|
1445
|
+
} else log("received peerLeft but no peer found", { peer: swarmEvent.peerLeft.peer.peerKey }, {
|
|
1446
|
+
"~LogMeta": "~LogMeta",
|
|
1447
|
+
F: __dxlog_file$9,
|
|
1448
|
+
L: 193,
|
|
1449
|
+
S: this
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
this._topology.update();
|
|
1453
|
+
}
|
|
1454
|
+
async onOffer(ctx, message) {
|
|
1455
|
+
log("offer", { message }, {
|
|
1456
|
+
"~LogMeta": "~LogMeta",
|
|
1457
|
+
F: __dxlog_file$9,
|
|
1458
|
+
L: 202,
|
|
1459
|
+
S: this
|
|
1460
|
+
});
|
|
1461
|
+
if (this._ctx.disposed) {
|
|
1462
|
+
log("ignored for disposed swarm", void 0, {
|
|
1463
|
+
"~LogMeta": "~LogMeta",
|
|
1464
|
+
F: __dxlog_file$9,
|
|
1465
|
+
L: 204,
|
|
1466
|
+
S: this
|
|
1467
|
+
});
|
|
1468
|
+
return { accept: false };
|
|
1469
|
+
}
|
|
1470
|
+
invariant(message.author, void 0, {
|
|
1471
|
+
"~LogMeta": "~LogMeta",
|
|
1472
|
+
F: __dxlog_file$9,
|
|
1473
|
+
L: 209,
|
|
1474
|
+
S: this,
|
|
1475
|
+
A: ["message.author", ""]
|
|
1476
|
+
});
|
|
1477
|
+
if (message.recipient.peerKey !== this._ownPeer.peerKey) {
|
|
1478
|
+
log("rejecting offer with incorrect peerId", { message }, {
|
|
1479
|
+
"~LogMeta": "~LogMeta",
|
|
1480
|
+
F: __dxlog_file$9,
|
|
1481
|
+
L: 211,
|
|
1482
|
+
S: this
|
|
1483
|
+
});
|
|
1484
|
+
return { accept: false };
|
|
1485
|
+
}
|
|
1486
|
+
if (!message.topic?.equals(this._topic)) {
|
|
1487
|
+
log("rejecting offer with incorrect topic", { message }, {
|
|
1488
|
+
"~LogMeta": "~LogMeta",
|
|
1489
|
+
F: __dxlog_file$9,
|
|
1490
|
+
L: 215,
|
|
1491
|
+
S: this
|
|
1492
|
+
});
|
|
1493
|
+
return { accept: false };
|
|
1494
|
+
}
|
|
1495
|
+
const answer = await this._getOfferSenderPeer(message.author).onOffer(ctx, message);
|
|
1496
|
+
this._topology.update();
|
|
1497
|
+
return answer;
|
|
1498
|
+
}
|
|
1499
|
+
_getOfferSenderPeer(senderInfo) {
|
|
1500
|
+
const peer = this._getOrCreatePeer(senderInfo);
|
|
1501
|
+
const connectionState = peer.connection?.state;
|
|
1502
|
+
if (connectionState === ConnectionState$2.CLOSING || connectionState === ConnectionState$2.ABORTING) {
|
|
1503
|
+
this._peers.delete(peer.remoteInfo);
|
|
1504
|
+
this.disconnected.emit(peer.remoteInfo);
|
|
1505
|
+
return this._getOrCreatePeer(peer.remoteInfo);
|
|
1506
|
+
}
|
|
1507
|
+
return peer;
|
|
1508
|
+
}
|
|
1509
|
+
async onSignal(ctx, message) {
|
|
1510
|
+
log("signal", { message }, {
|
|
1511
|
+
"~LogMeta": "~LogMeta",
|
|
1512
|
+
F: __dxlog_file$9,
|
|
1513
|
+
L: 240,
|
|
1514
|
+
S: this
|
|
1515
|
+
});
|
|
1516
|
+
if (this._ctx.disposed) {
|
|
1517
|
+
log.info("ignored for offline swarm", void 0, {
|
|
1518
|
+
"~LogMeta": "~LogMeta",
|
|
1519
|
+
F: __dxlog_file$9,
|
|
1520
|
+
L: 242,
|
|
1521
|
+
S: this
|
|
1522
|
+
});
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
invariant(message.recipient.peerKey === this._ownPeer.peerKey, `Invalid signal peer id expected=${this.ownPeerId}, actual=${message.recipient}`, {
|
|
1526
|
+
"~LogMeta": "~LogMeta",
|
|
1527
|
+
F: __dxlog_file$9,
|
|
1528
|
+
L: 245,
|
|
1529
|
+
S: this,
|
|
1530
|
+
A: ["message.recipient.peerKey === this._ownPeer.peerKey", "`Invalid signal peer id expected=${this.ownPeerId}, actual=${message.recipient}`"]
|
|
1531
|
+
});
|
|
1532
|
+
invariant(message.topic?.equals(this._topic), void 0, {
|
|
1533
|
+
"~LogMeta": "~LogMeta",
|
|
1534
|
+
F: __dxlog_file$9,
|
|
1535
|
+
L: 249,
|
|
1536
|
+
S: this,
|
|
1537
|
+
A: ["message.topic?.equals(this._topic)", ""]
|
|
1538
|
+
});
|
|
1539
|
+
invariant(message.author, void 0, {
|
|
1540
|
+
"~LogMeta": "~LogMeta",
|
|
1541
|
+
F: __dxlog_file$9,
|
|
1542
|
+
L: 250,
|
|
1543
|
+
S: this,
|
|
1544
|
+
A: ["message.author", ""]
|
|
1545
|
+
});
|
|
1546
|
+
await this._getOrCreatePeer(message.author).onSignal(ctx, message);
|
|
1547
|
+
}
|
|
1548
|
+
async goOffline() {
|
|
1549
|
+
await this._ctx.dispose();
|
|
1550
|
+
await Promise.all([...this._peers.keys()].map((peerId) => this._destroyPeer(peerId, "goOffline")));
|
|
1551
|
+
}
|
|
1552
|
+
async goOnline() {
|
|
1553
|
+
this._ctx = new Context(void 0, {
|
|
1554
|
+
"~LogMeta": "~LogMeta",
|
|
1555
|
+
F: __dxlog_file$9,
|
|
1556
|
+
L: 266
|
|
1557
|
+
});
|
|
1558
|
+
}
|
|
1559
|
+
_getOrCreatePeer(peerInfo) {
|
|
1560
|
+
invariant(peerInfo.peerKey, "PeerInfo.peerKey is required", {
|
|
1561
|
+
"~LogMeta": "~LogMeta",
|
|
1562
|
+
F: __dxlog_file$9,
|
|
1563
|
+
L: 270,
|
|
1564
|
+
S: this,
|
|
1565
|
+
A: ["peerInfo.peerKey", "'PeerInfo.peerKey is required'"]
|
|
1566
|
+
});
|
|
1567
|
+
let peer = this._peers.get(peerInfo);
|
|
1568
|
+
if (!peer) {
|
|
1569
|
+
peer = new Peer(peerInfo, this._topic, this._ownPeer, this._swarmMessenger, this._protocolProvider, this._transportFactory, this._connectionLimiter, {
|
|
1570
|
+
onInitiated: (connection) => {
|
|
1571
|
+
this.connectionAdded.emit(connection);
|
|
1572
|
+
},
|
|
1573
|
+
onConnected: () => {
|
|
1574
|
+
this.connected.emit(peerInfo);
|
|
1575
|
+
},
|
|
1576
|
+
onDisconnected: async () => {
|
|
1577
|
+
if (this._isUnregistered(peer)) {
|
|
1578
|
+
log.verbose("ignored onDisconnected for unregistered peer", void 0, {
|
|
1579
|
+
"~LogMeta": "~LogMeta",
|
|
1580
|
+
F: __dxlog_file$9,
|
|
1581
|
+
L: 290,
|
|
1582
|
+
S: this
|
|
1583
|
+
});
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
if (!peer.advertizing) await this._destroyPeer(peerInfo, "peer disconnected");
|
|
1587
|
+
this.disconnected.emit(peerInfo);
|
|
1588
|
+
this._topology.update();
|
|
1589
|
+
},
|
|
1590
|
+
onRejected: () => {
|
|
1591
|
+
if (!this._isUnregistered(peer)) {
|
|
1592
|
+
log("peer rejected connection", { peerInfo }, {
|
|
1593
|
+
"~LogMeta": "~LogMeta",
|
|
1594
|
+
F: __dxlog_file$9,
|
|
1595
|
+
L: 304,
|
|
1596
|
+
S: this
|
|
1597
|
+
});
|
|
1598
|
+
this._destroyPeer(peerInfo, "peer rejected connection");
|
|
1599
|
+
}
|
|
1600
|
+
},
|
|
1601
|
+
onAccepted: () => {
|
|
1602
|
+
this._topology.update();
|
|
1603
|
+
},
|
|
1604
|
+
onOffer: (remoteId) => {
|
|
1605
|
+
return this._topology.onOffer(PublicKey.from(remoteId.peerKey));
|
|
1606
|
+
},
|
|
1607
|
+
onPeerAvailable: () => {
|
|
1608
|
+
this._topology.update();
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
this._peers.set(peerInfo, peer);
|
|
1612
|
+
}
|
|
1613
|
+
return peer;
|
|
1614
|
+
}
|
|
1615
|
+
async _destroyPeer(peerInfo, reason) {
|
|
1616
|
+
log("destroy peer", {
|
|
1617
|
+
peerKey: peerInfo.peerKey,
|
|
1618
|
+
reason
|
|
1619
|
+
}, {
|
|
1620
|
+
"~LogMeta": "~LogMeta",
|
|
1621
|
+
F: __dxlog_file$9,
|
|
1622
|
+
L: 326,
|
|
1623
|
+
S: this
|
|
1624
|
+
});
|
|
1625
|
+
const peer = this._peers.get(peerInfo);
|
|
1626
|
+
invariant(peer, void 0, {
|
|
1627
|
+
"~LogMeta": "~LogMeta",
|
|
1628
|
+
F: __dxlog_file$9,
|
|
1629
|
+
L: 328,
|
|
1630
|
+
S: this,
|
|
1631
|
+
A: ["peer", ""]
|
|
1632
|
+
});
|
|
1633
|
+
this._peers.delete(peerInfo);
|
|
1634
|
+
await peer.safeDestroy(reason);
|
|
1635
|
+
}
|
|
1636
|
+
_getSwarmController() {
|
|
1637
|
+
return {
|
|
1638
|
+
getState: () => ({
|
|
1639
|
+
ownPeerId: PublicKey.from(this._ownPeer.peerKey),
|
|
1640
|
+
connected: Array.from(this._peers.entries()).filter(([_, peer]) => peer.connection).map(([info]) => PublicKey.from(info.peerKey)),
|
|
1641
|
+
candidates: Array.from(this._peers.entries()).filter(([_, peer]) => !peer.connection && peer.advertizing && peer.availableToConnect).map(([info]) => PublicKey.from(info.peerKey)),
|
|
1642
|
+
allPeers: Array.from(this._peers.keys()).map((info) => PublicKey.from(info.peerKey))
|
|
1643
|
+
}),
|
|
1644
|
+
connect: (peer) => {
|
|
1645
|
+
if (this._ctx.disposed) return;
|
|
1646
|
+
scheduleTask(this._ctx, async () => {
|
|
1647
|
+
try {
|
|
1648
|
+
await this._initiateConnection({ peerKey: peer.toHex() });
|
|
1649
|
+
} catch (err) {
|
|
1650
|
+
log("initiation error", err, {
|
|
1651
|
+
"~LogMeta": "~LogMeta",
|
|
1652
|
+
F: __dxlog_file$9,
|
|
1653
|
+
L: 355,
|
|
1654
|
+
S: this
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1658
|
+
},
|
|
1659
|
+
disconnect: async (peer) => {
|
|
1660
|
+
if (this._ctx.disposed) return;
|
|
1661
|
+
scheduleTask(this._ctx, async () => {
|
|
1662
|
+
await this._closeConnection({ peerKey: peer.toHex() });
|
|
1663
|
+
this._topology.update();
|
|
1664
|
+
});
|
|
1665
|
+
}
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Creates a connection then sends message over signal network.
|
|
1670
|
+
*/
|
|
1671
|
+
async _initiateConnection(remotePeer) {
|
|
1672
|
+
const ctx = this._ctx;
|
|
1673
|
+
const peer = this._getOrCreatePeer(remotePeer);
|
|
1674
|
+
if (remotePeer.peerKey < this._ownPeer.peerKey) {
|
|
1675
|
+
log("initiation delay", { remotePeer }, {
|
|
1676
|
+
"~LogMeta": "~LogMeta",
|
|
1677
|
+
F: __dxlog_file$9,
|
|
1678
|
+
L: 383,
|
|
1679
|
+
S: this
|
|
1680
|
+
});
|
|
1681
|
+
await sleep(this._initiationDelay);
|
|
1682
|
+
}
|
|
1683
|
+
if (ctx.disposed) return;
|
|
1684
|
+
if (this._isUnregistered(peer)) throw new Error("Peer left during initiation delay");
|
|
1685
|
+
if (peer.connection) return;
|
|
1686
|
+
log("initiating connection...", { remotePeer }, {
|
|
1687
|
+
"~LogMeta": "~LogMeta",
|
|
1688
|
+
F: __dxlog_file$9,
|
|
1689
|
+
L: 399,
|
|
1690
|
+
S: this
|
|
1691
|
+
});
|
|
1692
|
+
await peer.initiateConnection(ctx);
|
|
1693
|
+
this._topology.update();
|
|
1694
|
+
log("initiated", { remotePeer }, {
|
|
1695
|
+
"~LogMeta": "~LogMeta",
|
|
1696
|
+
F: __dxlog_file$9,
|
|
1697
|
+
L: 402,
|
|
1698
|
+
S: this
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
async _closeConnection(peerInfo) {
|
|
1702
|
+
const peer = this._peers.get(peerInfo);
|
|
1703
|
+
if (!peer) return;
|
|
1704
|
+
await peer.closeConnection();
|
|
1705
|
+
}
|
|
1706
|
+
_isConnectionEstablishmentInProgress(peer) {
|
|
1707
|
+
if (!peer.connection) return true;
|
|
1708
|
+
return [
|
|
1709
|
+
ConnectionState$2.INITIAL,
|
|
1710
|
+
ConnectionState$2.CREATED,
|
|
1711
|
+
ConnectionState$2.CONNECTING
|
|
1712
|
+
].includes(peer.connection.state);
|
|
1713
|
+
}
|
|
1714
|
+
_isUnregistered(peer) {
|
|
1715
|
+
return !peer || this._peers.get(peer.remoteInfo) !== peer;
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
__decorate([logInfo], Swarm.prototype, "_instanceId", void 0);
|
|
1719
|
+
__decorate([logInfo], Swarm.prototype, "ownPeer", null);
|
|
1720
|
+
__decorate([logInfo], Swarm.prototype, "topic", null);
|
|
1721
|
+
__decorate([synchronized], Swarm.prototype, "onSwarmEvent", null);
|
|
1722
|
+
__decorate([synchronized], Swarm.prototype, "onOffer", null);
|
|
1723
|
+
__decorate([synchronized], Swarm.prototype, "goOffline", null);
|
|
1724
|
+
__decorate([synchronized], Swarm.prototype, "goOnline", null);
|
|
1725
|
+
//#endregion
|
|
1726
|
+
//#region src/swarm/swarm-mapper.ts
|
|
1727
|
+
var __dxlog_file$8 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/swarm-mapper.ts";
|
|
1728
|
+
var SwarmMapper = class {
|
|
1729
|
+
_swarm;
|
|
1730
|
+
_subscriptions = new SubscriptionList();
|
|
1731
|
+
_connectionSubscriptions = new ComplexMap(PeerInfoHash);
|
|
1732
|
+
_peers = new ComplexMap(PeerInfoHash);
|
|
1733
|
+
mapUpdated = new Event();
|
|
1734
|
+
get peers() {
|
|
1735
|
+
return Array.from(this._peers.values());
|
|
1736
|
+
}
|
|
1737
|
+
constructor(_swarm) {
|
|
1738
|
+
this._swarm = _swarm;
|
|
1739
|
+
this._subscriptions.add(_swarm.connectionAdded.on((connection) => {
|
|
1740
|
+
this._update();
|
|
1741
|
+
this._connectionSubscriptions.set(connection.remoteInfo, connection.stateChanged.on(() => {
|
|
1742
|
+
this._update();
|
|
1743
|
+
}));
|
|
1744
|
+
}));
|
|
1745
|
+
this._subscriptions.add(_swarm.disconnected.on((peerId) => {
|
|
1746
|
+
this._connectionSubscriptions.get(peerId)?.();
|
|
1747
|
+
this._connectionSubscriptions.delete(peerId);
|
|
1748
|
+
this._update();
|
|
1749
|
+
}));
|
|
1750
|
+
this._update();
|
|
1751
|
+
}
|
|
1752
|
+
_update() {
|
|
1753
|
+
log("updating swarm", void 0, {
|
|
1754
|
+
"~LogMeta": "~LogMeta",
|
|
1755
|
+
F: __dxlog_file$8,
|
|
1756
|
+
L: 71,
|
|
1757
|
+
S: this
|
|
1758
|
+
});
|
|
1759
|
+
this._peers.clear();
|
|
1760
|
+
this._peers.set(this._swarm.ownPeer, {
|
|
1761
|
+
id: this._swarm.ownPeerId,
|
|
1762
|
+
state: "ME",
|
|
1763
|
+
connections: []
|
|
1764
|
+
});
|
|
1765
|
+
for (const connection of this._swarm.connections) this._peers.set(connection.remoteInfo, {
|
|
1766
|
+
id: PublicKey.from(connection.remoteInfo.peerKey),
|
|
1767
|
+
state: connection.state,
|
|
1768
|
+
connections: [this._swarm.ownPeerId]
|
|
1769
|
+
});
|
|
1770
|
+
log("graph changed", {
|
|
1771
|
+
directConnections: this._swarm.connections.length,
|
|
1772
|
+
totalPeersInSwarm: this._peers.size
|
|
1773
|
+
}, {
|
|
1774
|
+
"~LogMeta": "~LogMeta",
|
|
1775
|
+
F: __dxlog_file$8,
|
|
1776
|
+
L: 112,
|
|
1777
|
+
S: this
|
|
1778
|
+
});
|
|
1779
|
+
this.mapUpdated.emit(Array.from(this._peers.values()));
|
|
1780
|
+
}
|
|
1781
|
+
destroy() {
|
|
1782
|
+
Array.from(this._connectionSubscriptions.values()).forEach((cb) => cb());
|
|
1783
|
+
this._connectionSubscriptions.clear();
|
|
1784
|
+
this._subscriptions.clear();
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
//#endregion
|
|
1788
|
+
//#region src/swarm/connection-limiter.ts
|
|
1789
|
+
var __dxlog_file$7 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/swarm/connection-limiter.ts";
|
|
1790
|
+
var MAX_CONCURRENT_INITIATING_CONNECTIONS = 50;
|
|
1791
|
+
/**
|
|
1792
|
+
* Limits the amount of concurrent connections with 'CONNECTING' state.
|
|
1793
|
+
*/
|
|
1794
|
+
var ConnectionLimiter = class {
|
|
1795
|
+
_ctx = new Context(void 0, {
|
|
1796
|
+
"~LogMeta": "~LogMeta",
|
|
1797
|
+
F: __dxlog_file$7,
|
|
1798
|
+
L: 23
|
|
1799
|
+
});
|
|
1800
|
+
_maxConcurrentInitConnections;
|
|
1801
|
+
/**
|
|
1802
|
+
* Queue of promises to resolve when initiating connections amount is below the limit.
|
|
1803
|
+
*/
|
|
1804
|
+
_waitingPromises = new ComplexMap(PublicKey.hash);
|
|
1805
|
+
resolveWaitingPromises = new DeferredTask(this._ctx, async () => {
|
|
1806
|
+
Array.from(this._waitingPromises.values()).slice(0, this._maxConcurrentInitConnections).forEach(({ resolve }) => {
|
|
1807
|
+
resolve();
|
|
1808
|
+
});
|
|
1809
|
+
});
|
|
1810
|
+
constructor({ maxConcurrentInitConnections = 50 } = {}) {
|
|
1811
|
+
this._maxConcurrentInitConnections = maxConcurrentInitConnections;
|
|
1812
|
+
}
|
|
1813
|
+
/**
|
|
1814
|
+
* @returns Promise that resolves in queue when connections amount with 'CONNECTING' state is below the limit.
|
|
1815
|
+
*/
|
|
1816
|
+
async connecting(sessionId) {
|
|
1817
|
+
invariant(!this._waitingPromises.has(sessionId), "Peer is already waiting for connection", {
|
|
1818
|
+
"~LogMeta": "~LogMeta",
|
|
1819
|
+
F: __dxlog_file$7,
|
|
1820
|
+
L: 48,
|
|
1821
|
+
S: this,
|
|
1822
|
+
A: ["!this._waitingPromises.has(sessionId)", "'Peer is already waiting for connection'"]
|
|
1823
|
+
});
|
|
1824
|
+
log("waiting", { sessionId }, {
|
|
1825
|
+
"~LogMeta": "~LogMeta",
|
|
1826
|
+
F: __dxlog_file$7,
|
|
1827
|
+
L: 49,
|
|
1828
|
+
S: this
|
|
1829
|
+
});
|
|
1830
|
+
await new Promise((resolve, reject) => {
|
|
1831
|
+
this._waitingPromises.set(sessionId, {
|
|
1832
|
+
resolve,
|
|
1833
|
+
reject
|
|
1834
|
+
});
|
|
1835
|
+
this.resolveWaitingPromises.schedule();
|
|
1836
|
+
});
|
|
1837
|
+
log("allow", { sessionId }, {
|
|
1838
|
+
"~LogMeta": "~LogMeta",
|
|
1839
|
+
F: __dxlog_file$7,
|
|
1840
|
+
L: 57,
|
|
1841
|
+
S: this
|
|
1842
|
+
});
|
|
1843
|
+
}
|
|
1844
|
+
/**
|
|
1845
|
+
* Rejects promise returned by `connecting` method.
|
|
1846
|
+
*/
|
|
1847
|
+
doneConnecting(sessionId) {
|
|
1848
|
+
log("done", { sessionId }, {
|
|
1849
|
+
"~LogMeta": "~LogMeta",
|
|
1850
|
+
F: __dxlog_file$7,
|
|
1851
|
+
L: 64,
|
|
1852
|
+
S: this
|
|
1853
|
+
});
|
|
1854
|
+
if (!this._waitingPromises.has(sessionId)) return;
|
|
1855
|
+
this._waitingPromises.get(sessionId).reject(new CancelledError());
|
|
1856
|
+
this._waitingPromises.delete(sessionId);
|
|
1857
|
+
this.resolveWaitingPromises.schedule();
|
|
1858
|
+
}
|
|
1859
|
+
};
|
|
1860
|
+
//#endregion
|
|
1861
|
+
//#region src/connection-log.ts
|
|
1862
|
+
var CONNECTION_GC_THRESHOLD = 1e3 * 60 * 15;
|
|
1863
|
+
var EventType = /* @__PURE__ */ function(EventType) {
|
|
1864
|
+
EventType["CONNECTION_STATE_CHANGED"] = "CONNECTION_STATE_CHANGED";
|
|
1865
|
+
EventType["PROTOCOL_ERROR"] = "PROTOCOL_ERROR";
|
|
1866
|
+
EventType["PROTOCOL_EXTENSIONS_INITIALIZED"] = "PROTOCOL_EXTENSIONS_INITIALIZED";
|
|
1867
|
+
EventType["PROTOCOL_EXTENSIONS_HANDSHAKE"] = "PROTOCOL_EXTENSIONS_HANDSHAKE";
|
|
1868
|
+
EventType["PROTOCOL_HANDSHAKE"] = "PROTOCOL_HANDSHAKE";
|
|
1869
|
+
return EventType;
|
|
1870
|
+
}({});
|
|
1871
|
+
var ConnectionLog = class {
|
|
1872
|
+
/**
|
|
1873
|
+
* SwarmId => info
|
|
1874
|
+
*/
|
|
1875
|
+
_swarms = new ComplexMap(PublicKey.hash);
|
|
1876
|
+
update = new Event();
|
|
1877
|
+
getSwarmInfo(swarmId) {
|
|
1878
|
+
return this._swarms.get(swarmId) ?? raise(/* @__PURE__ */ new Error(`Swarm not found: ${swarmId}`));
|
|
1879
|
+
}
|
|
1880
|
+
get swarms() {
|
|
1881
|
+
return Array.from(this._swarms.values());
|
|
1882
|
+
}
|
|
1883
|
+
joinedSwarm(swarm) {
|
|
1884
|
+
const info = {
|
|
1885
|
+
id: PublicKey.from(swarm._instanceId),
|
|
1886
|
+
topic: swarm.topic,
|
|
1887
|
+
isActive: true,
|
|
1888
|
+
label: swarm.label,
|
|
1889
|
+
connections: []
|
|
1890
|
+
};
|
|
1891
|
+
this._swarms.set(PublicKey.from(swarm._instanceId), info);
|
|
1892
|
+
this.update.emit();
|
|
1893
|
+
swarm.connectionAdded.on((connection) => {
|
|
1894
|
+
const connectionInfo = {
|
|
1895
|
+
state: ConnectionState$2.CREATED,
|
|
1896
|
+
closeReason: connection.closeReason,
|
|
1897
|
+
remotePeerId: PublicKey.from(connection.remoteInfo.peerKey),
|
|
1898
|
+
sessionId: connection.sessionId,
|
|
1899
|
+
transport: connection.transport && Object.getPrototypeOf(connection.transport).constructor.name,
|
|
1900
|
+
protocolExtensions: [],
|
|
1901
|
+
events: [],
|
|
1902
|
+
lastUpdate: /* @__PURE__ */ new Date()
|
|
1903
|
+
};
|
|
1904
|
+
info.connections.push(connectionInfo);
|
|
1905
|
+
this.update.emit();
|
|
1906
|
+
connection.stateChanged.on(async (state) => {
|
|
1907
|
+
connectionInfo.state = state;
|
|
1908
|
+
connectionInfo.closeReason = connection.closeReason;
|
|
1909
|
+
connectionInfo.lastUpdate = /* @__PURE__ */ new Date();
|
|
1910
|
+
connectionInfo.events.push({
|
|
1911
|
+
type: "CONNECTION_STATE_CHANGED",
|
|
1912
|
+
newState: state
|
|
1913
|
+
});
|
|
1914
|
+
if (state === ConnectionState$2.CONNECTED) {
|
|
1915
|
+
const details = await connection.transport?.getDetails();
|
|
1916
|
+
connectionInfo.transportDetails = details;
|
|
1917
|
+
}
|
|
1918
|
+
this.update.emit();
|
|
1919
|
+
});
|
|
1920
|
+
connection.protocol?.stats?.on((stats) => {
|
|
1921
|
+
connectionInfo.readBufferSize = stats.readBufferSize;
|
|
1922
|
+
connectionInfo.writeBufferSize = stats.writeBufferSize;
|
|
1923
|
+
connectionInfo.streams = stats.channels;
|
|
1924
|
+
connectionInfo.lastUpdate = /* @__PURE__ */ new Date();
|
|
1925
|
+
this.update.emit();
|
|
1926
|
+
});
|
|
1927
|
+
connection.transportStats?.on((stats) => {
|
|
1928
|
+
connectionInfo.transportBytesSent = stats.bytesSent;
|
|
1929
|
+
connectionInfo.transportBytesReceived = stats.bytesReceived;
|
|
1930
|
+
connectionInfo.transportPacketsSent = stats.packetsSent;
|
|
1931
|
+
connectionInfo.transportPacketsReceived = stats.packetsReceived;
|
|
1932
|
+
});
|
|
1933
|
+
gcSwarm(info);
|
|
1934
|
+
});
|
|
1935
|
+
}
|
|
1936
|
+
leftSwarm(swarm) {
|
|
1937
|
+
this.getSwarmInfo(PublicKey.from(swarm._instanceId)).isActive = false;
|
|
1938
|
+
this.update.emit();
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
var gcSwarm = (swarm) => {
|
|
1942
|
+
swarm.connections = swarm.connections?.filter((connection) => {
|
|
1943
|
+
return connection.lastUpdate ? Date.now() - connection.lastUpdate.getTime() < CONNECTION_GC_THRESHOLD : true;
|
|
1944
|
+
});
|
|
1945
|
+
};
|
|
1946
|
+
//#endregion
|
|
1947
|
+
//#region src/network-manager.ts
|
|
1948
|
+
var __dxlog_file$6 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/network-manager.ts";
|
|
1949
|
+
/**
|
|
1950
|
+
* Effect service tag for {@link SwarmNetworkManager}.
|
|
1951
|
+
*/
|
|
1952
|
+
var SwarmNetworkManagerService = class extends EffectContext.Tag("@dxos/network-manager/SwarmNetworkManager")() {};
|
|
1953
|
+
/**
|
|
1954
|
+
* Manages p2p connection to the swarm.
|
|
1955
|
+
*/
|
|
1956
|
+
var SwarmNetworkManager = class {
|
|
1957
|
+
/**
|
|
1958
|
+
* @internal
|
|
1959
|
+
*/
|
|
1960
|
+
_swarms = new ComplexMap(PublicKey.hash);
|
|
1961
|
+
_mappers = new ComplexMap(PublicKey.hash);
|
|
1962
|
+
_transportFactory;
|
|
1963
|
+
_signalManager;
|
|
1964
|
+
_messenger;
|
|
1965
|
+
_signalConnection;
|
|
1966
|
+
_connectionLimiter;
|
|
1967
|
+
_connectionLog;
|
|
1968
|
+
_peerInfo = void 0;
|
|
1969
|
+
_connectionState = ConnectionState.ONLINE;
|
|
1970
|
+
connectionStateChanged = new Event();
|
|
1971
|
+
topicsUpdated = new Event();
|
|
1972
|
+
constructor({ transportFactory, signalManager, enableDevtoolsLogging, peerInfo }) {
|
|
1973
|
+
this._transportFactory = transportFactory;
|
|
1974
|
+
this._signalManager = signalManager;
|
|
1975
|
+
this._signalManager.swarmEvent.on((event) => this._swarms.get(event.topic)?.onSwarmEvent(event));
|
|
1976
|
+
this._messenger = new Messenger({ signalManager: this._signalManager });
|
|
1977
|
+
this._signalConnection = {
|
|
1978
|
+
join: (ctx, opts) => this._signalManager.join(ctx, opts),
|
|
1979
|
+
leave: (ctx, opts) => this._signalManager.leave(ctx, opts)
|
|
1980
|
+
};
|
|
1981
|
+
this._peerInfo = peerInfo;
|
|
1982
|
+
this._connectionLimiter = new ConnectionLimiter();
|
|
1983
|
+
if (enableDevtoolsLogging) this._connectionLog = new ConnectionLog();
|
|
1984
|
+
}
|
|
1985
|
+
get connectionLog() {
|
|
1986
|
+
return this._connectionLog;
|
|
1987
|
+
}
|
|
1988
|
+
get connectionState() {
|
|
1989
|
+
return this._connectionState;
|
|
1990
|
+
}
|
|
1991
|
+
get topics() {
|
|
1992
|
+
return Array.from(this._swarms.keys());
|
|
1993
|
+
}
|
|
1994
|
+
getSwarmMap(topic) {
|
|
1995
|
+
return this._mappers.get(topic);
|
|
1996
|
+
}
|
|
1997
|
+
getSwarm(topic) {
|
|
1998
|
+
return this._swarms.get(topic);
|
|
1999
|
+
}
|
|
2000
|
+
setPeerInfo(peerInfo) {
|
|
2001
|
+
this._peerInfo = peerInfo;
|
|
2002
|
+
}
|
|
2003
|
+
getPeerInfo() {
|
|
2004
|
+
return this._peerInfo;
|
|
2005
|
+
}
|
|
2006
|
+
async open() {
|
|
2007
|
+
log("opening network manager", void 0, {
|
|
2008
|
+
"~LogMeta": "~LogMeta",
|
|
2009
|
+
F: __dxlog_file$6,
|
|
2010
|
+
L: 146,
|
|
2011
|
+
S: this
|
|
2012
|
+
});
|
|
2013
|
+
await this._messenger.open();
|
|
2014
|
+
await this._signalManager.open();
|
|
2015
|
+
log("opened network manager", void 0, {
|
|
2016
|
+
"~LogMeta": "~LogMeta",
|
|
2017
|
+
F: __dxlog_file$6,
|
|
2018
|
+
L: 149,
|
|
2019
|
+
S: this
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
async close(ctx) {
|
|
2023
|
+
for (const topic of this._swarms.keys()) await this.leaveSwarm(ctx, topic).catch((err) => {
|
|
2024
|
+
log(err, void 0, {
|
|
2025
|
+
"~LogMeta": "~LogMeta",
|
|
2026
|
+
F: __dxlog_file$6,
|
|
2027
|
+
L: 155,
|
|
2028
|
+
S: this
|
|
2029
|
+
});
|
|
2030
|
+
});
|
|
2031
|
+
await this._messenger.close();
|
|
2032
|
+
await this._signalManager.close();
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Join the swarm.
|
|
2036
|
+
*/
|
|
2037
|
+
async joinSwarm(ctx, { topic, topology, protocolProvider: protocol, label }) {
|
|
2038
|
+
assertArgument(PublicKey.isPublicKey(topic), "topic");
|
|
2039
|
+
invariant(topology, void 0, {
|
|
2040
|
+
"~LogMeta": "~LogMeta",
|
|
2041
|
+
F: __dxlog_file$6,
|
|
2042
|
+
L: 177,
|
|
2043
|
+
S: this,
|
|
2044
|
+
A: ["topology", ""]
|
|
2045
|
+
});
|
|
2046
|
+
invariant(this._peerInfo, void 0, {
|
|
2047
|
+
"~LogMeta": "~LogMeta",
|
|
2048
|
+
F: __dxlog_file$6,
|
|
2049
|
+
L: 178,
|
|
2050
|
+
S: this,
|
|
2051
|
+
A: ["this._peerInfo", ""]
|
|
2052
|
+
});
|
|
2053
|
+
assertArgument(typeof protocol === "function", "protocolProvider");
|
|
2054
|
+
if (this._swarms.has(topic)) throw new Error(`Already connected to swarm: ${PublicKey.from(topic)}`);
|
|
2055
|
+
log("joining", {
|
|
2056
|
+
topic: PublicKey.from(topic),
|
|
2057
|
+
peerInfo: this._peerInfo,
|
|
2058
|
+
topology: topology.toString()
|
|
2059
|
+
}, {
|
|
2060
|
+
"~LogMeta": "~LogMeta",
|
|
2061
|
+
F: __dxlog_file$6,
|
|
2062
|
+
L: 184,
|
|
2063
|
+
S: this
|
|
2064
|
+
});
|
|
2065
|
+
const swarm = new Swarm(topic, this._peerInfo, topology, protocol, this._messenger, this._transportFactory, label, this._connectionLimiter);
|
|
2066
|
+
swarm.errors.handle((error) => {
|
|
2067
|
+
log("swarm error", { error }, {
|
|
2068
|
+
"~LogMeta": "~LogMeta",
|
|
2069
|
+
F: __dxlog_file$6,
|
|
2070
|
+
L: 197,
|
|
2071
|
+
S: this
|
|
2072
|
+
});
|
|
2073
|
+
});
|
|
2074
|
+
this._swarms.set(topic, swarm);
|
|
2075
|
+
this._mappers.set(topic, new SwarmMapper(swarm));
|
|
2076
|
+
await swarm.open();
|
|
2077
|
+
this._signalConnection.join(ctx, {
|
|
2078
|
+
topic,
|
|
2079
|
+
peer: this._peerInfo
|
|
2080
|
+
}).catch((error) => log.catch(error, void 0, {
|
|
2081
|
+
"~LogMeta": "~LogMeta",
|
|
2082
|
+
F: __dxlog_file$6,
|
|
2083
|
+
L: 206,
|
|
2084
|
+
S: this
|
|
2085
|
+
}));
|
|
2086
|
+
this.topicsUpdated.emit();
|
|
2087
|
+
this._connectionLog?.joinedSwarm(swarm);
|
|
2088
|
+
log("joined", {
|
|
2089
|
+
topic: PublicKey.from(topic),
|
|
2090
|
+
count: this._swarms.size
|
|
2091
|
+
}, {
|
|
2092
|
+
"~LogMeta": "~LogMeta",
|
|
2093
|
+
F: __dxlog_file$6,
|
|
2094
|
+
L: 210,
|
|
2095
|
+
S: this
|
|
2096
|
+
});
|
|
2097
|
+
return { close: (ctx) => this.leaveSwarm(ctx, topic) };
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Close the connection.
|
|
2101
|
+
*/
|
|
2102
|
+
async leaveSwarm(ctx, topic) {
|
|
2103
|
+
if (!this._swarms.has(topic)) return;
|
|
2104
|
+
log("leaving", { topic: PublicKey.from(topic) }, {
|
|
2105
|
+
"~LogMeta": "~LogMeta",
|
|
2106
|
+
F: __dxlog_file$6,
|
|
2107
|
+
L: 227,
|
|
2108
|
+
S: this
|
|
2109
|
+
});
|
|
2110
|
+
const swarm = this._swarms.get(topic);
|
|
2111
|
+
await this._signalConnection.leave(ctx, {
|
|
2112
|
+
topic,
|
|
2113
|
+
peer: swarm.ownPeer
|
|
2114
|
+
});
|
|
2115
|
+
this._mappers.get(topic).destroy();
|
|
2116
|
+
this._mappers.delete(topic);
|
|
2117
|
+
this._connectionLog?.leftSwarm(swarm);
|
|
2118
|
+
await swarm.destroy();
|
|
2119
|
+
this._swarms.delete(topic);
|
|
2120
|
+
this.topicsUpdated.emit();
|
|
2121
|
+
log("left", {
|
|
2122
|
+
topic: PublicKey.from(topic),
|
|
2123
|
+
count: this._swarms.size
|
|
2124
|
+
}, {
|
|
2125
|
+
"~LogMeta": "~LogMeta",
|
|
2126
|
+
F: __dxlog_file$6,
|
|
2127
|
+
L: 241,
|
|
2128
|
+
S: this
|
|
2129
|
+
});
|
|
2130
|
+
}
|
|
2131
|
+
async setConnectionState(state) {
|
|
2132
|
+
if (state === this._connectionState) return;
|
|
2133
|
+
switch (state) {
|
|
2134
|
+
case ConnectionState.OFFLINE:
|
|
2135
|
+
this._connectionState = state;
|
|
2136
|
+
await Promise.all([...this._swarms.values()].map((swarm) => swarm.goOffline()));
|
|
2137
|
+
await this._signalManager.close();
|
|
2138
|
+
break;
|
|
2139
|
+
case ConnectionState.ONLINE:
|
|
2140
|
+
this._connectionState = state;
|
|
2141
|
+
await Promise.all([...this._swarms.values()].map((swarm) => swarm.goOnline()));
|
|
2142
|
+
await this._signalManager.open();
|
|
2143
|
+
break;
|
|
2144
|
+
}
|
|
2145
|
+
this.connectionStateChanged.emit(this._connectionState);
|
|
2146
|
+
}
|
|
2147
|
+
};
|
|
2148
|
+
__decorate([synchronized], SwarmNetworkManager.prototype, "joinSwarm", null);
|
|
2149
|
+
__decorate([synchronized], SwarmNetworkManager.prototype, "leaveSwarm", null);
|
|
2150
|
+
//#endregion
|
|
2151
|
+
//#region src/topology/fully-connected-topology.ts
|
|
2152
|
+
var __dxlog_file$5 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/topology/fully-connected-topology.ts";
|
|
2153
|
+
var FullyConnectedTopology = class {
|
|
2154
|
+
_controller;
|
|
2155
|
+
toString() {
|
|
2156
|
+
return "FullyConnectedTopology";
|
|
2157
|
+
}
|
|
2158
|
+
init(controller) {
|
|
2159
|
+
invariant(!this._controller, "Already initialized", {
|
|
2160
|
+
"~LogMeta": "~LogMeta",
|
|
2161
|
+
F: __dxlog_file$5,
|
|
2162
|
+
L: 18,
|
|
2163
|
+
S: this,
|
|
2164
|
+
A: ["!this._controller", "'Already initialized'"]
|
|
2165
|
+
});
|
|
2166
|
+
this._controller = controller;
|
|
2167
|
+
}
|
|
2168
|
+
update() {
|
|
2169
|
+
invariant(this._controller, "Not initialized", {
|
|
2170
|
+
"~LogMeta": "~LogMeta",
|
|
2171
|
+
F: __dxlog_file$5,
|
|
2172
|
+
L: 23,
|
|
2173
|
+
S: this,
|
|
2174
|
+
A: ["this._controller", "'Not initialized'"]
|
|
2175
|
+
});
|
|
2176
|
+
const { candidates: discovered } = this._controller.getState();
|
|
2177
|
+
for (const peer of discovered) this._controller.connect(peer);
|
|
2178
|
+
}
|
|
2179
|
+
async onOffer(peer) {
|
|
2180
|
+
return true;
|
|
2181
|
+
}
|
|
2182
|
+
async destroy() {}
|
|
2183
|
+
};
|
|
2184
|
+
//#endregion
|
|
2185
|
+
//#region src/transport/memory-transport.ts
|
|
2186
|
+
var __dxlog_file$4 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/memory-transport.ts";
|
|
2187
|
+
var MEMORY_TRANSPORT_DELAY = 1;
|
|
2188
|
+
/**
|
|
2189
|
+
* Creates a binary stream that delays data being sent through the stream by the specified amount of time.
|
|
2190
|
+
*/
|
|
2191
|
+
var createStreamDelay = (delay) => {
|
|
2192
|
+
return new Transform({
|
|
2193
|
+
objectMode: true,
|
|
2194
|
+
transform: (chunk, _, cb) => {
|
|
2195
|
+
setTimeout(() => cb(null, chunk), delay);
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
};
|
|
2199
|
+
var MemoryTransportFactory = { createTransport: (options) => new MemoryTransport(options) };
|
|
2200
|
+
/**
|
|
2201
|
+
* Fake transport.
|
|
2202
|
+
*/
|
|
2203
|
+
var MemoryTransport = class MemoryTransport {
|
|
2204
|
+
_options;
|
|
2205
|
+
static _connections = new ComplexMap(PublicKey.hash);
|
|
2206
|
+
_instanceId = PublicKey.random();
|
|
2207
|
+
_remote = new Trigger();
|
|
2208
|
+
_outgoingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
|
|
2209
|
+
_incomingDelay = createStreamDelay(MEMORY_TRANSPORT_DELAY);
|
|
2210
|
+
_closed = false;
|
|
2211
|
+
_remoteInstanceId;
|
|
2212
|
+
_remoteConnection;
|
|
2213
|
+
closed = new Event();
|
|
2214
|
+
connected = new Event();
|
|
2215
|
+
errors = new ErrorStream();
|
|
2216
|
+
constructor(_options) {
|
|
2217
|
+
this._options = _options;
|
|
2218
|
+
invariant(!MemoryTransport._connections.has(this._instanceId), "Duplicate memory connection", {
|
|
2219
|
+
"~LogMeta": "~LogMeta",
|
|
2220
|
+
F: __dxlog_file$4,
|
|
2221
|
+
L: 64,
|
|
2222
|
+
S: this,
|
|
2223
|
+
A: ["!MemoryTransport._connections.has(this._instanceId)", "'Duplicate memory connection'"]
|
|
2224
|
+
});
|
|
2225
|
+
MemoryTransport._connections.set(this._instanceId, this);
|
|
2226
|
+
}
|
|
2227
|
+
get isOpen() {
|
|
2228
|
+
return !this._closed;
|
|
2229
|
+
}
|
|
2230
|
+
async open() {
|
|
2231
|
+
log("opening...", void 0, {
|
|
2232
|
+
"~LogMeta": "~LogMeta",
|
|
2233
|
+
F: __dxlog_file$4,
|
|
2234
|
+
L: 74,
|
|
2235
|
+
S: this
|
|
2236
|
+
});
|
|
2237
|
+
if (this._options.initiator) {
|
|
2238
|
+
log("sending signal", void 0, {
|
|
2239
|
+
"~LogMeta": "~LogMeta",
|
|
2240
|
+
F: __dxlog_file$4,
|
|
2241
|
+
L: 78,
|
|
2242
|
+
S: this
|
|
2243
|
+
});
|
|
2244
|
+
try {
|
|
2245
|
+
await this._options.sendSignal({ payload: { transportId: this._instanceId.toHex() } });
|
|
2246
|
+
} catch (err) {
|
|
2247
|
+
if (!this._closed) this.errors.raise(toError(err));
|
|
2248
|
+
}
|
|
2249
|
+
} else this._remote.wait({ timeout: this._options.timeout ?? 1e3 }).then((remoteId) => {
|
|
2250
|
+
if (this._closed) return;
|
|
2251
|
+
this._remoteInstanceId = remoteId;
|
|
2252
|
+
this._remoteConnection = MemoryTransport._connections.get(this._remoteInstanceId);
|
|
2253
|
+
if (!this._remoteConnection) {
|
|
2254
|
+
this._closed = true;
|
|
2255
|
+
this.closed.emit();
|
|
2256
|
+
return;
|
|
2257
|
+
}
|
|
2258
|
+
invariant(!this._remoteConnection._remoteConnection, `Remote already connected: ${this._remoteInstanceId}`, {
|
|
2259
|
+
"~LogMeta": "~LogMeta",
|
|
2260
|
+
F: __dxlog_file$4,
|
|
2261
|
+
L: 104,
|
|
2262
|
+
S: this,
|
|
2263
|
+
A: ["!this._remoteConnection._remoteConnection", "`Remote already connected: ${this._remoteInstanceId}`"]
|
|
2264
|
+
});
|
|
2265
|
+
this._remoteConnection._remoteConnection = this;
|
|
2266
|
+
this._remoteConnection._remoteInstanceId = this._instanceId;
|
|
2267
|
+
log("connected", void 0, {
|
|
2268
|
+
"~LogMeta": "~LogMeta",
|
|
2269
|
+
F: __dxlog_file$4,
|
|
2270
|
+
L: 108,
|
|
2271
|
+
S: this
|
|
2272
|
+
});
|
|
2273
|
+
this._options.stream.pipe(this._outgoingDelay).pipe(this._remoteConnection._options.stream).pipe(this._incomingDelay).pipe(this._options.stream);
|
|
2274
|
+
this.connected.emit();
|
|
2275
|
+
this._remoteConnection.connected.emit();
|
|
2276
|
+
}).catch((err) => {
|
|
2277
|
+
if (this._closed) return;
|
|
2278
|
+
this.errors.raise(err);
|
|
2279
|
+
});
|
|
2280
|
+
return this;
|
|
2281
|
+
}
|
|
2282
|
+
async close() {
|
|
2283
|
+
log("closing...", void 0, {
|
|
2284
|
+
"~LogMeta": "~LogMeta",
|
|
2285
|
+
F: __dxlog_file$4,
|
|
2286
|
+
L: 130,
|
|
2287
|
+
S: this
|
|
2288
|
+
});
|
|
2289
|
+
this._closed = true;
|
|
2290
|
+
MemoryTransport._connections.delete(this._instanceId);
|
|
2291
|
+
if (this._remoteConnection) {
|
|
2292
|
+
this._remoteConnection._closed = true;
|
|
2293
|
+
MemoryTransport._connections.delete(this._remoteInstanceId);
|
|
2294
|
+
this._options.stream.unpipe(this._incomingDelay);
|
|
2295
|
+
this._incomingDelay.unpipe(this._remoteConnection._options.stream);
|
|
2296
|
+
this._remoteConnection._options.stream.unpipe(this._outgoingDelay);
|
|
2297
|
+
this._outgoingDelay.unpipe(this._options.stream);
|
|
2298
|
+
this._options.stream.unpipe(this._outgoingDelay);
|
|
2299
|
+
this._remoteConnection.closed.emit();
|
|
2300
|
+
this._remoteConnection._remoteConnection = void 0;
|
|
2301
|
+
this._remoteConnection = void 0;
|
|
2302
|
+
}
|
|
2303
|
+
this.closed.emit();
|
|
2304
|
+
log("closed", void 0, {
|
|
2305
|
+
"~LogMeta": "~LogMeta",
|
|
2306
|
+
F: __dxlog_file$4,
|
|
2307
|
+
L: 158,
|
|
2308
|
+
S: this
|
|
2309
|
+
});
|
|
2310
|
+
return this;
|
|
2311
|
+
}
|
|
2312
|
+
async onSignal({ payload }) {
|
|
2313
|
+
log("received signal", { payload }, {
|
|
2314
|
+
"~LogMeta": "~LogMeta",
|
|
2315
|
+
F: __dxlog_file$4,
|
|
2316
|
+
L: 163,
|
|
2317
|
+
S: this
|
|
2318
|
+
});
|
|
2319
|
+
if (!payload?.transportId) return;
|
|
2320
|
+
const transportId = payload.transportId;
|
|
2321
|
+
if (transportId) {
|
|
2322
|
+
const remoteId = PublicKey.fromHex(transportId);
|
|
2323
|
+
this._remote.wake(remoteId);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
async getDetails() {
|
|
2327
|
+
return this._instanceId.toHex();
|
|
2328
|
+
}
|
|
2329
|
+
async getStats() {
|
|
2330
|
+
return {
|
|
2331
|
+
bytesSent: 0,
|
|
2332
|
+
bytesReceived: 0,
|
|
2333
|
+
packetsSent: 0,
|
|
2334
|
+
packetsReceived: 0
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
__decorate([logInfo], MemoryTransport.prototype, "_instanceId", void 0);
|
|
2339
|
+
__decorate([logInfo], MemoryTransport.prototype, "_remoteInstanceId", void 0);
|
|
2340
|
+
var toError = (err) => err instanceof Error ? err : new Error(String(err));
|
|
2341
|
+
//#endregion
|
|
2342
|
+
//#region src/transport/transport.ts
|
|
2343
|
+
var TransportKind = /* @__PURE__ */ function(TransportKind) {
|
|
2344
|
+
TransportKind["WEB_RTC"] = "WEB-RTC";
|
|
2345
|
+
TransportKind["WEB_RTC_PROXY"] = "WEB-RTC_PROXY";
|
|
2346
|
+
TransportKind["MEMORY"] = "MEMORY";
|
|
2347
|
+
TransportKind["TCP"] = "TCP";
|
|
2348
|
+
return TransportKind;
|
|
2349
|
+
}({});
|
|
2350
|
+
//#endregion
|
|
2351
|
+
//#region src/transport/webrtc/rtc-connection-factory.ts
|
|
2352
|
+
/**
|
|
2353
|
+
* Use built-in browser RTCPeerConnection.
|
|
2354
|
+
*/
|
|
2355
|
+
var BrowserRtcConnectionFactory = class {
|
|
2356
|
+
async initialize() {}
|
|
2357
|
+
async onConnectionDestroyed() {}
|
|
2358
|
+
async createConnection(config) {
|
|
2359
|
+
return new RTCPeerConnection(config);
|
|
2360
|
+
}
|
|
2361
|
+
async initConnection(connection, info) {}
|
|
2362
|
+
};
|
|
2363
|
+
/**
|
|
2364
|
+
* Use `node-datachannel` polyfill.
|
|
2365
|
+
* https://github.com/paullouisageneau/libdatachannel
|
|
2366
|
+
*/
|
|
2367
|
+
var NodeRtcConnectionFactory = class NodeRtcConnectionFactory {
|
|
2368
|
+
static _createdConnections = 0;
|
|
2369
|
+
static _cleanupMutex = new Mutex();
|
|
2370
|
+
async initialize() {}
|
|
2371
|
+
async onConnectionDestroyed() {
|
|
2372
|
+
return NodeRtcConnectionFactory._cleanupMutex.executeSynchronized(async () => {
|
|
2373
|
+
if (--NodeRtcConnectionFactory._createdConnections === 0) (await import("#node-datachannel")).cleanup();
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
async createConnection(config) {
|
|
2377
|
+
return NodeRtcConnectionFactory._cleanupMutex.executeSynchronized(async () => {
|
|
2378
|
+
const { RTCPeerConnection } = await import("#node-datachannel/polyfill");
|
|
2379
|
+
NodeRtcConnectionFactory._createdConnections++;
|
|
2380
|
+
return new RTCPeerConnection(config);
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
async initConnection(connection, info) {
|
|
2384
|
+
if (info.initiator) connection.onnegotiationneeded?.(null);
|
|
2385
|
+
}
|
|
2386
|
+
};
|
|
2387
|
+
/**
|
|
2388
|
+
* Create platform-specific connection factory.
|
|
2389
|
+
*/
|
|
2390
|
+
var getRtcConnectionFactory = () => {
|
|
2391
|
+
return typeof globalThis.RTCPeerConnection === "undefined" ? new NodeRtcConnectionFactory() : new BrowserRtcConnectionFactory();
|
|
2392
|
+
};
|
|
2393
|
+
//#endregion
|
|
2394
|
+
//#region src/transport/webrtc/rtc-transport-stats.ts
|
|
2395
|
+
var describeSelectedRemoteCandidate = async (connection) => {
|
|
2396
|
+
const rc = (connection && await getRtcConnectionStats(connection))?.remoteCandidate;
|
|
2397
|
+
if (!rc) return "unavailable";
|
|
2398
|
+
if (rc.candidateType === "relay") return `${rc.ip}:${rc.port} relay for ${rc.relatedAddress}:${rc.relatedPort}`;
|
|
2399
|
+
return `${rc.ip}:${rc.port} ${rc.candidateType}`;
|
|
2400
|
+
};
|
|
2401
|
+
var createRtcTransportStats = async (connection, topic) => {
|
|
2402
|
+
const stats = connection && await getRtcConnectionStats(connection, topic);
|
|
2403
|
+
if (!stats) return {
|
|
2404
|
+
bytesSent: 0,
|
|
2405
|
+
bytesReceived: 0,
|
|
2406
|
+
packetsSent: 0,
|
|
2407
|
+
packetsReceived: 0,
|
|
2408
|
+
rawStats: {}
|
|
2409
|
+
};
|
|
2410
|
+
return {
|
|
2411
|
+
bytesSent: stats.dataChannel?.bytesSent,
|
|
2412
|
+
bytesReceived: stats.dataChannel?.bytesReceived,
|
|
2413
|
+
packetsSent: 0,
|
|
2414
|
+
packetsReceived: 0,
|
|
2415
|
+
rawStats: stats.raw
|
|
2416
|
+
};
|
|
2417
|
+
};
|
|
2418
|
+
var getRtcConnectionStats = async (connection, channelTopic) => {
|
|
2419
|
+
const stats = await connection.getStats();
|
|
2420
|
+
const statsEntries = Array.from(stats.entries());
|
|
2421
|
+
const transport = statsEntries.find(([_, entry]) => entry.type === "transport")?.[1];
|
|
2422
|
+
const selectedCandidatePair = transport && statsEntries.find(([entryId]) => entryId === transport.selectedCandidatePairId)?.[1];
|
|
2423
|
+
const remoteCandidate = selectedCandidatePair && statsEntries.find(([entryId]) => entryId === selectedCandidatePair.remoteCandidateId)?.[1];
|
|
2424
|
+
return {
|
|
2425
|
+
transport,
|
|
2426
|
+
selectedCandidatePair,
|
|
2427
|
+
dataChannel: channelTopic && statsEntries.find(([_, entry]) => entry.type === "data-channel" && entry.label === channelTopic)?.[1],
|
|
2428
|
+
remoteCandidate,
|
|
2429
|
+
raw: Object.fromEntries(stats)
|
|
2430
|
+
};
|
|
2431
|
+
};
|
|
2432
|
+
//#endregion
|
|
2433
|
+
//#region src/transport/webrtc/rtc-transport-channel.ts
|
|
2434
|
+
var __dxlog_file$3 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-transport-channel.ts";
|
|
2435
|
+
var MAX_MESSAGE_SIZE = 64 * 1024;
|
|
2436
|
+
var MAX_BUFFERED_AMOUNT = 64 * 1024;
|
|
2437
|
+
/**
|
|
2438
|
+
* A WebRTC connection data channel.
|
|
2439
|
+
* Manages a WebRTC connection to a remote peer using an abstract signalling mechanism.
|
|
2440
|
+
*/
|
|
2441
|
+
var RtcTransportChannel = class extends Resource {
|
|
2442
|
+
_connection;
|
|
2443
|
+
_options;
|
|
2444
|
+
closed = new Event();
|
|
2445
|
+
connected = new Event();
|
|
2446
|
+
errors = new ErrorStream();
|
|
2447
|
+
_channel;
|
|
2448
|
+
_stream;
|
|
2449
|
+
_streamDataFlushedCallback = null;
|
|
2450
|
+
_isChannelCreationInProgress = false;
|
|
2451
|
+
constructor(_connection, _options) {
|
|
2452
|
+
super();
|
|
2453
|
+
this._connection = _connection;
|
|
2454
|
+
this._options = _options;
|
|
2455
|
+
}
|
|
2456
|
+
get isRtcChannelCreationInProgress() {
|
|
2457
|
+
return this._isChannelCreationInProgress;
|
|
2458
|
+
}
|
|
2459
|
+
onConnectionError(error) {
|
|
2460
|
+
if (this.isOpen) this.errors.raise(error);
|
|
2461
|
+
}
|
|
2462
|
+
async _open() {
|
|
2463
|
+
invariant(!this._isChannelCreationInProgress, void 0, {
|
|
2464
|
+
"~LogMeta": "~LogMeta",
|
|
2465
|
+
F: __dxlog_file$3,
|
|
2466
|
+
L: 56,
|
|
2467
|
+
S: this,
|
|
2468
|
+
A: ["!this._isChannelCreationInProgress", ""]
|
|
2469
|
+
});
|
|
2470
|
+
this._isChannelCreationInProgress = true;
|
|
2471
|
+
this._connection.createDataChannel(this._options.topic).then((channel) => {
|
|
2472
|
+
if (this.isOpen) {
|
|
2473
|
+
this._channel = channel;
|
|
2474
|
+
this._initChannel(this._channel);
|
|
2475
|
+
} else this._safeCloseChannel(channel);
|
|
2476
|
+
}).catch((err) => {
|
|
2477
|
+
if (this.isOpen) {
|
|
2478
|
+
const error = err instanceof Error ? err : new ConnectivityError({ message: `Failed to create a channel: ${JSON.stringify(err?.message)}` });
|
|
2479
|
+
this.errors.raise(error);
|
|
2480
|
+
} else log.verbose("connection establishment failed after transport was closed", { err }, {
|
|
2481
|
+
"~LogMeta": "~LogMeta",
|
|
2482
|
+
F: __dxlog_file$3,
|
|
2483
|
+
L: 76,
|
|
2484
|
+
S: this
|
|
2485
|
+
});
|
|
2486
|
+
}).finally(() => {
|
|
2487
|
+
this._isChannelCreationInProgress = false;
|
|
2488
|
+
});
|
|
2489
|
+
}
|
|
2490
|
+
async _close() {
|
|
2491
|
+
if (this._channel) {
|
|
2492
|
+
this._safeCloseChannel(this._channel);
|
|
2493
|
+
this._channel = void 0;
|
|
2494
|
+
this._stream = void 0;
|
|
2495
|
+
}
|
|
2496
|
+
this.closed.emit();
|
|
2497
|
+
log("closed", void 0, {
|
|
2498
|
+
"~LogMeta": "~LogMeta",
|
|
2499
|
+
F: __dxlog_file$3,
|
|
2500
|
+
L: 92,
|
|
2501
|
+
S: this
|
|
2502
|
+
});
|
|
2503
|
+
}
|
|
2504
|
+
_initChannel(channel) {
|
|
2505
|
+
Object.assign(channel, {
|
|
2506
|
+
onopen: () => {
|
|
2507
|
+
if (!this.isOpen) {
|
|
2508
|
+
log.warn("channel opened in a closed transport", { topic: this._options.topic }, {
|
|
2509
|
+
"~LogMeta": "~LogMeta",
|
|
2510
|
+
F: __dxlog_file$3,
|
|
2511
|
+
L: 99,
|
|
2512
|
+
S: this
|
|
2513
|
+
});
|
|
2514
|
+
this._safeCloseChannel(channel);
|
|
2515
|
+
return;
|
|
2516
|
+
}
|
|
2517
|
+
log("onopen", void 0, {
|
|
2518
|
+
"~LogMeta": "~LogMeta",
|
|
2519
|
+
F: __dxlog_file$3,
|
|
2520
|
+
L: 104,
|
|
2521
|
+
S: this
|
|
2522
|
+
});
|
|
2523
|
+
const duplex = new Duplex({
|
|
2524
|
+
read: () => {},
|
|
2525
|
+
write: (chunk, encoding, callback) => {
|
|
2526
|
+
return this._handleChannelWrite(chunk, callback);
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
duplex.pipe(this._options.stream).pipe(duplex);
|
|
2530
|
+
this._stream = duplex;
|
|
2531
|
+
this.connected.emit();
|
|
2532
|
+
},
|
|
2533
|
+
onclose: async () => {
|
|
2534
|
+
log("onclose", void 0, {
|
|
2535
|
+
"~LogMeta": "~LogMeta",
|
|
2536
|
+
F: __dxlog_file$3,
|
|
2537
|
+
L: 117,
|
|
2538
|
+
S: this
|
|
2539
|
+
});
|
|
2540
|
+
await this.close();
|
|
2541
|
+
},
|
|
2542
|
+
onmessage: async (event) => {
|
|
2543
|
+
if (!this._stream) {
|
|
2544
|
+
log.warn("ignoring message on a closed channel", void 0, {
|
|
2545
|
+
"~LogMeta": "~LogMeta",
|
|
2546
|
+
F: __dxlog_file$3,
|
|
2547
|
+
L: 123,
|
|
2548
|
+
S: this
|
|
2549
|
+
});
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
let data = event.data;
|
|
2553
|
+
if (data instanceof ArrayBuffer) data = Buffer.from(data);
|
|
2554
|
+
else if (data instanceof Blob) data = Buffer.from(await data.arrayBuffer());
|
|
2555
|
+
this._stream.push(data);
|
|
2556
|
+
},
|
|
2557
|
+
onerror: (event) => {
|
|
2558
|
+
if (this.isOpen) {
|
|
2559
|
+
const err = event.error instanceof Error ? event.error : /* @__PURE__ */ new Error(`Datachannel error: ${event.type}.`);
|
|
2560
|
+
this.errors.raise(err);
|
|
2561
|
+
}
|
|
2562
|
+
},
|
|
2563
|
+
onbufferedamountlow: () => {
|
|
2564
|
+
const cb = this._streamDataFlushedCallback;
|
|
2565
|
+
this._streamDataFlushedCallback = null;
|
|
2566
|
+
cb?.();
|
|
2567
|
+
}
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
async _handleChannelWrite(chunk, callback) {
|
|
2571
|
+
if (!this._channel) {
|
|
2572
|
+
log.warn("writing to a channel after a connection was closed", void 0, {
|
|
2573
|
+
"~LogMeta": "~LogMeta",
|
|
2574
|
+
F: __dxlog_file$3,
|
|
2575
|
+
L: 153,
|
|
2576
|
+
S: this
|
|
2577
|
+
});
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
if (chunk.length > MAX_MESSAGE_SIZE) {
|
|
2581
|
+
const error = /* @__PURE__ */ new Error(`Message too large: ${chunk.length} > ${MAX_MESSAGE_SIZE}.`);
|
|
2582
|
+
this.errors.raise(error);
|
|
2583
|
+
callback();
|
|
2584
|
+
return;
|
|
2585
|
+
}
|
|
2586
|
+
try {
|
|
2587
|
+
this._channel.send(chunk);
|
|
2588
|
+
} catch (err) {
|
|
2589
|
+
this.errors.raise(err);
|
|
2590
|
+
callback();
|
|
2591
|
+
return;
|
|
2592
|
+
}
|
|
2593
|
+
if (this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {
|
|
2594
|
+
if (this._streamDataFlushedCallback !== null) log.error("consumer trying to write before we are ready for more data", void 0, {
|
|
2595
|
+
"~LogMeta": "~LogMeta",
|
|
2596
|
+
F: __dxlog_file$3,
|
|
2597
|
+
L: 174,
|
|
2598
|
+
S: this
|
|
2599
|
+
});
|
|
2600
|
+
this._streamDataFlushedCallback = callback;
|
|
2601
|
+
} else callback();
|
|
2602
|
+
}
|
|
2603
|
+
_safeCloseChannel(channel) {
|
|
2604
|
+
try {
|
|
2605
|
+
channel.close();
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
log.catch(error, void 0, {
|
|
2608
|
+
"~LogMeta": "~LogMeta",
|
|
2609
|
+
F: __dxlog_file$3,
|
|
2610
|
+
L: 186,
|
|
2611
|
+
S: this
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
onSignal(signal) {
|
|
2616
|
+
return this._connection.onSignal(signal);
|
|
2617
|
+
}
|
|
2618
|
+
async getDetails() {
|
|
2619
|
+
return describeSelectedRemoteCandidate(this._connection.currentConnection);
|
|
2620
|
+
}
|
|
2621
|
+
async getStats() {
|
|
2622
|
+
return createRtcTransportStats(this._connection.currentConnection, this._options.topic);
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
//#endregion
|
|
2626
|
+
//#region src/transport/webrtc/utils.ts
|
|
2627
|
+
var chooseInitiatorPeer = (peer1Key, peer2Key) => peer1Key < peer2Key ? peer1Key : peer2Key;
|
|
2628
|
+
var areSdpEqual = (sdp1, sdp2) => {
|
|
2629
|
+
const sdp1Lines = deduplicatedSdpLines(sdp1);
|
|
2630
|
+
const sdp2Lines = deduplicatedSdpLines(sdp2);
|
|
2631
|
+
if (sdp1Lines.length !== sdp2Lines.length) return false;
|
|
2632
|
+
return sdp1Lines.every((line, idx) => line === sdp2Lines[idx]);
|
|
2633
|
+
};
|
|
2634
|
+
/**
|
|
2635
|
+
* For some reason libdatachannel duplicates some attributes after an sdp is set.
|
|
2636
|
+
* So the following test might fail:
|
|
2637
|
+
* conn.setRemoteDescription(sdp);
|
|
2638
|
+
* expect(conn.remoteDescription.sdp).toEqual(sdp);
|
|
2639
|
+
*/
|
|
2640
|
+
var deduplicatedSdpLines = (sdp) => {
|
|
2641
|
+
const deduplicatedLines = [];
|
|
2642
|
+
const seenLines = [];
|
|
2643
|
+
for (const line of sdp.split("\r\n")) {
|
|
2644
|
+
if (line.startsWith("m")) seenLines.length = 0;
|
|
2645
|
+
if (seenLines.includes(line)) continue;
|
|
2646
|
+
seenLines.push(line);
|
|
2647
|
+
deduplicatedLines.push(line);
|
|
2648
|
+
}
|
|
2649
|
+
return deduplicatedLines;
|
|
2650
|
+
};
|
|
2651
|
+
//#endregion
|
|
2652
|
+
//#region src/transport/webrtc/rtc-peer-connection.ts
|
|
2653
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-peer-connection.ts";
|
|
2654
|
+
/**
|
|
2655
|
+
* A factory for rtc Transport implementations for a particular peer.
|
|
2656
|
+
* Contains WebRTC connection establishment logic.
|
|
2657
|
+
* When the first Transport is opened a connection is established and kept until all the transports are closed.
|
|
2658
|
+
*/
|
|
2659
|
+
var RtcPeerConnection = class {
|
|
2660
|
+
_factory;
|
|
2661
|
+
_options;
|
|
2662
|
+
_channelCreatedCallbacks = /* @__PURE__ */ new Map();
|
|
2663
|
+
_transportChannels = /* @__PURE__ */ new Map();
|
|
2664
|
+
_dataChannels = /* @__PURE__ */ new Map();
|
|
2665
|
+
_readyForCandidates = new Trigger();
|
|
2666
|
+
_offerProcessingMutex = new Mutex();
|
|
2667
|
+
/**
|
|
2668
|
+
* Can't use peer.connection.initiator, because if two connections to the same peer are created in
|
|
2669
|
+
* different swarms, we might be the initiator of the first one, but not of the other one.
|
|
2670
|
+
* Use a stable peer keypair property (key ordering) to decide who's acting as the initiator of
|
|
2671
|
+
* transport connection establishment and data channel creation.
|
|
2672
|
+
*/
|
|
2673
|
+
_initiator;
|
|
2674
|
+
_connection;
|
|
2675
|
+
constructor(_factory, _options) {
|
|
2676
|
+
this._factory = _factory;
|
|
2677
|
+
this._options = _options;
|
|
2678
|
+
this._initiator = chooseInitiatorPeer(_options.ownPeerKey, _options.remotePeerKey) === _options.ownPeerKey;
|
|
2679
|
+
}
|
|
2680
|
+
get transportChannelCount() {
|
|
2681
|
+
return this._transportChannels.size;
|
|
2682
|
+
}
|
|
2683
|
+
get currentConnection() {
|
|
2684
|
+
return this._connection;
|
|
2685
|
+
}
|
|
2686
|
+
async createDataChannel(topic) {
|
|
2687
|
+
const connection = await this._openConnection();
|
|
2688
|
+
if (!this._transportChannels.has(topic)) {
|
|
2689
|
+
if (!this._transportChannels.size) this._lockAndCloseConnection();
|
|
2690
|
+
throw new Error("Transport closed while connection was being open");
|
|
2691
|
+
}
|
|
2692
|
+
if (this._initiator) {
|
|
2693
|
+
const channel = connection.createDataChannel(topic);
|
|
2694
|
+
this._dataChannels.set(topic, channel);
|
|
2695
|
+
return channel;
|
|
2696
|
+
} else {
|
|
2697
|
+
const existingChannel = this._dataChannels.get(topic);
|
|
2698
|
+
if (existingChannel) return existingChannel;
|
|
2699
|
+
log("waiting for initiator-peer to open a data channel", void 0, {
|
|
2700
|
+
"~LogMeta": "~LogMeta",
|
|
2701
|
+
F: __dxlog_file$2,
|
|
2702
|
+
L: 93,
|
|
2703
|
+
S: this
|
|
2704
|
+
});
|
|
2705
|
+
return new Promise((resolve, reject) => {
|
|
2706
|
+
this._channelCreatedCallbacks.set(topic, {
|
|
2707
|
+
resolve,
|
|
2708
|
+
reject
|
|
2709
|
+
});
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
createTransportChannel(options) {
|
|
2714
|
+
const channel = new RtcTransportChannel(this, options);
|
|
2715
|
+
this._transportChannels.set(options.topic, channel);
|
|
2716
|
+
channel.closed.on(() => {
|
|
2717
|
+
this._transportChannels.delete(options.topic);
|
|
2718
|
+
if (this._transportChannels.size === 0) this._lockAndCloseConnection();
|
|
2719
|
+
});
|
|
2720
|
+
return channel;
|
|
2721
|
+
}
|
|
2722
|
+
async _openConnection() {
|
|
2723
|
+
if (this._connection) return this._connection;
|
|
2724
|
+
log("initializing connection...", () => ({ remotePeer: this._options.remotePeerKey }), {
|
|
2725
|
+
"~LogMeta": "~LogMeta",
|
|
2726
|
+
F: __dxlog_file$2,
|
|
2727
|
+
L: 118,
|
|
2728
|
+
S: this
|
|
2729
|
+
});
|
|
2730
|
+
const config = await this._loadConnectionConfig();
|
|
2731
|
+
const connection = await this._factory.createConnection(config);
|
|
2732
|
+
const iceCandidateErrors = [];
|
|
2733
|
+
Object.assign(connection, {
|
|
2734
|
+
onnegotiationneeded: async () => {
|
|
2735
|
+
invariant(this._initiator, void 0, {
|
|
2736
|
+
"~LogMeta": "~LogMeta",
|
|
2737
|
+
F: __dxlog_file$2,
|
|
2738
|
+
L: 133,
|
|
2739
|
+
S: this,
|
|
2740
|
+
A: ["this._initiator", ""]
|
|
2741
|
+
});
|
|
2742
|
+
if (connection !== this._connection) {
|
|
2743
|
+
this._onConnectionCallbackAfterClose("onnegotiationneeded", connection);
|
|
2744
|
+
return;
|
|
2745
|
+
}
|
|
2746
|
+
log("onnegotiationneeded", void 0, {
|
|
2747
|
+
"~LogMeta": "~LogMeta",
|
|
2748
|
+
F: __dxlog_file$2,
|
|
2749
|
+
L: 140,
|
|
2750
|
+
S: this
|
|
2751
|
+
});
|
|
2752
|
+
try {
|
|
2753
|
+
const offer = await connection.createOffer();
|
|
2754
|
+
await connection.setLocalDescription(offer);
|
|
2755
|
+
await this._sendDescription(connection, offer);
|
|
2756
|
+
} catch (err) {
|
|
2757
|
+
this._lockAndAbort(connection, err);
|
|
2758
|
+
}
|
|
2759
|
+
},
|
|
2760
|
+
onicecandidate: async (event) => {
|
|
2761
|
+
if (connection !== this._connection) {
|
|
2762
|
+
this._onConnectionCallbackAfterClose("onicecandidate", connection);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
if (event.candidate) {
|
|
2766
|
+
log("onicecandidate", { candidate: event.candidate.candidate }, {
|
|
2767
|
+
"~LogMeta": "~LogMeta",
|
|
2768
|
+
F: __dxlog_file$2,
|
|
2769
|
+
L: 159,
|
|
2770
|
+
S: this
|
|
2771
|
+
});
|
|
2772
|
+
await this._sendIceCandidate(event.candidate);
|
|
2773
|
+
} else log("onicecandidate gathering complete", void 0, {
|
|
2774
|
+
"~LogMeta": "~LogMeta",
|
|
2775
|
+
F: __dxlog_file$2,
|
|
2776
|
+
L: 162,
|
|
2777
|
+
S: this
|
|
2778
|
+
});
|
|
2779
|
+
},
|
|
2780
|
+
onicecandidateerror: (event) => {
|
|
2781
|
+
const { url, errorCode, errorText } = event;
|
|
2782
|
+
iceCandidateErrors.push({
|
|
2783
|
+
url,
|
|
2784
|
+
errorCode,
|
|
2785
|
+
errorText
|
|
2786
|
+
});
|
|
2787
|
+
},
|
|
2788
|
+
oniceconnectionstatechange: () => {
|
|
2789
|
+
if (connection !== this._connection) {
|
|
2790
|
+
this._onConnectionCallbackAfterClose("oniceconnectionstatechange", connection);
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
log("oniceconnectionstatechange", { state: connection.iceConnectionState }, {
|
|
2794
|
+
"~LogMeta": "~LogMeta",
|
|
2795
|
+
F: __dxlog_file$2,
|
|
2796
|
+
L: 182,
|
|
2797
|
+
S: this
|
|
2798
|
+
});
|
|
2799
|
+
if (connection.iceConnectionState === "failed") this._lockAndAbort(connection, createIceFailureError(iceCandidateErrors));
|
|
2800
|
+
},
|
|
2801
|
+
onconnectionstatechange: () => {
|
|
2802
|
+
if (connection !== this._connection) {
|
|
2803
|
+
if (connection.connectionState !== "closed" && connection.connectionState !== "failed") this._onConnectionCallbackAfterClose("onconnectionstatechange", connection);
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
log("onconnectionstatechange", { state: connection.connectionState }, {
|
|
2807
|
+
"~LogMeta": "~LogMeta",
|
|
2808
|
+
F: __dxlog_file$2,
|
|
2809
|
+
L: 199,
|
|
2810
|
+
S: this
|
|
2811
|
+
});
|
|
2812
|
+
if (connection.connectionState === "failed") this._lockAndAbort(connection, /* @__PURE__ */ new Error("Connection failed."));
|
|
2813
|
+
},
|
|
2814
|
+
onsignalingstatechange: () => {
|
|
2815
|
+
log("onsignalingstatechange", { state: connection.signalingState }, {
|
|
2816
|
+
"~LogMeta": "~LogMeta",
|
|
2817
|
+
F: __dxlog_file$2,
|
|
2818
|
+
L: 206,
|
|
2819
|
+
S: this
|
|
2820
|
+
});
|
|
2821
|
+
},
|
|
2822
|
+
ondatachannel: (event) => {
|
|
2823
|
+
invariant(!this._initiator, "Initiator is expected to create data channels.", {
|
|
2824
|
+
"~LogMeta": "~LogMeta",
|
|
2825
|
+
F: __dxlog_file$2,
|
|
2826
|
+
L: 212,
|
|
2827
|
+
S: this,
|
|
2828
|
+
A: ["!this._initiator", "'Initiator is expected to create data channels.'"]
|
|
2829
|
+
});
|
|
2830
|
+
if (connection !== this._connection) {
|
|
2831
|
+
this._onConnectionCallbackAfterClose("ondatachannel", connection);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
log("ondatachannel", { label: event.channel.label }, {
|
|
2835
|
+
"~LogMeta": "~LogMeta",
|
|
2836
|
+
F: __dxlog_file$2,
|
|
2837
|
+
L: 219,
|
|
2838
|
+
S: this
|
|
2839
|
+
});
|
|
2840
|
+
this._dataChannels.set(event.channel.label, event.channel);
|
|
2841
|
+
const pendingCallback = this._channelCreatedCallbacks.get(event.channel.label);
|
|
2842
|
+
if (pendingCallback) {
|
|
2843
|
+
this._channelCreatedCallbacks.delete(event.channel.label);
|
|
2844
|
+
pendingCallback.resolve(event.channel);
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
});
|
|
2848
|
+
this._connection = connection;
|
|
2849
|
+
this._readyForCandidates.reset();
|
|
2850
|
+
await this._factory.initConnection(connection, { initiator: this._initiator });
|
|
2851
|
+
return this._connection;
|
|
2852
|
+
}
|
|
2853
|
+
async _lockAndAbort(connection, error) {
|
|
2854
|
+
this._abortConnection(connection, error);
|
|
2855
|
+
}
|
|
2856
|
+
_abortConnection(connection, error) {
|
|
2857
|
+
if (connection !== this._connection) {
|
|
2858
|
+
log.error("attempted to abort an inactive connection", { error }, {
|
|
2859
|
+
"~LogMeta": "~LogMeta",
|
|
2860
|
+
F: __dxlog_file$2,
|
|
2861
|
+
L: 244,
|
|
2862
|
+
S: this
|
|
2863
|
+
});
|
|
2864
|
+
this._safeCloseConnection(connection);
|
|
2865
|
+
return;
|
|
2866
|
+
}
|
|
2867
|
+
for (const [topic, pendingCallback] of this._channelCreatedCallbacks.entries()) {
|
|
2868
|
+
pendingCallback.reject(error);
|
|
2869
|
+
this._transportChannels.delete(topic);
|
|
2870
|
+
}
|
|
2871
|
+
this._channelCreatedCallbacks.clear();
|
|
2872
|
+
for (const channel of this._transportChannels.values()) channel.onConnectionError(error);
|
|
2873
|
+
this._transportChannels.clear();
|
|
2874
|
+
this._safeCloseConnection();
|
|
2875
|
+
log("connection aborted", { reason: error.message }, {
|
|
2876
|
+
"~LogMeta": "~LogMeta",
|
|
2877
|
+
F: __dxlog_file$2,
|
|
2878
|
+
L: 258,
|
|
2879
|
+
S: this
|
|
2880
|
+
});
|
|
2881
|
+
}
|
|
2882
|
+
async _lockAndCloseConnection() {
|
|
2883
|
+
invariant(this._transportChannels.size === 0, void 0, {
|
|
2884
|
+
"~LogMeta": "~LogMeta",
|
|
2885
|
+
F: __dxlog_file$2,
|
|
2886
|
+
L: 263,
|
|
2887
|
+
S: this,
|
|
2888
|
+
A: ["this._transportChannels.size === 0", ""]
|
|
2889
|
+
});
|
|
2890
|
+
if (this._connection) {
|
|
2891
|
+
this._safeCloseConnection();
|
|
2892
|
+
log("connection closed", void 0, {
|
|
2893
|
+
"~LogMeta": "~LogMeta",
|
|
2894
|
+
F: __dxlog_file$2,
|
|
2895
|
+
L: 266,
|
|
2896
|
+
S: this
|
|
2897
|
+
});
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
async onSignal(signal) {
|
|
2901
|
+
const connection = this._connection;
|
|
2902
|
+
if (!connection) {
|
|
2903
|
+
log.warn("a signal ignored because the connection was closed", { type: signal.payload.data.type }, {
|
|
2904
|
+
"~LogMeta": "~LogMeta",
|
|
2905
|
+
F: __dxlog_file$2,
|
|
2906
|
+
L: 274,
|
|
2907
|
+
S: this
|
|
2908
|
+
});
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
const data = signal.payload.data;
|
|
2912
|
+
switch (data.type) {
|
|
2913
|
+
case "offer":
|
|
2914
|
+
await this._offerProcessingMutex.executeSynchronized(async () => {
|
|
2915
|
+
if (isRemoteDescriptionSet(connection, data)) return;
|
|
2916
|
+
if (connection.connectionState !== "new") {
|
|
2917
|
+
this._abortConnection(connection, /* @__PURE__ */ new Error(`Received an offer in ${connection.connectionState}.`));
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
try {
|
|
2921
|
+
await connection.setRemoteDescription({
|
|
2922
|
+
type: data.type,
|
|
2923
|
+
sdp: data.sdp
|
|
2924
|
+
});
|
|
2925
|
+
const answer = await connection.createAnswer();
|
|
2926
|
+
await connection.setLocalDescription(answer);
|
|
2927
|
+
await this._sendDescription(connection, answer);
|
|
2928
|
+
this._onSessionNegotiated(connection);
|
|
2929
|
+
} catch (err) {
|
|
2930
|
+
this._abortConnection(connection, new Error("Error handling a remote offer.", { cause: err }));
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2933
|
+
break;
|
|
2934
|
+
case "answer":
|
|
2935
|
+
await this._offerProcessingMutex.executeSynchronized(async () => {
|
|
2936
|
+
try {
|
|
2937
|
+
if (isRemoteDescriptionSet(connection, data)) return;
|
|
2938
|
+
if (connection.signalingState !== "have-local-offer") {
|
|
2939
|
+
this._abortConnection(connection, /* @__PURE__ */ new Error(`Unexpected answer from remote peer, signalingState was ${connection.signalingState}.`));
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2942
|
+
await connection.setRemoteDescription({
|
|
2943
|
+
type: data.type,
|
|
2944
|
+
sdp: data.sdp
|
|
2945
|
+
});
|
|
2946
|
+
this._onSessionNegotiated(connection);
|
|
2947
|
+
} catch (err) {
|
|
2948
|
+
this._abortConnection(connection, new Error("Error handling a remote answer.", { cause: err }));
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
break;
|
|
2952
|
+
case "candidate":
|
|
2953
|
+
this._processIceCandidate(connection, data.candidate);
|
|
2954
|
+
break;
|
|
2955
|
+
default:
|
|
2956
|
+
this._abortConnection(connection, /* @__PURE__ */ new Error(`Unknown signal type ${data.type}.`));
|
|
2957
|
+
break;
|
|
2958
|
+
}
|
|
2959
|
+
log("signal processed", { type: data.type }, {
|
|
2960
|
+
"~LogMeta": "~LogMeta",
|
|
2961
|
+
F: __dxlog_file$2,
|
|
2962
|
+
L: 333,
|
|
2963
|
+
S: this
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
async _processIceCandidate(connection, candidate) {
|
|
2967
|
+
try {
|
|
2968
|
+
await this._readyForCandidates.wait();
|
|
2969
|
+
if (connection === this._connection) {
|
|
2970
|
+
log("adding ice candidate", { candidate }, {
|
|
2971
|
+
"~LogMeta": "~LogMeta",
|
|
2972
|
+
F: __dxlog_file$2,
|
|
2973
|
+
L: 341,
|
|
2974
|
+
S: this
|
|
2975
|
+
});
|
|
2976
|
+
await connection.addIceCandidate(candidate);
|
|
2977
|
+
}
|
|
2978
|
+
} catch (err) {
|
|
2979
|
+
log.catch(err, void 0, {
|
|
2980
|
+
"~LogMeta": "~LogMeta",
|
|
2981
|
+
F: __dxlog_file$2,
|
|
2982
|
+
L: 345,
|
|
2983
|
+
S: this
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
_onSessionNegotiated(connection) {
|
|
2988
|
+
if (connection === this._connection) {
|
|
2989
|
+
log("ready to process ice candidates", void 0, {
|
|
2990
|
+
"~LogMeta": "~LogMeta",
|
|
2991
|
+
F: __dxlog_file$2,
|
|
2992
|
+
L: 351,
|
|
2993
|
+
S: this
|
|
2994
|
+
});
|
|
2995
|
+
this._readyForCandidates.wake();
|
|
2996
|
+
} else log.warn("session was negotiated after connection became inactive", void 0, {
|
|
2997
|
+
"~LogMeta": "~LogMeta",
|
|
2998
|
+
F: __dxlog_file$2,
|
|
2999
|
+
L: 354,
|
|
3000
|
+
S: this
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
_onConnectionCallbackAfterClose(callback, connection) {
|
|
3004
|
+
log.warn("callback invoked after a connection was destroyed, this is probably a bug", {
|
|
3005
|
+
callback,
|
|
3006
|
+
state: connection.connectionState
|
|
3007
|
+
}, {
|
|
3008
|
+
"~LogMeta": "~LogMeta",
|
|
3009
|
+
F: __dxlog_file$2,
|
|
3010
|
+
L: 359,
|
|
3011
|
+
S: this
|
|
3012
|
+
});
|
|
3013
|
+
this._safeCloseConnection(connection);
|
|
3014
|
+
}
|
|
3015
|
+
_safeCloseConnection(connection = this._connection) {
|
|
3016
|
+
const resetFields = this._connection && connection === this._connection;
|
|
3017
|
+
try {
|
|
3018
|
+
connection?.close();
|
|
3019
|
+
} catch (err) {
|
|
3020
|
+
log.catch(err, void 0, {
|
|
3021
|
+
"~LogMeta": "~LogMeta",
|
|
3022
|
+
F: __dxlog_file$2,
|
|
3023
|
+
L: 371,
|
|
3024
|
+
S: this
|
|
3025
|
+
});
|
|
3026
|
+
}
|
|
3027
|
+
if (resetFields) {
|
|
3028
|
+
this._connection = void 0;
|
|
3029
|
+
this._dataChannels.clear();
|
|
3030
|
+
this._readyForCandidates.wake();
|
|
3031
|
+
this._factory.onConnectionDestroyed().catch((err) => log.catch(err, void 0, {
|
|
3032
|
+
"~LogMeta": "~LogMeta",
|
|
3033
|
+
F: __dxlog_file$2,
|
|
3034
|
+
L: 377,
|
|
3035
|
+
S: this
|
|
3036
|
+
}));
|
|
3037
|
+
for (const [_, pendingCallback] of this._channelCreatedCallbacks.entries()) pendingCallback.reject("Connection closed.");
|
|
3038
|
+
this._channelCreatedCallbacks.clear();
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
async _loadConnectionConfig() {
|
|
3042
|
+
const config = { ...this._options.webrtcConfig };
|
|
3043
|
+
try {
|
|
3044
|
+
const providedIceServers = await this._options.iceProvider?.getIceServers() ?? [];
|
|
3045
|
+
if (providedIceServers.length > 0) config.iceServers = [...config.iceServers ?? [], ...providedIceServers];
|
|
3046
|
+
} catch (error) {
|
|
3047
|
+
log.catch(error, void 0, {
|
|
3048
|
+
"~LogMeta": "~LogMeta",
|
|
3049
|
+
F: __dxlog_file$2,
|
|
3050
|
+
L: 393,
|
|
3051
|
+
S: this
|
|
3052
|
+
});
|
|
3053
|
+
}
|
|
3054
|
+
return config;
|
|
3055
|
+
}
|
|
3056
|
+
async _sendIceCandidate(candidate) {
|
|
3057
|
+
try {
|
|
3058
|
+
await this._options.sendSignal({ payload: { data: {
|
|
3059
|
+
type: "candidate",
|
|
3060
|
+
candidate: {
|
|
3061
|
+
candidate: candidate.candidate,
|
|
3062
|
+
sdpMLineIndex: candidate.sdpMLineIndex ?? "0",
|
|
3063
|
+
sdpMid: candidate.sdpMid ?? "0"
|
|
3064
|
+
}
|
|
3065
|
+
} } });
|
|
3066
|
+
} catch (err) {
|
|
3067
|
+
log.warn("signaling error", { err }, {
|
|
3068
|
+
"~LogMeta": "~LogMeta",
|
|
3069
|
+
F: __dxlog_file$2,
|
|
3070
|
+
L: 414,
|
|
3071
|
+
S: this
|
|
3072
|
+
});
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
async _sendDescription(connection, description) {
|
|
3076
|
+
if (connection !== this._connection) return;
|
|
3077
|
+
const data = {
|
|
3078
|
+
type: description.type,
|
|
3079
|
+
sdp: description.sdp
|
|
3080
|
+
};
|
|
3081
|
+
await this._options.sendSignal({ payload: { data } });
|
|
3082
|
+
}
|
|
3083
|
+
get _connectionInfo() {
|
|
3084
|
+
return {
|
|
3085
|
+
...this._connection && {
|
|
3086
|
+
connectionState: this._connection.connectionState,
|
|
3087
|
+
iceConnectionState: this._connection.iceConnectionState,
|
|
3088
|
+
iceGatheringState: this._connection.iceGatheringState,
|
|
3089
|
+
signalingState: this._connection.signalingState,
|
|
3090
|
+
remoteDescription: this._connection.remoteDescription,
|
|
3091
|
+
localDescription: this._connection.localDescription
|
|
3092
|
+
},
|
|
3093
|
+
ts: Date.now(),
|
|
3094
|
+
remotePeerKey: this._options.remotePeerKey,
|
|
3095
|
+
channels: [...this._transportChannels.keys()].map((topic) => topic),
|
|
3096
|
+
config: this._connection?.getConfiguration()
|
|
3097
|
+
};
|
|
3098
|
+
}
|
|
3099
|
+
get _loggerContext() {
|
|
3100
|
+
return {
|
|
3101
|
+
ownPeerKey: this._options.ownPeerKey,
|
|
3102
|
+
remotePeerKey: this._options.remotePeerKey,
|
|
3103
|
+
initiator: this._initiator,
|
|
3104
|
+
channels: this._transportChannels.size
|
|
3105
|
+
};
|
|
3106
|
+
}
|
|
3107
|
+
};
|
|
3108
|
+
__decorate([synchronized], RtcPeerConnection.prototype, "_openConnection", null);
|
|
3109
|
+
__decorate([synchronized], RtcPeerConnection.prototype, "_lockAndAbort", null);
|
|
3110
|
+
__decorate([synchronized], RtcPeerConnection.prototype, "_lockAndCloseConnection", null);
|
|
3111
|
+
__decorate([synchronized], RtcPeerConnection.prototype, "onSignal", null);
|
|
3112
|
+
__decorate([logInfo], RtcPeerConnection.prototype, "_loggerContext", null);
|
|
3113
|
+
var isRemoteDescriptionSet = (connection, data) => {
|
|
3114
|
+
if (!connection.remoteDescription?.type || connection.remoteDescription?.type !== data.type) return false;
|
|
3115
|
+
return areSdpEqual(connection.remoteDescription.sdp, data.sdp);
|
|
3116
|
+
};
|
|
3117
|
+
var createIceFailureError = (details) => {
|
|
3118
|
+
return new ConnectivityError({ message: `ICE failed:\n${details.map(({ url, errorCode, errorText }) => `${errorCode} ${url}: ${errorText}`).join("\n")}` });
|
|
3119
|
+
};
|
|
3120
|
+
//#endregion
|
|
3121
|
+
//#region src/transport/webrtc/rtc-transport-factory.ts
|
|
3122
|
+
var createRtcTransportFactory = (webrtcConfig, iceProvider) => {
|
|
3123
|
+
const connectionFactory = getRtcConnectionFactory();
|
|
3124
|
+
return { createTransport: (options) => {
|
|
3125
|
+
return new RtcPeerConnection(connectionFactory, {
|
|
3126
|
+
ownPeerKey: options.ownPeerKey,
|
|
3127
|
+
remotePeerKey: options.remotePeerKey,
|
|
3128
|
+
sendSignal: options.sendSignal,
|
|
3129
|
+
legacyInitiator: options.initiator,
|
|
3130
|
+
webrtcConfig,
|
|
3131
|
+
iceProvider
|
|
3132
|
+
}).createTransportChannel(options);
|
|
3133
|
+
} };
|
|
3134
|
+
};
|
|
3135
|
+
//#endregion
|
|
3136
|
+
//#region src/transport/webrtc/rtc-transport-proxy.ts
|
|
3137
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-transport-proxy.ts";
|
|
3138
|
+
var RPC_TIMEOUT = 1e4;
|
|
3139
|
+
var CLOSE_RPC_TIMEOUT = 3e3;
|
|
3140
|
+
var RESP_MIN_THRESHOLD = 500;
|
|
3141
|
+
var RtcTransportProxy = class extends Resource {
|
|
3142
|
+
_options;
|
|
3143
|
+
_proxyId = PublicKey.random();
|
|
3144
|
+
closed = new Event();
|
|
3145
|
+
connected = new Event();
|
|
3146
|
+
errors = new ErrorStream();
|
|
3147
|
+
_serviceStream;
|
|
3148
|
+
constructor(_options) {
|
|
3149
|
+
super();
|
|
3150
|
+
this._options = _options;
|
|
3151
|
+
}
|
|
3152
|
+
async _open() {
|
|
3153
|
+
let stream;
|
|
3154
|
+
try {
|
|
3155
|
+
stream = this._options.bridgeService.open({
|
|
3156
|
+
proxyId: this._proxyId,
|
|
3157
|
+
remotePeerKey: this._options.remotePeerKey,
|
|
3158
|
+
ownPeerKey: this._options.ownPeerKey,
|
|
3159
|
+
topic: this._options.topic,
|
|
3160
|
+
initiator: this._options.initiator ?? false
|
|
3161
|
+
}, { timeout: RPC_TIMEOUT });
|
|
3162
|
+
} catch (error) {
|
|
3163
|
+
this.errors.raise(error);
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
this._serviceStream = stream;
|
|
3167
|
+
stream.waitUntilReady().then(() => {
|
|
3168
|
+
stream.subscribe(async (event) => {
|
|
3169
|
+
log("rtc transport proxy event", event, {
|
|
3170
|
+
"~LogMeta": "~LogMeta",
|
|
3171
|
+
F: __dxlog_file$1,
|
|
3172
|
+
L: 66,
|
|
3173
|
+
S: this
|
|
3174
|
+
});
|
|
3175
|
+
if (event.connection) await this._handleConnection(event.connection);
|
|
3176
|
+
else if (event.data) this._handleData(event.data);
|
|
3177
|
+
else if (event.signal) await this._handleSignal(event.signal);
|
|
3178
|
+
}, (err) => {
|
|
3179
|
+
log("rtc bridge stream closed", { err }, {
|
|
3180
|
+
"~LogMeta": "~LogMeta",
|
|
3181
|
+
F: __dxlog_file$1,
|
|
3182
|
+
L: 76,
|
|
3183
|
+
S: this
|
|
3184
|
+
});
|
|
3185
|
+
if (err) this._raiseIfOpen(err);
|
|
3186
|
+
else this.close();
|
|
3187
|
+
});
|
|
3188
|
+
const connectorStream = new Writable({ write: (chunk, _, callback) => {
|
|
3189
|
+
const sendStartMs = Date.now();
|
|
3190
|
+
this._options.bridgeService.sendData({
|
|
3191
|
+
proxyId: this._proxyId,
|
|
3192
|
+
payload: chunk
|
|
3193
|
+
}, { timeout: RPC_TIMEOUT }).then(() => {
|
|
3194
|
+
if (Date.now() - sendStartMs > RESP_MIN_THRESHOLD) {
|
|
3195
|
+
log("slow response, delaying callback", void 0, {
|
|
3196
|
+
"~LogMeta": "~LogMeta",
|
|
3197
|
+
F: __dxlog_file$1,
|
|
3198
|
+
L: 93,
|
|
3199
|
+
S: this
|
|
3200
|
+
});
|
|
3201
|
+
scheduleTask(this._ctx, () => callback(), RESP_MIN_THRESHOLD);
|
|
3202
|
+
} else callback();
|
|
3203
|
+
}, (err) => {
|
|
3204
|
+
callback();
|
|
3205
|
+
this._raiseIfOpen(err);
|
|
3206
|
+
});
|
|
3207
|
+
} });
|
|
3208
|
+
connectorStream.on("error", (err) => {
|
|
3209
|
+
this._raiseIfOpen(err);
|
|
3210
|
+
});
|
|
3211
|
+
this._options.stream.pipe(connectorStream);
|
|
3212
|
+
}, (error) => {
|
|
3213
|
+
if (error) this._raiseIfOpen(error);
|
|
3214
|
+
else this.close();
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
async _close() {
|
|
3218
|
+
try {
|
|
3219
|
+
await this._serviceStream?.close();
|
|
3220
|
+
this._serviceStream = void 0;
|
|
3221
|
+
} catch (err) {
|
|
3222
|
+
log.catch(err, void 0, {
|
|
3223
|
+
"~LogMeta": "~LogMeta",
|
|
3224
|
+
F: __dxlog_file$1,
|
|
3225
|
+
L: 128,
|
|
3226
|
+
S: this
|
|
3227
|
+
});
|
|
3228
|
+
}
|
|
3229
|
+
try {
|
|
3230
|
+
await this._options.bridgeService.close({ proxyId: this._proxyId }, { timeout: CLOSE_RPC_TIMEOUT });
|
|
3231
|
+
} catch (err) {
|
|
3232
|
+
log.catch(err, void 0, {
|
|
3233
|
+
"~LogMeta": "~LogMeta",
|
|
3234
|
+
F: __dxlog_file$1,
|
|
3235
|
+
L: 134,
|
|
3236
|
+
S: this
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
3239
|
+
this.closed.emit();
|
|
3240
|
+
}
|
|
3241
|
+
async onSignal(signal) {
|
|
3242
|
+
this._options.bridgeService.sendSignal({
|
|
3243
|
+
proxyId: this._proxyId,
|
|
3244
|
+
signal
|
|
3245
|
+
}, { timeout: RPC_TIMEOUT }).catch((err) => this._raiseIfOpen(decodeError(err)));
|
|
3246
|
+
}
|
|
3247
|
+
async _handleConnection(connectionEvent) {
|
|
3248
|
+
if (connectionEvent.error) {
|
|
3249
|
+
this.errors.raise(decodeError(connectionEvent.error));
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
switch (connectionEvent.state) {
|
|
3253
|
+
case ConnectionState$1.CONNECTED:
|
|
3254
|
+
this.connected.emit();
|
|
3255
|
+
break;
|
|
3256
|
+
case ConnectionState$1.CLOSED:
|
|
3257
|
+
await this.close();
|
|
3258
|
+
break;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
_handleData(dataEvent) {
|
|
3262
|
+
try {
|
|
3263
|
+
this._options.stream.write(arrayToBuffer(dataEvent.payload));
|
|
3264
|
+
} catch (error) {
|
|
3265
|
+
this._raiseIfOpen(error);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
async _handleSignal(signalEvent) {
|
|
3269
|
+
try {
|
|
3270
|
+
await this._options.sendSignal(signalEvent.payload);
|
|
3271
|
+
} catch (error) {
|
|
3272
|
+
const type = signalEvent.payload.payload.data?.type;
|
|
3273
|
+
if (type === "offer" || type === "answer") this._raiseIfOpen(new ConnectivityError({ message: `Session establishment failed: ${type} couldn't be sent.` }));
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
async getDetails() {
|
|
3277
|
+
try {
|
|
3278
|
+
return (await this._options.bridgeService.getDetails({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).details;
|
|
3279
|
+
} catch (err) {
|
|
3280
|
+
return "bridge-svc unreachable";
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
async getStats() {
|
|
3284
|
+
try {
|
|
3285
|
+
return (await this._options.bridgeService.getStats({ proxyId: this._proxyId }, { timeout: RPC_TIMEOUT })).stats;
|
|
3286
|
+
} catch (err) {
|
|
3287
|
+
return {
|
|
3288
|
+
bytesSent: 0,
|
|
3289
|
+
bytesReceived: 0,
|
|
3290
|
+
packetsSent: 0,
|
|
3291
|
+
packetsReceived: 0,
|
|
3292
|
+
rawStats: "bridge-svc unreachable"
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
_raiseIfOpen(error) {
|
|
3297
|
+
if (this.isOpen) this.errors.raise(error);
|
|
3298
|
+
else log.info("error swallowed because transport was closed", { message: error.message }, {
|
|
3299
|
+
"~LogMeta": "~LogMeta",
|
|
3300
|
+
F: __dxlog_file$1,
|
|
3301
|
+
L: 217,
|
|
3302
|
+
S: this
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Called when underlying proxy service becomes unavailable.
|
|
3307
|
+
*/
|
|
3308
|
+
forceClose() {
|
|
3309
|
+
this._serviceStream?.close();
|
|
3310
|
+
this.closed.emit();
|
|
3311
|
+
}
|
|
3312
|
+
};
|
|
3313
|
+
var RtcTransportProxyFactory = class {
|
|
3314
|
+
_bridgeService;
|
|
3315
|
+
_connections = /* @__PURE__ */ new Set();
|
|
3316
|
+
/**
|
|
3317
|
+
* Sets the current BridgeService to be used to open connections.
|
|
3318
|
+
* Calling this method will close any existing connections.
|
|
3319
|
+
*/
|
|
3320
|
+
setBridgeService(bridgeService) {
|
|
3321
|
+
this._bridgeService = bridgeService;
|
|
3322
|
+
for (const connection of this._connections) connection.forceClose();
|
|
3323
|
+
return this;
|
|
3324
|
+
}
|
|
3325
|
+
createTransport(options) {
|
|
3326
|
+
invariant(this._bridgeService, "RtcTransportProxyFactory is not ready to open connections", {
|
|
3327
|
+
"~LogMeta": "~LogMeta",
|
|
3328
|
+
F: __dxlog_file$1,
|
|
3329
|
+
L: 247,
|
|
3330
|
+
S: this,
|
|
3331
|
+
A: ["this._bridgeService", "'RtcTransportProxyFactory is not ready to open connections'"]
|
|
3332
|
+
});
|
|
3333
|
+
const transport = new RtcTransportProxy({
|
|
3334
|
+
...options,
|
|
3335
|
+
bridgeService: this._bridgeService
|
|
3336
|
+
});
|
|
3337
|
+
this._connections.add(transport);
|
|
3338
|
+
transport.closed.on(() => {
|
|
3339
|
+
this._connections.delete(transport);
|
|
3340
|
+
});
|
|
3341
|
+
return transport;
|
|
3342
|
+
}
|
|
3343
|
+
};
|
|
3344
|
+
var decodeError = (err) => {
|
|
3345
|
+
const message = typeof err === "string" ? err : err.message;
|
|
3346
|
+
if (message.includes("CONNECTION_RESET")) return new ConnectionResetError({ message });
|
|
3347
|
+
else if (message.includes("TIMEOUT")) return new TimeoutError({ message });
|
|
3348
|
+
else if (message.includes("CONNECTIVITY_ERROR")) return new ConnectivityError({ message });
|
|
3349
|
+
else return typeof err === "string" ? new Error(err) : err;
|
|
3350
|
+
};
|
|
3351
|
+
//#endregion
|
|
3352
|
+
//#region src/transport/webrtc/rtc-transport-service.ts
|
|
3353
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/core/mesh/network-manager/src/transport/webrtc/rtc-transport-service.ts";
|
|
3354
|
+
var RtcTransportService = class {
|
|
3355
|
+
_transportFactory;
|
|
3356
|
+
_openTransports = new ComplexMap(PublicKey.hash);
|
|
3357
|
+
constructor(webrtcConfig, iceProvider, _transportFactory = createRtcTransportFactory(webrtcConfig, iceProvider)) {
|
|
3358
|
+
this._transportFactory = _transportFactory;
|
|
3359
|
+
}
|
|
3360
|
+
hasOpenTransports() {
|
|
3361
|
+
return this._openTransports.size > 0;
|
|
3362
|
+
}
|
|
3363
|
+
open(request) {
|
|
3364
|
+
const existingTransport = this._openTransports.get(request.proxyId);
|
|
3365
|
+
if (existingTransport) {
|
|
3366
|
+
log.error("requesting a new transport bridge for an existing proxy", void 0, {
|
|
3367
|
+
"~LogMeta": "~LogMeta",
|
|
3368
|
+
F: __dxlog_file,
|
|
3369
|
+
L: 53,
|
|
3370
|
+
S: this
|
|
3371
|
+
});
|
|
3372
|
+
this._safeCloseTransport(existingTransport);
|
|
3373
|
+
this._openTransports.delete(request.proxyId);
|
|
3374
|
+
}
|
|
3375
|
+
return new Stream(({ ready, next, close }) => {
|
|
3376
|
+
const pushNewState = createStateUpdater(next);
|
|
3377
|
+
const transportStream = new Duplex({
|
|
3378
|
+
read: () => {
|
|
3379
|
+
const callbacks = [...transportState.writeProcessedCallbacks];
|
|
3380
|
+
transportState.writeProcessedCallbacks.length = 0;
|
|
3381
|
+
callbacks.forEach((cb) => cb());
|
|
3382
|
+
},
|
|
3383
|
+
write: function(chunk, _, callback) {
|
|
3384
|
+
next({ data: { payload: chunk } });
|
|
3385
|
+
callback();
|
|
3386
|
+
}
|
|
3387
|
+
});
|
|
3388
|
+
const transport = this._transportFactory.createTransport({
|
|
3389
|
+
initiator: request.initiator,
|
|
3390
|
+
topic: request.topic,
|
|
3391
|
+
ownPeerKey: request.ownPeerKey,
|
|
3392
|
+
remotePeerKey: request.remotePeerKey,
|
|
3393
|
+
stream: transportStream,
|
|
3394
|
+
sendSignal: async (signal) => {
|
|
3395
|
+
next({ signal: { payload: signal } });
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
const transportState = {
|
|
3399
|
+
proxyId: request.proxyId,
|
|
3400
|
+
transport,
|
|
3401
|
+
connectorStream: transportStream,
|
|
3402
|
+
writeProcessedCallbacks: []
|
|
3403
|
+
};
|
|
3404
|
+
transport.connected.on(() => pushNewState(ConnectionState$1.CONNECTED));
|
|
3405
|
+
transport.errors.handle(async (err) => {
|
|
3406
|
+
pushNewState(ConnectionState$1.CLOSED, err);
|
|
3407
|
+
this._safeCloseTransport(transportState);
|
|
3408
|
+
close(err);
|
|
3409
|
+
});
|
|
3410
|
+
transport.closed.on(async () => {
|
|
3411
|
+
pushNewState(ConnectionState$1.CLOSED);
|
|
3412
|
+
this._safeCloseTransport(transportState);
|
|
3413
|
+
close();
|
|
3414
|
+
});
|
|
3415
|
+
this._openTransports.set(request.proxyId, transportState);
|
|
3416
|
+
transport.open().catch(async (err) => {
|
|
3417
|
+
pushNewState(ConnectionState$1.CLOSED, err);
|
|
3418
|
+
this._safeCloseTransport(transportState);
|
|
3419
|
+
close(err);
|
|
3420
|
+
});
|
|
3421
|
+
ready();
|
|
3422
|
+
log("stream ready", void 0, {
|
|
3423
|
+
"~LogMeta": "~LogMeta",
|
|
3424
|
+
F: __dxlog_file,
|
|
3425
|
+
L: 115,
|
|
3426
|
+
S: this
|
|
3427
|
+
});
|
|
3428
|
+
pushNewState(ConnectionState$1.CONNECTING);
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
async sendSignal({ proxyId, signal }) {
|
|
3432
|
+
const transport = this._openTransports.get(proxyId);
|
|
3433
|
+
invariant(transport, void 0, {
|
|
3434
|
+
"~LogMeta": "~LogMeta",
|
|
3435
|
+
F: __dxlog_file,
|
|
3436
|
+
L: 123,
|
|
3437
|
+
S: this,
|
|
3438
|
+
A: ["transport", ""]
|
|
3439
|
+
});
|
|
3440
|
+
await transport.transport.onSignal(signal);
|
|
3441
|
+
}
|
|
3442
|
+
async getDetails({ proxyId }) {
|
|
3443
|
+
const transport = this._openTransports.get(proxyId);
|
|
3444
|
+
invariant(transport, void 0, {
|
|
3445
|
+
"~LogMeta": "~LogMeta",
|
|
3446
|
+
F: __dxlog_file,
|
|
3447
|
+
L: 130,
|
|
3448
|
+
S: this,
|
|
3449
|
+
A: ["transport", ""]
|
|
3450
|
+
});
|
|
3451
|
+
return { details: await transport.transport.getDetails() };
|
|
3452
|
+
}
|
|
3453
|
+
async getStats({ proxyId }) {
|
|
3454
|
+
const transport = this._openTransports.get(proxyId);
|
|
3455
|
+
invariant(transport, void 0, {
|
|
3456
|
+
"~LogMeta": "~LogMeta",
|
|
3457
|
+
F: __dxlog_file,
|
|
3458
|
+
L: 137,
|
|
3459
|
+
S: this,
|
|
3460
|
+
A: ["transport", ""]
|
|
3461
|
+
});
|
|
3462
|
+
return { stats: await transport.transport.getStats() };
|
|
3463
|
+
}
|
|
3464
|
+
async sendData({ proxyId, payload }) {
|
|
3465
|
+
const transport = this._openTransports.get(proxyId);
|
|
3466
|
+
invariant(transport, void 0, {
|
|
3467
|
+
"~LogMeta": "~LogMeta",
|
|
3468
|
+
F: __dxlog_file,
|
|
3469
|
+
L: 144,
|
|
3470
|
+
S: this,
|
|
3471
|
+
A: ["transport", ""]
|
|
3472
|
+
});
|
|
3473
|
+
if (!transport.connectorStream.push(payload)) await new Promise((resolve) => {
|
|
3474
|
+
transport.writeProcessedCallbacks.push(resolve);
|
|
3475
|
+
});
|
|
3476
|
+
}
|
|
3477
|
+
async close({ proxyId }) {
|
|
3478
|
+
const transport = this._openTransports.get(proxyId);
|
|
3479
|
+
if (!transport) return;
|
|
3480
|
+
this._openTransports.delete(proxyId);
|
|
3481
|
+
await this._safeCloseTransport(transport);
|
|
3482
|
+
}
|
|
3483
|
+
async _safeCloseTransport(transport) {
|
|
3484
|
+
if (this._openTransports.get(transport.proxyId) === transport) this._openTransports.delete(transport.proxyId);
|
|
3485
|
+
transport.writeProcessedCallbacks.forEach((cb) => cb());
|
|
3486
|
+
try {
|
|
3487
|
+
await transport.transport.close();
|
|
3488
|
+
} catch (error) {
|
|
3489
|
+
log.warn("transport close error", { message: error?.message }, {
|
|
3490
|
+
"~LogMeta": "~LogMeta",
|
|
3491
|
+
F: __dxlog_file,
|
|
3492
|
+
L: 174,
|
|
3493
|
+
S: this
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
try {
|
|
3497
|
+
transport.connectorStream.end();
|
|
3498
|
+
} catch (error) {
|
|
3499
|
+
log.warn("connectorStream close error", { message: error?.message }, {
|
|
3500
|
+
"~LogMeta": "~LogMeta",
|
|
3501
|
+
F: __dxlog_file,
|
|
3502
|
+
L: 179,
|
|
3503
|
+
S: this
|
|
3504
|
+
});
|
|
3505
|
+
}
|
|
3506
|
+
log("closed", void 0, {
|
|
3507
|
+
"~LogMeta": "~LogMeta",
|
|
3508
|
+
F: __dxlog_file,
|
|
3509
|
+
L: 181,
|
|
3510
|
+
S: this
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
var createStateUpdater = (next) => {
|
|
3515
|
+
return (state, err) => {
|
|
3516
|
+
next({ connection: {
|
|
3517
|
+
state,
|
|
3518
|
+
...err ? { error: err.message } : void 0
|
|
3519
|
+
} });
|
|
3520
|
+
};
|
|
3521
|
+
};
|
|
3522
|
+
//#endregion
|
|
3523
|
+
//#region src/wire-protocol.ts
|
|
3524
|
+
/**
|
|
3525
|
+
* Create a wire-protocol provider backed by a teleport instance.
|
|
3526
|
+
* @param onConnection Called after teleport is initialized for the session. Protocol extensions could be attached here.
|
|
3527
|
+
* @param defaultProps Optionally provide default Teleport params that might be overridden by factory callers.
|
|
3528
|
+
* @returns
|
|
3529
|
+
*/
|
|
3530
|
+
var createTeleportProtocolFactory = (onConnection, defaultProps) => {
|
|
3531
|
+
return (params) => {
|
|
3532
|
+
const teleport = new Teleport({
|
|
3533
|
+
...defaultProps,
|
|
3534
|
+
...params
|
|
3535
|
+
});
|
|
3536
|
+
return {
|
|
3537
|
+
stream: teleport.stream,
|
|
3538
|
+
open: async (sessionId) => {
|
|
3539
|
+
await teleport.open(sessionId);
|
|
3540
|
+
await onConnection(teleport);
|
|
3541
|
+
},
|
|
3542
|
+
close: async () => {
|
|
3543
|
+
await teleport.close();
|
|
3544
|
+
},
|
|
3545
|
+
abort: async () => {
|
|
3546
|
+
await teleport.abort();
|
|
3547
|
+
}
|
|
3548
|
+
};
|
|
3549
|
+
};
|
|
3550
|
+
};
|
|
3551
|
+
//#endregion
|
|
3552
|
+
export { Swarm as _, createRtcTransportFactory as a, ConnectionState$2 as b, MemoryTransportFactory as c, SwarmNetworkManagerService as d, ConnectionLog as f, SwarmMapper as g, MAX_CONCURRENT_INITIATING_CONNECTIONS as h, RtcTransportProxyFactory as i, FullyConnectedTopology as l, ConnectionLimiter as m, RtcTransportService as n, TransportKind as o, EventType as p, RtcTransportProxy as r, MemoryTransport as s, createTeleportProtocolFactory as t, SwarmNetworkManager as u, SwarmMessenger as v, Connection as y };
|
|
3553
|
+
|
|
3554
|
+
//# sourceMappingURL=chunk-wire-protocol.mjs.map
|