@dxos/teleport-extension-gossip 0.3.7-main.fcec8cd → 0.3.7
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/browser/index.mjs +65 -12
- package/dist/lib/browser/index.mjs.map +3 -3
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +65 -12
- package/dist/lib/node/index.cjs.map +3 -3
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/gossip.d.ts +5 -0
- package/dist/types/src/gossip.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/gossip.ts +65 -23
|
@@ -12,7 +12,7 @@ import { invariant } from "@dxos/invariant";
|
|
|
12
12
|
import { log } from "@dxos/log";
|
|
13
13
|
import { schema } from "@dxos/protocols";
|
|
14
14
|
import { createProtoRpcPeer } from "@dxos/rpc";
|
|
15
|
-
var __dxlog_file = "/home/
|
|
15
|
+
var __dxlog_file = "/home/circleci/project/packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts";
|
|
16
16
|
var GossipExtension = class {
|
|
17
17
|
constructor(_callbacks = {}) {
|
|
18
18
|
this._callbacks = _callbacks;
|
|
@@ -115,8 +115,12 @@ var GossipExtension = class {
|
|
|
115
115
|
};
|
|
116
116
|
|
|
117
117
|
// packages/core/mesh/teleport-extension-gossip/src/gossip.ts
|
|
118
|
-
var __dxlog_file2 = "/home/
|
|
118
|
+
var __dxlog_file2 = "/home/circleci/project/packages/core/mesh/teleport-extension-gossip/src/gossip.ts";
|
|
119
119
|
var RECEIVED_MESSAGES_GC_INTERVAL = 12e4;
|
|
120
|
+
var YJS_CHANNEL_PREFIX = "user-channel/yjs.awareness";
|
|
121
|
+
var YJS_TIMEOUT_THRESHOLD = 20;
|
|
122
|
+
var YJS_TIMEOUT_WINDOW = 1e3 * 30;
|
|
123
|
+
var MAX_CTX_TASKS = 50;
|
|
120
124
|
var Gossip = class {
|
|
121
125
|
constructor(_params) {
|
|
122
126
|
this._params = _params;
|
|
@@ -124,7 +128,7 @@ var Gossip = class {
|
|
|
124
128
|
onError: (err) => {
|
|
125
129
|
log2.catch(err, void 0, {
|
|
126
130
|
F: __dxlog_file2,
|
|
127
|
-
L:
|
|
131
|
+
L: 35,
|
|
128
132
|
S: this,
|
|
129
133
|
C: (f, a) => f(...a)
|
|
130
134
|
});
|
|
@@ -135,6 +139,8 @@ var Gossip = class {
|
|
|
135
139
|
this._toClear = new ComplexSet(PublicKey.hash);
|
|
136
140
|
this._connections = new ComplexMap(PublicKey.hash);
|
|
137
141
|
this.connectionClosed = new Event();
|
|
142
|
+
this._yjs_timeout = new Array(YJS_TIMEOUT_THRESHOLD);
|
|
143
|
+
this._yjs_timeout_index = 0;
|
|
138
144
|
}
|
|
139
145
|
get localPeerId() {
|
|
140
146
|
return this._params.localPeerId;
|
|
@@ -158,6 +164,24 @@ var Gossip = class {
|
|
|
158
164
|
}
|
|
159
165
|
this._receivedMessages.add(message.messageId);
|
|
160
166
|
this._callListeners(message);
|
|
167
|
+
if (message.channelId.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {
|
|
168
|
+
log2("skipping propagating YJS gossip message due to timeouts", void 0, {
|
|
169
|
+
F: __dxlog_file2,
|
|
170
|
+
L: 91,
|
|
171
|
+
S: this,
|
|
172
|
+
C: (f, a) => f(...a)
|
|
173
|
+
});
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (this._ctx.disposeCallbacksLength > MAX_CTX_TASKS) {
|
|
177
|
+
log2("skipping propagating YJS gossip message due to exessive tasks", void 0, {
|
|
178
|
+
F: __dxlog_file2,
|
|
179
|
+
L: 95,
|
|
180
|
+
S: this,
|
|
181
|
+
C: (f, a) => f(...a)
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
161
185
|
scheduleTask(this._ctx, async () => {
|
|
162
186
|
await this._propagateAnnounce(message);
|
|
163
187
|
});
|
|
@@ -166,7 +190,7 @@ var Gossip = class {
|
|
|
166
190
|
if (err) {
|
|
167
191
|
log2.catch(err, void 0, {
|
|
168
192
|
F: __dxlog_file2,
|
|
169
|
-
L:
|
|
193
|
+
L: 104,
|
|
170
194
|
S: this,
|
|
171
195
|
C: (f, a) => f(...a)
|
|
172
196
|
});
|
|
@@ -181,8 +205,17 @@ var Gossip = class {
|
|
|
181
205
|
return extension;
|
|
182
206
|
}
|
|
183
207
|
postMessage(channel, payload) {
|
|
208
|
+
if (channel.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {
|
|
209
|
+
log2("skipping YJS gossip message due to timeouts", void 0, {
|
|
210
|
+
F: __dxlog_file2,
|
|
211
|
+
L: 119,
|
|
212
|
+
S: this,
|
|
213
|
+
C: (f, a) => f(...a)
|
|
214
|
+
});
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
184
217
|
for (const extension of this._connections.values()) {
|
|
185
|
-
|
|
218
|
+
this._sendAnnounceWithTimeoutTracking(extension, {
|
|
186
219
|
peerId: this._params.localPeerId,
|
|
187
220
|
messageId: PublicKey.random(),
|
|
188
221
|
channelId: channel,
|
|
@@ -194,7 +227,7 @@ var Gossip = class {
|
|
|
194
227
|
err
|
|
195
228
|
}, {
|
|
196
229
|
F: __dxlog_file2,
|
|
197
|
-
L:
|
|
230
|
+
L: 131,
|
|
198
231
|
S: this,
|
|
199
232
|
C: (f, a) => f(...a)
|
|
200
233
|
});
|
|
@@ -203,14 +236,14 @@ var Gossip = class {
|
|
|
203
236
|
err
|
|
204
237
|
}, {
|
|
205
238
|
F: __dxlog_file2,
|
|
206
|
-
L:
|
|
239
|
+
L: 137,
|
|
207
240
|
S: this,
|
|
208
241
|
C: (f, a) => f(...a)
|
|
209
242
|
});
|
|
210
243
|
} else {
|
|
211
244
|
log2.catch(err, void 0, {
|
|
212
245
|
F: __dxlog_file2,
|
|
213
|
-
L:
|
|
246
|
+
L: 139,
|
|
214
247
|
S: this,
|
|
215
248
|
C: (f, a) => f(...a)
|
|
216
249
|
});
|
|
@@ -243,9 +276,9 @@ var Gossip = class {
|
|
|
243
276
|
if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {
|
|
244
277
|
return;
|
|
245
278
|
}
|
|
246
|
-
return
|
|
279
|
+
return this._sendAnnounceWithTimeoutTracking(extension, message).catch((err) => log2(err, void 0, {
|
|
247
280
|
F: __dxlog_file2,
|
|
248
|
-
L:
|
|
281
|
+
L: 172,
|
|
249
282
|
S: this,
|
|
250
283
|
C: (f, a) => f(...a)
|
|
251
284
|
}));
|
|
@@ -266,12 +299,32 @@ var Gossip = class {
|
|
|
266
299
|
elapsed
|
|
267
300
|
}, {
|
|
268
301
|
F: __dxlog_file2,
|
|
269
|
-
L:
|
|
302
|
+
L: 190,
|
|
270
303
|
S: this,
|
|
271
304
|
C: (f, a) => f(...a)
|
|
272
305
|
});
|
|
273
306
|
}
|
|
274
307
|
}
|
|
308
|
+
_addYjsTimeout() {
|
|
309
|
+
this._yjs_timeout[this._yjs_timeout_index] = Date.now();
|
|
310
|
+
this._yjs_timeout_index = (this._yjs_timeout_index + 1) % YJS_TIMEOUT_THRESHOLD;
|
|
311
|
+
}
|
|
312
|
+
_oldestYjsTimeoutInWindow() {
|
|
313
|
+
const lastTS = this._yjs_timeout[(this._yjs_timeout_index + YJS_TIMEOUT_THRESHOLD - 1) % YJS_TIMEOUT_THRESHOLD];
|
|
314
|
+
if (!lastTS) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
return Date.now() - lastTS < YJS_TIMEOUT_WINDOW;
|
|
318
|
+
}
|
|
319
|
+
_sendAnnounceWithTimeoutTracking(extension, message) {
|
|
320
|
+
return extension.sendAnnounce(message).catch((err) => {
|
|
321
|
+
if (err instanceof TimeoutError || err.constructor.name === "TimeoutError" || err.message.startsWith("Timeout")) {
|
|
322
|
+
if (message.channelId.startsWith(YJS_CHANNEL_PREFIX)) {
|
|
323
|
+
this._addYjsTimeout();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
275
328
|
};
|
|
276
329
|
|
|
277
330
|
// packages/core/mesh/teleport-extension-gossip/src/presence.ts
|
|
@@ -281,7 +334,7 @@ import { invariant as invariant2 } from "@dxos/invariant";
|
|
|
281
334
|
import { PublicKey as PublicKey2 } from "@dxos/keys";
|
|
282
335
|
import { log as log3 } from "@dxos/log";
|
|
283
336
|
import { ComplexMap as ComplexMap2 } from "@dxos/util";
|
|
284
|
-
var __dxlog_file3 = "/home/
|
|
337
|
+
var __dxlog_file3 = "/home/circleci/project/packages/core/mesh/teleport-extension-gossip/src/presence.ts";
|
|
285
338
|
var PRESENCE_CHANNEL_ID = "dxos.mesh.presence.Presence";
|
|
286
339
|
var Presence = class {
|
|
287
340
|
// remotePeerId -> PresenceExtension
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/gossip.ts", "../../../src/gossip-extension.ts", "../../../src/presence.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { scheduleTask, Event, scheduleTaskInterval } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { RpcClosedError, TimeoutError } from '@dxos/protocols';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap, ComplexSet } from '@dxos/util';\n\nimport { GossipExtension } from './gossip-extension';\n\nexport type GossipParams = {\n localPeerId: PublicKey;\n};\n\nconst RECEIVED_MESSAGES_GC_INTERVAL = 120_000;\n\n/**\n * Gossip extensions manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received announces to all connected peers.\n * Exposes API send announce to everybody and subscribe to .\n */\nexport class Gossip {\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _listeners = new Map<string, Set<(message: GossipMessage) => void>>();\n\n private readonly _receivedMessages = new ComplexSet<PublicKey>(PublicKey.hash);\n\n /**\n * Keys scheduled to be cleared from _receivedMessages on the next iteration.\n */\n private readonly _toClear = new ComplexSet<PublicKey>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n private readonly _connections = new ComplexMap<PublicKey, GossipExtension>(PublicKey.hash);\n\n public readonly connectionClosed = new Event<PublicKey>();\n\n constructor(private readonly _params: GossipParams) {}\n\n get localPeerId() {\n return this._params.localPeerId;\n }\n\n async open() {\n // Clear the map periodically.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this._performGc();\n },\n RECEIVED_MESSAGES_GC_INTERVAL,\n );\n }\n\n async close() {\n await this._ctx.dispose();\n }\n\n getConnections() {\n return Array.from(this._connections.keys());\n }\n\n createExtension({ remotePeerId }: { remotePeerId: PublicKey }): GossipExtension {\n const extension = new GossipExtension({\n onAnnounce: async (message) => {\n if (this._receivedMessages.has(message.messageId)) {\n return;\n }\n this._receivedMessages.add(message.messageId);\n this._callListeners(message);\n scheduleTask(this._ctx, async () => {\n await this._propagateAnnounce(message);\n });\n },\n onClose: async (err) => {\n if (err) {\n log.catch(err);\n }\n if (this._connections.has(remotePeerId)) {\n this._connections.delete(remotePeerId);\n }\n this.connectionClosed.emit(remotePeerId);\n },\n });\n this._connections.set(remotePeerId, extension);\n\n return extension;\n }\n\n postMessage(channel: string, payload: any) {\n for (const extension of this._connections.values()) {\n void extension\n .sendAnnounce({\n peerId: this._params.localPeerId,\n messageId: PublicKey.random(),\n channelId: channel,\n timestamp: new Date(),\n payload,\n })\n\n .catch(async (err) => {\n if (err instanceof RpcClosedError) {\n log('sendAnnounce failed because of RpcClosedError', { err });\n } else if (\n err instanceof TimeoutError ||\n err.constructor.name === 'TimeoutError' ||\n err.message.startsWith('Timeout')\n ) {\n log('sendAnnounce failed because of TimeoutError', { err });\n } else {\n log.catch(err);\n }\n });\n }\n }\n\n listen(channel: string, callback: (message: GossipMessage) => void) {\n if (!this._listeners.has(channel)) {\n this._listeners.set(channel, new Set());\n }\n this._listeners.get(channel)!.add(callback);\n\n return {\n unsubscribe: () => {\n this._listeners.get(channel)!.delete(callback);\n },\n };\n }\n\n private _callListeners(message: GossipMessage) {\n if (this._listeners.has(message.channelId)) {\n this._listeners.get(message.channelId)!.forEach((callback) => {\n callback(message);\n });\n }\n }\n\n private _propagateAnnounce(message: GossipMessage) {\n return Promise.all(\n [...this._connections.entries()].map(async ([remotePeerId, extension]) => {\n if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {\n return;\n }\n return extension.sendAnnounce(message).catch((err) => log(err));\n }),\n );\n }\n\n private _performGc() {\n const start = performance.now();\n\n for (const key of this._toClear.keys()) {\n this._receivedMessages.delete(key);\n }\n this._toClear.clear();\n for (const key of this._receivedMessages.keys()) {\n this._toClear.add(key);\n }\n\n const elapsed = performance.now() - start;\n if (elapsed > 100) {\n log.warn('GC took too long', { elapsed });\n }\n }\n}\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Trigger } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { schema } from '@dxos/protocols';\nimport { type GossipMessage, type GossipService } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { createProtoRpcPeer, type ProtoRpcPeer } from '@dxos/rpc';\nimport { type ExtensionContext, type TeleportExtension } from '@dxos/teleport';\n\nexport type GossipCallbacks = {\n /**\n * Callback to be called when a new announce is received.\n */\n onAnnounce?: (message: GossipMessage) => Promise<void>;\n\n /**\n * Callback to be called when the extension is closed.\n */\n onClose?: (err?: Error) => Promise<void>;\n};\n\n/**\n * Sends announces between two peers for a single teleport session.\n */\nexport class GossipExtension implements TeleportExtension {\n private readonly _opened = new Trigger();\n private _closed = false;\n\n private _rpc?: ProtoRpcPeer<ServiceBundle>;\n\n constructor(private readonly _callbacks: GossipCallbacks = {}) {}\n\n async onOpen(context: ExtensionContext): Promise<void> {\n log('onOpen', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId });\n\n this._rpc = createProtoRpcPeer<ServiceBundle, ServiceBundle>({\n requested: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n exposed: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n handlers: {\n GossipService: {\n announce: async (message: GossipMessage) => {\n log('received announce', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId, message });\n await this._callbacks.onAnnounce?.(message);\n },\n },\n },\n port: await context.createPort('rpc', { contentType: 'application/x-protobuf; messageType=\"dxos.rpc.Message\"' }),\n });\n await this._rpc.open();\n this._opened.wake();\n }\n\n async onClose(err?: Error): Promise<void> {\n log('close', { err });\n await this._rpc?.close();\n await this._callbacks.onClose?.(err);\n this._closed = true;\n }\n\n async onAbort(err?: Error): Promise<void> {\n log('abort', { err });\n try {\n await this._rpc?.abort();\n } catch (err) {\n log.catch(err);\n } finally {\n await this._callbacks.onClose?.(err);\n }\n this._closed = true;\n }\n\n async sendAnnounce(message: GossipMessage) {\n if (this._closed) {\n return;\n }\n await this._opened.wait();\n invariant(this._rpc, 'RPC not initialized');\n await this._rpc.rpc.GossipService.announce(message);\n }\n}\n\ntype ServiceBundle = {\n GossipService: GossipService;\n};\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Event, scheduleTaskInterval } from '@dxos/async';\nimport { type WithTypeUrl } from '@dxos/codec-protobuf';\nimport { Context } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type Gossip } from './gossip';\n\nexport type PresenceParams = {\n /**\n * Interval between presence announces.\n */\n announceInterval: number;\n /**\n * Timeout after which a peer is considered offline.\n * Should be greater than announceInterval.\n */\n offlineTimeout: number;\n\n /**\n * Identity key of the local peer.\n */\n identityKey: PublicKey; // TODO(mykola): Remove once IdentityKey can be obtained from DeviceKey.\n\n gossip: Gossip;\n};\n\nconst PRESENCE_CHANNEL_ID = 'dxos.mesh.presence.Presence';\n\n/**\n * Presence manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received presence announces to all connected peers.\n * Exposes API to get the list of peers that are online.\n */\nexport class Presence {\n public readonly updated = new Event<void>();\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _peerStates = new ComplexMap<PublicKey, GossipMessage>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n\n constructor(private readonly _params: PresenceParams) {\n invariant(\n this._params.announceInterval < this._params.offlineTimeout,\n 'Announce interval should be less than offline timeout.',\n );\n\n this._params.gossip.listen(PRESENCE_CHANNEL_ID, (message) => {\n this._receiveAnnounces(message);\n });\n\n // Send announce to all connected peers.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n const peerState: WithTypeUrl<PeerState> = {\n '@type': 'dxos.mesh.presence.PeerState',\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n };\n await this._params.gossip.postMessage(PRESENCE_CHANNEL_ID, peerState);\n },\n _params.announceInterval,\n );\n\n // Emit updated event in case some peers went offline.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this.updated.emit();\n },\n _params.offlineTimeout,\n );\n\n // Remove peer state when connection is closed.\n this._params.gossip.connectionClosed.on((peerId) => {\n this._peerStates.delete(peerId);\n this.updated.emit();\n });\n }\n\n getPeers(): PeerState[] {\n return Array.from(this._peerStates.values()).map((message) => message.payload);\n }\n\n getPeersOnline(): PeerState[] {\n return Array.from(this._peerStates.values())\n .filter((message) => message.timestamp.getTime() > Date.now() - this._params.offlineTimeout)\n .map((message) => message.payload);\n }\n\n getLocalState(): PeerState {\n return {\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n peerId: this._params.gossip.localPeerId,\n };\n }\n\n async destroy() {\n await this._ctx.dispose();\n }\n\n private _receiveAnnounces(message: GossipMessage) {\n invariant(message.channelId === PRESENCE_CHANNEL_ID, `Invalid channel ID: ${message.channelId}`);\n const oldPeerState = this._peerStates.get(message.peerId);\n if (!oldPeerState || oldPeerState.timestamp.getTime() < message.timestamp.getTime()) {\n // Assign peer id to payload.\n (message.payload as PeerState).peerId = message.peerId;\n\n this._peerStates.set(message.peerId, message);\n this.updated.emit();\n }\n }\n}\n"],
|
|
5
|
-
"mappings": ";AAIA,SAASA,cAAcC,OAAOC,4BAA4B;AAC1D,SAASC,eAAe;AACxB,SAASC,iBAAiB;AAC1B,SAASC,OAAAA,YAAW;AACpB,SAASC,gBAAgBC,oBAAoB;AAE7C,SAASC,YAAYC,kBAAkB;;;ACNvC,SAASC,eAAe;AACxB,SAASC,iBAAiB;AAC1B,SAASC,WAAW;AACpB,SAASC,cAAc;AAEvB,SAASC,0BAA6C;;AAkB/C,IAAMC,kBAAN,MAAMA;EAMXC,YAA6BC,aAA8B,CAAC,GAAG;sBAAlCA;SALZC,UAAU,IAAIR,QAAAA;SACvBS,UAAU;EAI8C;EAEhE,MAAMC,OAAOC,SAA0C;AACrDT,QAAI,UAAU;MAAEU,aAAaD,QAAQC;MAAaC,cAAcF,QAAQE;IAAa,GAAA;;;;;;AAErF,SAAKC,OAAOV,mBAAiD;MAC3DW,WAAW;QACTC,eAAeb,OAAOc,WAAW,yCAAA;MACnC;MACAC,SAAS;QACPF,eAAeb,OAAOc,WAAW,yCAAA;MACnC;MACAE,UAAU;QACRH,eAAe;UACbI,UAAU,OAAOC,YAAAA;AACfnB,gBAAI,qBAAqB;cAAEU,aAAaD,QAAQC;cAAaC,cAAcF,QAAQE;cAAcQ;YAAQ,GAAA;;;;;;AACzG,kBAAM,KAAKd,WAAWe,aAAaD,OAAAA;UACrC;QACF;MACF;MACAE,MAAM,MAAMZ,QAAQa,WAAW,OAAO;QAAEC,aAAa;MAAyD,CAAA;IAChH,CAAA;AACA,UAAM,KAAKX,KAAKY,KAAI;AACpB,SAAKlB,QAAQmB,KAAI;EACnB;EAEA,MAAMC,QAAQC,KAA4B;AACxC3B,QAAI,SAAS;MAAE2B;IAAI,GAAA;;;;;;AACnB,UAAM,KAAKf,MAAMgB,MAAAA;AACjB,UAAM,KAAKvB,WAAWqB,UAAUC,GAAAA;AAChC,SAAKpB,UAAU;EACjB;EAEA,MAAMsB,QAAQF,KAA4B;AACxC3B,QAAI,SAAS;MAAE2B;IAAI,GAAA;;;;;;AACnB,QAAI;AACF,YAAM,KAAKf,MAAMkB,MAAAA;IACnB,SAASH,MAAK;AACZ3B,UAAI+B,MAAMJ,MAAAA,QAAAA;;;;;;IACZ,UAAA;AACE,YAAM,KAAKtB,WAAWqB,UAAUC,GAAAA;IAClC;AACA,SAAKpB,UAAU;EACjB;EAEA,MAAMyB,aAAab,SAAwB;AACzC,QAAI,KAAKZ,SAAS;AAChB;IACF;AACA,UAAM,KAAKD,QAAQ2B,KAAI;AACvBlC,cAAU,KAAKa,MAAM,uBAAA;;;;;;;;;AACrB,UAAM,KAAKA,KAAKsB,IAAIpB,cAAcI,SAASC,OAAAA;EAC7C;AACF;;;;ADpEA,IAAMgB,gCAAgC;
|
|
6
|
-
"names": ["scheduleTask", "Event", "scheduleTaskInterval", "Context", "PublicKey", "log", "RpcClosedError", "TimeoutError", "ComplexMap", "ComplexSet", "Trigger", "invariant", "log", "schema", "createProtoRpcPeer", "GossipExtension", "constructor", "_callbacks", "_opened", "_closed", "onOpen", "context", "localPeerId", "remotePeerId", "_rpc", "requested", "GossipService", "getService", "exposed", "handlers", "announce", "message", "onAnnounce", "port", "createPort", "contentType", "open", "wake", "onClose", "err", "close", "onAbort", "abort", "catch", "sendAnnounce", "wait", "rpc", "RECEIVED_MESSAGES_GC_INTERVAL", "Gossip", "constructor", "_params", "_ctx", "Context", "onError", "err", "log", "catch", "_listeners", "Map", "_receivedMessages", "ComplexSet", "PublicKey", "hash", "_toClear", "_connections", "ComplexMap", "connectionClosed", "Event", "localPeerId", "open", "scheduleTaskInterval", "_performGc", "close", "dispose", "getConnections", "
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2023 DXOS.org\n//\n\nimport { scheduleTask, Event, scheduleTaskInterval } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { RpcClosedError, TimeoutError } from '@dxos/protocols';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap, ComplexSet } from '@dxos/util';\n\nimport { GossipExtension } from './gossip-extension';\n\nexport type GossipParams = {\n localPeerId: PublicKey;\n};\n\nconst RECEIVED_MESSAGES_GC_INTERVAL = 120_000;\n\nconst YJS_CHANNEL_PREFIX = 'user-channel/yjs.awareness';\nconst YJS_TIMEOUT_THRESHOLD = 20;\nconst YJS_TIMEOUT_WINDOW = 1000 * 30;\nconst MAX_CTX_TASKS = 50;\n\n/**\n * Gossip extensions manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received announces to all connected peers.\n * Exposes API send announce to everybody and subscribe to .\n */\nexport class Gossip {\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _listeners = new Map<string, Set<(message: GossipMessage) => void>>();\n\n private readonly _receivedMessages = new ComplexSet<PublicKey>(PublicKey.hash);\n\n /**\n * Keys scheduled to be cleared from _receivedMessages on the next iteration.\n */\n private readonly _toClear = new ComplexSet<PublicKey>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n private readonly _connections = new ComplexMap<PublicKey, GossipExtension>(PublicKey.hash);\n\n public readonly connectionClosed = new Event<PublicKey>();\n\n // ringbuffer for yjs timeouts\n private _yjs_timeout = new Array(YJS_TIMEOUT_THRESHOLD);\n private _yjs_timeout_index = 0;\n\n constructor(private readonly _params: GossipParams) {}\n\n get localPeerId() {\n return this._params.localPeerId;\n }\n\n async open() {\n // Clear the map periodically.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this._performGc();\n },\n RECEIVED_MESSAGES_GC_INTERVAL,\n );\n }\n\n async close() {\n await this._ctx.dispose();\n }\n\n getConnections() {\n return Array.from(this._connections.keys());\n }\n\n createExtension({ remotePeerId }: { remotePeerId: PublicKey }): GossipExtension {\n const extension = new GossipExtension({\n onAnnounce: async (message) => {\n if (this._receivedMessages.has(message.messageId)) {\n return;\n }\n this._receivedMessages.add(message.messageId);\n this._callListeners(message);\n if (message.channelId.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {\n log('skipping propagating YJS gossip message due to timeouts');\n return;\n }\n if (this._ctx.disposeCallbacksLength > MAX_CTX_TASKS) {\n log('skipping propagating YJS gossip message due to exessive tasks');\n return;\n }\n scheduleTask(this._ctx, async () => {\n await this._propagateAnnounce(message);\n });\n },\n onClose: async (err) => {\n if (err) {\n log.catch(err);\n }\n if (this._connections.has(remotePeerId)) {\n this._connections.delete(remotePeerId);\n }\n this.connectionClosed.emit(remotePeerId);\n },\n });\n this._connections.set(remotePeerId, extension);\n\n return extension;\n }\n\n postMessage(channel: string, payload: any) {\n if (channel.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {\n log('skipping YJS gossip message due to timeouts');\n return;\n }\n for (const extension of this._connections.values()) {\n this._sendAnnounceWithTimeoutTracking(extension, {\n peerId: this._params.localPeerId,\n messageId: PublicKey.random(),\n channelId: channel,\n timestamp: new Date(),\n payload,\n }).catch(async (err) => {\n if (err instanceof RpcClosedError) {\n log('sendAnnounce failed because of RpcClosedError', { err });\n } else if (\n err instanceof TimeoutError ||\n err.constructor.name === 'TimeoutError' ||\n err.message.startsWith('Timeout')\n ) {\n log('sendAnnounce failed because of TimeoutError', { err });\n } else {\n log.catch(err);\n }\n });\n }\n }\n\n listen(channel: string, callback: (message: GossipMessage) => void) {\n if (!this._listeners.has(channel)) {\n this._listeners.set(channel, new Set());\n }\n this._listeners.get(channel)!.add(callback);\n\n return {\n unsubscribe: () => {\n this._listeners.get(channel)!.delete(callback);\n },\n };\n }\n\n private _callListeners(message: GossipMessage) {\n if (this._listeners.has(message.channelId)) {\n this._listeners.get(message.channelId)!.forEach((callback) => {\n callback(message);\n });\n }\n }\n\n private _propagateAnnounce(message: GossipMessage) {\n return Promise.all(\n [...this._connections.entries()].map(async ([remotePeerId, extension]) => {\n if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {\n return;\n }\n return this._sendAnnounceWithTimeoutTracking(extension, message).catch((err) => log(err));\n }),\n );\n }\n\n private _performGc() {\n const start = performance.now();\n\n for (const key of this._toClear.keys()) {\n this._receivedMessages.delete(key);\n }\n this._toClear.clear();\n for (const key of this._receivedMessages.keys()) {\n this._toClear.add(key);\n }\n\n const elapsed = performance.now() - start;\n if (elapsed > 100) {\n log.warn('GC took too long', { elapsed });\n }\n }\n\n private _addYjsTimeout() {\n this._yjs_timeout[this._yjs_timeout_index] = Date.now();\n this._yjs_timeout_index = (this._yjs_timeout_index + 1) % YJS_TIMEOUT_THRESHOLD;\n }\n\n private _oldestYjsTimeoutInWindow(): boolean {\n const lastTS = this._yjs_timeout[(this._yjs_timeout_index + YJS_TIMEOUT_THRESHOLD - 1) % YJS_TIMEOUT_THRESHOLD];\n if (!lastTS) {\n return false;\n }\n\n return Date.now() - lastTS < YJS_TIMEOUT_WINDOW;\n }\n\n private _sendAnnounceWithTimeoutTracking(extension: GossipExtension, message: GossipMessage) {\n return extension.sendAnnounce(message).catch((err) => {\n if (err instanceof TimeoutError || err.constructor.name === 'TimeoutError' || err.message.startsWith('Timeout')) {\n if (message.channelId.startsWith(YJS_CHANNEL_PREFIX)) {\n this._addYjsTimeout();\n }\n }\n });\n }\n}\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Trigger } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { schema } from '@dxos/protocols';\nimport { type GossipMessage, type GossipService } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { createProtoRpcPeer, type ProtoRpcPeer } from '@dxos/rpc';\nimport { type ExtensionContext, type TeleportExtension } from '@dxos/teleport';\n\nexport type GossipCallbacks = {\n /**\n * Callback to be called when a new announce is received.\n */\n onAnnounce?: (message: GossipMessage) => Promise<void>;\n\n /**\n * Callback to be called when the extension is closed.\n */\n onClose?: (err?: Error) => Promise<void>;\n};\n\n/**\n * Sends announces between two peers for a single teleport session.\n */\nexport class GossipExtension implements TeleportExtension {\n private readonly _opened = new Trigger();\n private _closed = false;\n\n private _rpc?: ProtoRpcPeer<ServiceBundle>;\n\n constructor(private readonly _callbacks: GossipCallbacks = {}) {}\n\n async onOpen(context: ExtensionContext): Promise<void> {\n log('onOpen', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId });\n\n this._rpc = createProtoRpcPeer<ServiceBundle, ServiceBundle>({\n requested: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n exposed: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n handlers: {\n GossipService: {\n announce: async (message: GossipMessage) => {\n log('received announce', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId, message });\n await this._callbacks.onAnnounce?.(message);\n },\n },\n },\n port: await context.createPort('rpc', { contentType: 'application/x-protobuf; messageType=\"dxos.rpc.Message\"' }),\n });\n await this._rpc.open();\n this._opened.wake();\n }\n\n async onClose(err?: Error): Promise<void> {\n log('close', { err });\n await this._rpc?.close();\n await this._callbacks.onClose?.(err);\n this._closed = true;\n }\n\n async onAbort(err?: Error): Promise<void> {\n log('abort', { err });\n try {\n await this._rpc?.abort();\n } catch (err) {\n log.catch(err);\n } finally {\n await this._callbacks.onClose?.(err);\n }\n this._closed = true;\n }\n\n async sendAnnounce(message: GossipMessage) {\n if (this._closed) {\n return;\n }\n await this._opened.wait();\n invariant(this._rpc, 'RPC not initialized');\n await this._rpc.rpc.GossipService.announce(message);\n }\n}\n\ntype ServiceBundle = {\n GossipService: GossipService;\n};\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Event, scheduleTaskInterval } from '@dxos/async';\nimport { type WithTypeUrl } from '@dxos/codec-protobuf';\nimport { Context } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type Gossip } from './gossip';\n\nexport type PresenceParams = {\n /**\n * Interval between presence announces.\n */\n announceInterval: number;\n /**\n * Timeout after which a peer is considered offline.\n * Should be greater than announceInterval.\n */\n offlineTimeout: number;\n\n /**\n * Identity key of the local peer.\n */\n identityKey: PublicKey; // TODO(mykola): Remove once IdentityKey can be obtained from DeviceKey.\n\n gossip: Gossip;\n};\n\nconst PRESENCE_CHANNEL_ID = 'dxos.mesh.presence.Presence';\n\n/**\n * Presence manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received presence announces to all connected peers.\n * Exposes API to get the list of peers that are online.\n */\nexport class Presence {\n public readonly updated = new Event<void>();\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _peerStates = new ComplexMap<PublicKey, GossipMessage>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n\n constructor(private readonly _params: PresenceParams) {\n invariant(\n this._params.announceInterval < this._params.offlineTimeout,\n 'Announce interval should be less than offline timeout.',\n );\n\n this._params.gossip.listen(PRESENCE_CHANNEL_ID, (message) => {\n this._receiveAnnounces(message);\n });\n\n // Send announce to all connected peers.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n const peerState: WithTypeUrl<PeerState> = {\n '@type': 'dxos.mesh.presence.PeerState',\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n };\n await this._params.gossip.postMessage(PRESENCE_CHANNEL_ID, peerState);\n },\n _params.announceInterval,\n );\n\n // Emit updated event in case some peers went offline.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this.updated.emit();\n },\n _params.offlineTimeout,\n );\n\n // Remove peer state when connection is closed.\n this._params.gossip.connectionClosed.on((peerId) => {\n this._peerStates.delete(peerId);\n this.updated.emit();\n });\n }\n\n getPeers(): PeerState[] {\n return Array.from(this._peerStates.values()).map((message) => message.payload);\n }\n\n getPeersOnline(): PeerState[] {\n return Array.from(this._peerStates.values())\n .filter((message) => message.timestamp.getTime() > Date.now() - this._params.offlineTimeout)\n .map((message) => message.payload);\n }\n\n getLocalState(): PeerState {\n return {\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n peerId: this._params.gossip.localPeerId,\n };\n }\n\n async destroy() {\n await this._ctx.dispose();\n }\n\n private _receiveAnnounces(message: GossipMessage) {\n invariant(message.channelId === PRESENCE_CHANNEL_ID, `Invalid channel ID: ${message.channelId}`);\n const oldPeerState = this._peerStates.get(message.peerId);\n if (!oldPeerState || oldPeerState.timestamp.getTime() < message.timestamp.getTime()) {\n // Assign peer id to payload.\n (message.payload as PeerState).peerId = message.peerId;\n\n this._peerStates.set(message.peerId, message);\n this.updated.emit();\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["scheduleTask", "Event", "scheduleTaskInterval", "Context", "PublicKey", "log", "RpcClosedError", "TimeoutError", "ComplexMap", "ComplexSet", "Trigger", "invariant", "log", "schema", "createProtoRpcPeer", "GossipExtension", "constructor", "_callbacks", "_opened", "_closed", "onOpen", "context", "localPeerId", "remotePeerId", "_rpc", "requested", "GossipService", "getService", "exposed", "handlers", "announce", "message", "onAnnounce", "port", "createPort", "contentType", "open", "wake", "onClose", "err", "close", "onAbort", "abort", "catch", "sendAnnounce", "wait", "rpc", "RECEIVED_MESSAGES_GC_INTERVAL", "YJS_CHANNEL_PREFIX", "YJS_TIMEOUT_THRESHOLD", "YJS_TIMEOUT_WINDOW", "MAX_CTX_TASKS", "Gossip", "constructor", "_params", "_ctx", "Context", "onError", "err", "log", "catch", "_listeners", "Map", "_receivedMessages", "ComplexSet", "PublicKey", "hash", "_toClear", "_connections", "ComplexMap", "connectionClosed", "Event", "_yjs_timeout", "Array", "_yjs_timeout_index", "localPeerId", "open", "scheduleTaskInterval", "_performGc", "close", "dispose", "getConnections", "from", "keys", "createExtension", "remotePeerId", "extension", "GossipExtension", "onAnnounce", "message", "has", "messageId", "add", "_callListeners", "channelId", "startsWith", "_oldestYjsTimeoutInWindow", "disposeCallbacksLength", "scheduleTask", "_propagateAnnounce", "onClose", "delete", "emit", "set", "postMessage", "channel", "payload", "values", "_sendAnnounceWithTimeoutTracking", "peerId", "random", "timestamp", "Date", "RpcClosedError", "TimeoutError", "name", "listen", "callback", "Set", "get", "unsubscribe", "forEach", "Promise", "all", "entries", "map", "equals", "start", "performance", "now", "key", "clear", "elapsed", "warn", "_addYjsTimeout", "lastTS", "sendAnnounce", "Event", "scheduleTaskInterval", "Context", "invariant", "PublicKey", "log", "ComplexMap", "PRESENCE_CHANNEL_ID", "Presence", "constructor", "_params", "updated", "_ctx", "onError", "err", "catch", "_peerStates", "hash", "announceInterval", "offlineTimeout", "gossip", "listen", "message", "_receiveAnnounces", "peerState", "identityKey", "connections", "getConnections", "postMessage", "emit", "connectionClosed", "on", "peerId", "delete", "getPeers", "Array", "from", "values", "map", "payload", "getPeersOnline", "filter", "timestamp", "getTime", "Date", "now", "getLocalState", "localPeerId", "destroy", "dispose", "channelId", "oldPeerState", "get", "set"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts":{"bytes":9930,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/protocols","kind":"import-statement","external":true},{"path":"@dxos/rpc","kind":"import-statement","external":true}],"format":"esm"},"packages/core/mesh/teleport-extension-gossip/src/gossip.ts":{"bytes":25396,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/protocols","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts","kind":"import-statement","original":"./gossip-extension"}],"format":"esm"},"packages/core/mesh/teleport-extension-gossip/src/presence.ts":{"bytes":13151,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/mesh/teleport-extension-gossip/src/index.ts":{"bytes":673,"imports":[{"path":"packages/core/mesh/teleport-extension-gossip/src/gossip.ts","kind":"import-statement","original":"./gossip"},{"path":"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts","kind":"import-statement","original":"./gossip-extension"},{"path":"packages/core/mesh/teleport-extension-gossip/src/presence.ts","kind":"import-statement","original":"./presence"}],"format":"esm"}},"outputs":{"packages/core/mesh/teleport-extension-gossip/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":23051},"packages/core/mesh/teleport-extension-gossip/dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/protocols","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/protocols","kind":"import-statement","external":true},{"path":"@dxos/rpc","kind":"import-statement","external":true},{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Gossip","GossipExtension","Presence"],"entryPoint":"packages/core/mesh/teleport-extension-gossip/src/index.ts","inputs":{"packages/core/mesh/teleport-extension-gossip/src/gossip.ts":{"bytesInOutput":6800},"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts":{"bytesInOutput":2668},"packages/core/mesh/teleport-extension-gossip/src/index.ts":{"bytesInOutput":0},"packages/core/mesh/teleport-extension-gossip/src/presence.ts":{"bytesInOutput":3170}},"bytes":12988}}}
|
package/dist/lib/node/index.cjs
CHANGED
|
@@ -40,7 +40,7 @@ var import_invariant = require("@dxos/invariant");
|
|
|
40
40
|
var import_log = require("@dxos/log");
|
|
41
41
|
var import_protocols = require("@dxos/protocols");
|
|
42
42
|
var import_rpc = require("@dxos/rpc");
|
|
43
|
-
var __dxlog_file = "/home/
|
|
43
|
+
var __dxlog_file = "/home/circleci/project/packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts";
|
|
44
44
|
var GossipExtension = class {
|
|
45
45
|
constructor(_callbacks = {}) {
|
|
46
46
|
this._callbacks = _callbacks;
|
|
@@ -143,8 +143,12 @@ var GossipExtension = class {
|
|
|
143
143
|
};
|
|
144
144
|
|
|
145
145
|
// packages/core/mesh/teleport-extension-gossip/src/gossip.ts
|
|
146
|
-
var __dxlog_file2 = "/home/
|
|
146
|
+
var __dxlog_file2 = "/home/circleci/project/packages/core/mesh/teleport-extension-gossip/src/gossip.ts";
|
|
147
147
|
var RECEIVED_MESSAGES_GC_INTERVAL = 12e4;
|
|
148
|
+
var YJS_CHANNEL_PREFIX = "user-channel/yjs.awareness";
|
|
149
|
+
var YJS_TIMEOUT_THRESHOLD = 20;
|
|
150
|
+
var YJS_TIMEOUT_WINDOW = 1e3 * 30;
|
|
151
|
+
var MAX_CTX_TASKS = 50;
|
|
148
152
|
var Gossip = class {
|
|
149
153
|
constructor(_params) {
|
|
150
154
|
this._params = _params;
|
|
@@ -152,7 +156,7 @@ var Gossip = class {
|
|
|
152
156
|
onError: (err) => {
|
|
153
157
|
import_log2.log.catch(err, void 0, {
|
|
154
158
|
F: __dxlog_file2,
|
|
155
|
-
L:
|
|
159
|
+
L: 35,
|
|
156
160
|
S: this,
|
|
157
161
|
C: (f, a) => f(...a)
|
|
158
162
|
});
|
|
@@ -163,6 +167,8 @@ var Gossip = class {
|
|
|
163
167
|
this._toClear = new import_util.ComplexSet(import_keys.PublicKey.hash);
|
|
164
168
|
this._connections = new import_util.ComplexMap(import_keys.PublicKey.hash);
|
|
165
169
|
this.connectionClosed = new import_async2.Event();
|
|
170
|
+
this._yjs_timeout = new Array(YJS_TIMEOUT_THRESHOLD);
|
|
171
|
+
this._yjs_timeout_index = 0;
|
|
166
172
|
}
|
|
167
173
|
get localPeerId() {
|
|
168
174
|
return this._params.localPeerId;
|
|
@@ -186,6 +192,24 @@ var Gossip = class {
|
|
|
186
192
|
}
|
|
187
193
|
this._receivedMessages.add(message.messageId);
|
|
188
194
|
this._callListeners(message);
|
|
195
|
+
if (message.channelId.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {
|
|
196
|
+
(0, import_log2.log)("skipping propagating YJS gossip message due to timeouts", void 0, {
|
|
197
|
+
F: __dxlog_file2,
|
|
198
|
+
L: 91,
|
|
199
|
+
S: this,
|
|
200
|
+
C: (f, a) => f(...a)
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (this._ctx.disposeCallbacksLength > MAX_CTX_TASKS) {
|
|
205
|
+
(0, import_log2.log)("skipping propagating YJS gossip message due to exessive tasks", void 0, {
|
|
206
|
+
F: __dxlog_file2,
|
|
207
|
+
L: 95,
|
|
208
|
+
S: this,
|
|
209
|
+
C: (f, a) => f(...a)
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
189
213
|
(0, import_async2.scheduleTask)(this._ctx, async () => {
|
|
190
214
|
await this._propagateAnnounce(message);
|
|
191
215
|
});
|
|
@@ -194,7 +218,7 @@ var Gossip = class {
|
|
|
194
218
|
if (err) {
|
|
195
219
|
import_log2.log.catch(err, void 0, {
|
|
196
220
|
F: __dxlog_file2,
|
|
197
|
-
L:
|
|
221
|
+
L: 104,
|
|
198
222
|
S: this,
|
|
199
223
|
C: (f, a) => f(...a)
|
|
200
224
|
});
|
|
@@ -209,8 +233,17 @@ var Gossip = class {
|
|
|
209
233
|
return extension;
|
|
210
234
|
}
|
|
211
235
|
postMessage(channel, payload) {
|
|
236
|
+
if (channel.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {
|
|
237
|
+
(0, import_log2.log)("skipping YJS gossip message due to timeouts", void 0, {
|
|
238
|
+
F: __dxlog_file2,
|
|
239
|
+
L: 119,
|
|
240
|
+
S: this,
|
|
241
|
+
C: (f, a) => f(...a)
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
212
245
|
for (const extension of this._connections.values()) {
|
|
213
|
-
|
|
246
|
+
this._sendAnnounceWithTimeoutTracking(extension, {
|
|
214
247
|
peerId: this._params.localPeerId,
|
|
215
248
|
messageId: import_keys.PublicKey.random(),
|
|
216
249
|
channelId: channel,
|
|
@@ -222,7 +255,7 @@ var Gossip = class {
|
|
|
222
255
|
err
|
|
223
256
|
}, {
|
|
224
257
|
F: __dxlog_file2,
|
|
225
|
-
L:
|
|
258
|
+
L: 131,
|
|
226
259
|
S: this,
|
|
227
260
|
C: (f, a) => f(...a)
|
|
228
261
|
});
|
|
@@ -231,14 +264,14 @@ var Gossip = class {
|
|
|
231
264
|
err
|
|
232
265
|
}, {
|
|
233
266
|
F: __dxlog_file2,
|
|
234
|
-
L:
|
|
267
|
+
L: 137,
|
|
235
268
|
S: this,
|
|
236
269
|
C: (f, a) => f(...a)
|
|
237
270
|
});
|
|
238
271
|
} else {
|
|
239
272
|
import_log2.log.catch(err, void 0, {
|
|
240
273
|
F: __dxlog_file2,
|
|
241
|
-
L:
|
|
274
|
+
L: 139,
|
|
242
275
|
S: this,
|
|
243
276
|
C: (f, a) => f(...a)
|
|
244
277
|
});
|
|
@@ -271,9 +304,9 @@ var Gossip = class {
|
|
|
271
304
|
if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {
|
|
272
305
|
return;
|
|
273
306
|
}
|
|
274
|
-
return
|
|
307
|
+
return this._sendAnnounceWithTimeoutTracking(extension, message).catch((err) => (0, import_log2.log)(err, void 0, {
|
|
275
308
|
F: __dxlog_file2,
|
|
276
|
-
L:
|
|
309
|
+
L: 172,
|
|
277
310
|
S: this,
|
|
278
311
|
C: (f, a) => f(...a)
|
|
279
312
|
}));
|
|
@@ -294,12 +327,32 @@ var Gossip = class {
|
|
|
294
327
|
elapsed
|
|
295
328
|
}, {
|
|
296
329
|
F: __dxlog_file2,
|
|
297
|
-
L:
|
|
330
|
+
L: 190,
|
|
298
331
|
S: this,
|
|
299
332
|
C: (f, a) => f(...a)
|
|
300
333
|
});
|
|
301
334
|
}
|
|
302
335
|
}
|
|
336
|
+
_addYjsTimeout() {
|
|
337
|
+
this._yjs_timeout[this._yjs_timeout_index] = Date.now();
|
|
338
|
+
this._yjs_timeout_index = (this._yjs_timeout_index + 1) % YJS_TIMEOUT_THRESHOLD;
|
|
339
|
+
}
|
|
340
|
+
_oldestYjsTimeoutInWindow() {
|
|
341
|
+
const lastTS = this._yjs_timeout[(this._yjs_timeout_index + YJS_TIMEOUT_THRESHOLD - 1) % YJS_TIMEOUT_THRESHOLD];
|
|
342
|
+
if (!lastTS) {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
return Date.now() - lastTS < YJS_TIMEOUT_WINDOW;
|
|
346
|
+
}
|
|
347
|
+
_sendAnnounceWithTimeoutTracking(extension, message) {
|
|
348
|
+
return extension.sendAnnounce(message).catch((err) => {
|
|
349
|
+
if (err instanceof import_protocols2.TimeoutError || err.constructor.name === "TimeoutError" || err.message.startsWith("Timeout")) {
|
|
350
|
+
if (message.channelId.startsWith(YJS_CHANNEL_PREFIX)) {
|
|
351
|
+
this._addYjsTimeout();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
303
356
|
};
|
|
304
357
|
|
|
305
358
|
// packages/core/mesh/teleport-extension-gossip/src/presence.ts
|
|
@@ -309,7 +362,7 @@ var import_invariant2 = require("@dxos/invariant");
|
|
|
309
362
|
var import_keys2 = require("@dxos/keys");
|
|
310
363
|
var import_log3 = require("@dxos/log");
|
|
311
364
|
var import_util2 = require("@dxos/util");
|
|
312
|
-
var __dxlog_file3 = "/home/
|
|
365
|
+
var __dxlog_file3 = "/home/circleci/project/packages/core/mesh/teleport-extension-gossip/src/presence.ts";
|
|
313
366
|
var PRESENCE_CHANNEL_ID = "dxos.mesh.presence.Presence";
|
|
314
367
|
var Presence = class {
|
|
315
368
|
// remotePeerId -> PresenceExtension
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts", "../../../src/gossip.ts", "../../../src/gossip-extension.ts", "../../../src/presence.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nexport * from './gossip';\nexport * from './gossip-extension';\nexport * from './presence';\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { scheduleTask, Event, scheduleTaskInterval } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { RpcClosedError, TimeoutError } from '@dxos/protocols';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap, ComplexSet } from '@dxos/util';\n\nimport { GossipExtension } from './gossip-extension';\n\nexport type GossipParams = {\n localPeerId: PublicKey;\n};\n\nconst RECEIVED_MESSAGES_GC_INTERVAL = 120_000;\n\n/**\n * Gossip extensions manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received announces to all connected peers.\n * Exposes API send announce to everybody and subscribe to .\n */\nexport class Gossip {\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _listeners = new Map<string, Set<(message: GossipMessage) => void>>();\n\n private readonly _receivedMessages = new ComplexSet<PublicKey>(PublicKey.hash);\n\n /**\n * Keys scheduled to be cleared from _receivedMessages on the next iteration.\n */\n private readonly _toClear = new ComplexSet<PublicKey>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n private readonly _connections = new ComplexMap<PublicKey, GossipExtension>(PublicKey.hash);\n\n public readonly connectionClosed = new Event<PublicKey>();\n\n constructor(private readonly _params: GossipParams) {}\n\n get localPeerId() {\n return this._params.localPeerId;\n }\n\n async open() {\n // Clear the map periodically.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this._performGc();\n },\n RECEIVED_MESSAGES_GC_INTERVAL,\n );\n }\n\n async close() {\n await this._ctx.dispose();\n }\n\n getConnections() {\n return Array.from(this._connections.keys());\n }\n\n createExtension({ remotePeerId }: { remotePeerId: PublicKey }): GossipExtension {\n const extension = new GossipExtension({\n onAnnounce: async (message) => {\n if (this._receivedMessages.has(message.messageId)) {\n return;\n }\n this._receivedMessages.add(message.messageId);\n this._callListeners(message);\n scheduleTask(this._ctx, async () => {\n await this._propagateAnnounce(message);\n });\n },\n onClose: async (err) => {\n if (err) {\n log.catch(err);\n }\n if (this._connections.has(remotePeerId)) {\n this._connections.delete(remotePeerId);\n }\n this.connectionClosed.emit(remotePeerId);\n },\n });\n this._connections.set(remotePeerId, extension);\n\n return extension;\n }\n\n postMessage(channel: string, payload: any) {\n for (const extension of this._connections.values()) {\n void extension\n .sendAnnounce({\n peerId: this._params.localPeerId,\n messageId: PublicKey.random(),\n channelId: channel,\n timestamp: new Date(),\n payload,\n })\n\n .catch(async (err) => {\n if (err instanceof RpcClosedError) {\n log('sendAnnounce failed because of RpcClosedError', { err });\n } else if (\n err instanceof TimeoutError ||\n err.constructor.name === 'TimeoutError' ||\n err.message.startsWith('Timeout')\n ) {\n log('sendAnnounce failed because of TimeoutError', { err });\n } else {\n log.catch(err);\n }\n });\n }\n }\n\n listen(channel: string, callback: (message: GossipMessage) => void) {\n if (!this._listeners.has(channel)) {\n this._listeners.set(channel, new Set());\n }\n this._listeners.get(channel)!.add(callback);\n\n return {\n unsubscribe: () => {\n this._listeners.get(channel)!.delete(callback);\n },\n };\n }\n\n private _callListeners(message: GossipMessage) {\n if (this._listeners.has(message.channelId)) {\n this._listeners.get(message.channelId)!.forEach((callback) => {\n callback(message);\n });\n }\n }\n\n private _propagateAnnounce(message: GossipMessage) {\n return Promise.all(\n [...this._connections.entries()].map(async ([remotePeerId, extension]) => {\n if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {\n return;\n }\n return extension.sendAnnounce(message).catch((err) => log(err));\n }),\n );\n }\n\n private _performGc() {\n const start = performance.now();\n\n for (const key of this._toClear.keys()) {\n this._receivedMessages.delete(key);\n }\n this._toClear.clear();\n for (const key of this._receivedMessages.keys()) {\n this._toClear.add(key);\n }\n\n const elapsed = performance.now() - start;\n if (elapsed > 100) {\n log.warn('GC took too long', { elapsed });\n }\n }\n}\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Trigger } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { schema } from '@dxos/protocols';\nimport { type GossipMessage, type GossipService } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { createProtoRpcPeer, type ProtoRpcPeer } from '@dxos/rpc';\nimport { type ExtensionContext, type TeleportExtension } from '@dxos/teleport';\n\nexport type GossipCallbacks = {\n /**\n * Callback to be called when a new announce is received.\n */\n onAnnounce?: (message: GossipMessage) => Promise<void>;\n\n /**\n * Callback to be called when the extension is closed.\n */\n onClose?: (err?: Error) => Promise<void>;\n};\n\n/**\n * Sends announces between two peers for a single teleport session.\n */\nexport class GossipExtension implements TeleportExtension {\n private readonly _opened = new Trigger();\n private _closed = false;\n\n private _rpc?: ProtoRpcPeer<ServiceBundle>;\n\n constructor(private readonly _callbacks: GossipCallbacks = {}) {}\n\n async onOpen(context: ExtensionContext): Promise<void> {\n log('onOpen', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId });\n\n this._rpc = createProtoRpcPeer<ServiceBundle, ServiceBundle>({\n requested: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n exposed: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n handlers: {\n GossipService: {\n announce: async (message: GossipMessage) => {\n log('received announce', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId, message });\n await this._callbacks.onAnnounce?.(message);\n },\n },\n },\n port: await context.createPort('rpc', { contentType: 'application/x-protobuf; messageType=\"dxos.rpc.Message\"' }),\n });\n await this._rpc.open();\n this._opened.wake();\n }\n\n async onClose(err?: Error): Promise<void> {\n log('close', { err });\n await this._rpc?.close();\n await this._callbacks.onClose?.(err);\n this._closed = true;\n }\n\n async onAbort(err?: Error): Promise<void> {\n log('abort', { err });\n try {\n await this._rpc?.abort();\n } catch (err) {\n log.catch(err);\n } finally {\n await this._callbacks.onClose?.(err);\n }\n this._closed = true;\n }\n\n async sendAnnounce(message: GossipMessage) {\n if (this._closed) {\n return;\n }\n await this._opened.wait();\n invariant(this._rpc, 'RPC not initialized');\n await this._rpc.rpc.GossipService.announce(message);\n }\n}\n\ntype ServiceBundle = {\n GossipService: GossipService;\n};\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Event, scheduleTaskInterval } from '@dxos/async';\nimport { type WithTypeUrl } from '@dxos/codec-protobuf';\nimport { Context } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type Gossip } from './gossip';\n\nexport type PresenceParams = {\n /**\n * Interval between presence announces.\n */\n announceInterval: number;\n /**\n * Timeout after which a peer is considered offline.\n * Should be greater than announceInterval.\n */\n offlineTimeout: number;\n\n /**\n * Identity key of the local peer.\n */\n identityKey: PublicKey; // TODO(mykola): Remove once IdentityKey can be obtained from DeviceKey.\n\n gossip: Gossip;\n};\n\nconst PRESENCE_CHANNEL_ID = 'dxos.mesh.presence.Presence';\n\n/**\n * Presence manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received presence announces to all connected peers.\n * Exposes API to get the list of peers that are online.\n */\nexport class Presence {\n public readonly updated = new Event<void>();\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _peerStates = new ComplexMap<PublicKey, GossipMessage>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n\n constructor(private readonly _params: PresenceParams) {\n invariant(\n this._params.announceInterval < this._params.offlineTimeout,\n 'Announce interval should be less than offline timeout.',\n );\n\n this._params.gossip.listen(PRESENCE_CHANNEL_ID, (message) => {\n this._receiveAnnounces(message);\n });\n\n // Send announce to all connected peers.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n const peerState: WithTypeUrl<PeerState> = {\n '@type': 'dxos.mesh.presence.PeerState',\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n };\n await this._params.gossip.postMessage(PRESENCE_CHANNEL_ID, peerState);\n },\n _params.announceInterval,\n );\n\n // Emit updated event in case some peers went offline.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this.updated.emit();\n },\n _params.offlineTimeout,\n );\n\n // Remove peer state when connection is closed.\n this._params.gossip.connectionClosed.on((peerId) => {\n this._peerStates.delete(peerId);\n this.updated.emit();\n });\n }\n\n getPeers(): PeerState[] {\n return Array.from(this._peerStates.values()).map((message) => message.payload);\n }\n\n getPeersOnline(): PeerState[] {\n return Array.from(this._peerStates.values())\n .filter((message) => message.timestamp.getTime() > Date.now() - this._params.offlineTimeout)\n .map((message) => message.payload);\n }\n\n getLocalState(): PeerState {\n return {\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n peerId: this._params.gossip.localPeerId,\n };\n }\n\n async destroy() {\n await this._ctx.dispose();\n }\n\n private _receiveAnnounces(message: GossipMessage) {\n invariant(message.channelId === PRESENCE_CHANNEL_ID, `Invalid channel ID: ${message.channelId}`);\n const oldPeerState = this._peerStates.get(message.peerId);\n if (!oldPeerState || oldPeerState.timestamp.getTime() < message.timestamp.getTime()) {\n // Assign peer id to payload.\n (message.payload as PeerState).peerId = message.peerId;\n\n this._peerStates.set(message.peerId, message);\n this.updated.emit();\n }\n }\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;ACIA,IAAAA,gBAA0D;AAC1D,qBAAwB;AACxB,kBAA0B;AAC1B,IAAAC,cAAoB;AACpB,IAAAC,oBAA6C;AAE7C,kBAAuC;;;ACNvC,mBAAwB;AACxB,uBAA0B;AAC1B,iBAAoB;AACpB,uBAAuB;AAEvB,iBAAsD;;AAkB/C,IAAMC,kBAAN,MAAMA;EAMXC,YAA6BC,aAA8B,CAAC,GAAG;sBAAlCA;SALZC,UAAU,IAAIC,qBAAAA;SACvBC,UAAU;EAI8C;EAEhE,MAAMC,OAAOC,SAA0C;AACrDC,wBAAI,UAAU;MAAEC,aAAaF,QAAQE;MAAaC,cAAcH,QAAQG;IAAa,GAAA;;;;;;AAErF,SAAKC,WAAOC,+BAAiD;MAC3DC,WAAW;QACTC,eAAeC,wBAAOC,WAAW,yCAAA;MACnC;MACAC,SAAS;QACPH,eAAeC,wBAAOC,WAAW,yCAAA;MACnC;MACAE,UAAU;QACRJ,eAAe;UACbK,UAAU,OAAOC,YAAAA;AACfZ,gCAAI,qBAAqB;cAAEC,aAAaF,QAAQE;cAAaC,cAAcH,QAAQG;cAAcU;YAAQ,GAAA;;;;;;AACzG,kBAAM,KAAKlB,WAAWmB,aAAaD,OAAAA;UACrC;QACF;MACF;MACAE,MAAM,MAAMf,QAAQgB,WAAW,OAAO;QAAEC,aAAa;MAAyD,CAAA;IAChH,CAAA;AACA,UAAM,KAAKb,KAAKc,KAAI;AACpB,SAAKtB,QAAQuB,KAAI;EACnB;EAEA,MAAMC,QAAQC,KAA4B;AACxCpB,wBAAI,SAAS;MAAEoB;IAAI,GAAA;;;;;;AACnB,UAAM,KAAKjB,MAAMkB,MAAAA;AACjB,UAAM,KAAK3B,WAAWyB,UAAUC,GAAAA;AAChC,SAAKvB,UAAU;EACjB;EAEA,MAAMyB,QAAQF,KAA4B;AACxCpB,wBAAI,SAAS;MAAEoB;IAAI,GAAA;;;;;;AACnB,QAAI;AACF,YAAM,KAAKjB,MAAMoB,MAAAA;IACnB,SAASH,MAAK;AACZpB,qBAAIwB,MAAMJ,MAAAA,QAAAA;;;;;;IACZ,UAAA;AACE,YAAM,KAAK1B,WAAWyB,UAAUC,GAAAA;IAClC;AACA,SAAKvB,UAAU;EACjB;EAEA,MAAM4B,aAAab,SAAwB;AACzC,QAAI,KAAKf,SAAS;AAChB;IACF;AACA,UAAM,KAAKF,QAAQ+B,KAAI;AACvBC,oCAAU,KAAKxB,MAAM,uBAAA;;;;;;;;;AACrB,UAAM,KAAKA,KAAKyB,IAAItB,cAAcK,SAASC,OAAAA;EAC7C;AACF;;;;ADpEA,IAAMiB,gCAAgC;
|
|
6
|
-
"names": ["import_async", "import_log", "import_protocols", "GossipExtension", "constructor", "_callbacks", "_opened", "Trigger", "_closed", "onOpen", "context", "log", "localPeerId", "remotePeerId", "_rpc", "createProtoRpcPeer", "requested", "GossipService", "schema", "getService", "exposed", "handlers", "announce", "message", "onAnnounce", "port", "createPort", "contentType", "open", "wake", "onClose", "err", "close", "onAbort", "abort", "catch", "sendAnnounce", "wait", "invariant", "rpc", "RECEIVED_MESSAGES_GC_INTERVAL", "Gossip", "constructor", "_params", "_ctx", "Context", "onError", "err", "log", "catch", "_listeners", "Map", "_receivedMessages", "ComplexSet", "PublicKey", "hash", "_toClear", "_connections", "ComplexMap", "connectionClosed", "Event", "localPeerId", "open", "scheduleTaskInterval", "_performGc", "close", "dispose", "getConnections", "
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nexport * from './gossip';\nexport * from './gossip-extension';\nexport * from './presence';\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { scheduleTask, Event, scheduleTaskInterval } from '@dxos/async';\nimport { Context } from '@dxos/context';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { RpcClosedError, TimeoutError } from '@dxos/protocols';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap, ComplexSet } from '@dxos/util';\n\nimport { GossipExtension } from './gossip-extension';\n\nexport type GossipParams = {\n localPeerId: PublicKey;\n};\n\nconst RECEIVED_MESSAGES_GC_INTERVAL = 120_000;\n\nconst YJS_CHANNEL_PREFIX = 'user-channel/yjs.awareness';\nconst YJS_TIMEOUT_THRESHOLD = 20;\nconst YJS_TIMEOUT_WINDOW = 1000 * 30;\nconst MAX_CTX_TASKS = 50;\n\n/**\n * Gossip extensions manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received announces to all connected peers.\n * Exposes API send announce to everybody and subscribe to .\n */\nexport class Gossip {\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _listeners = new Map<string, Set<(message: GossipMessage) => void>>();\n\n private readonly _receivedMessages = new ComplexSet<PublicKey>(PublicKey.hash);\n\n /**\n * Keys scheduled to be cleared from _receivedMessages on the next iteration.\n */\n private readonly _toClear = new ComplexSet<PublicKey>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n private readonly _connections = new ComplexMap<PublicKey, GossipExtension>(PublicKey.hash);\n\n public readonly connectionClosed = new Event<PublicKey>();\n\n // ringbuffer for yjs timeouts\n private _yjs_timeout = new Array(YJS_TIMEOUT_THRESHOLD);\n private _yjs_timeout_index = 0;\n\n constructor(private readonly _params: GossipParams) {}\n\n get localPeerId() {\n return this._params.localPeerId;\n }\n\n async open() {\n // Clear the map periodically.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this._performGc();\n },\n RECEIVED_MESSAGES_GC_INTERVAL,\n );\n }\n\n async close() {\n await this._ctx.dispose();\n }\n\n getConnections() {\n return Array.from(this._connections.keys());\n }\n\n createExtension({ remotePeerId }: { remotePeerId: PublicKey }): GossipExtension {\n const extension = new GossipExtension({\n onAnnounce: async (message) => {\n if (this._receivedMessages.has(message.messageId)) {\n return;\n }\n this._receivedMessages.add(message.messageId);\n this._callListeners(message);\n if (message.channelId.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {\n log('skipping propagating YJS gossip message due to timeouts');\n return;\n }\n if (this._ctx.disposeCallbacksLength > MAX_CTX_TASKS) {\n log('skipping propagating YJS gossip message due to exessive tasks');\n return;\n }\n scheduleTask(this._ctx, async () => {\n await this._propagateAnnounce(message);\n });\n },\n onClose: async (err) => {\n if (err) {\n log.catch(err);\n }\n if (this._connections.has(remotePeerId)) {\n this._connections.delete(remotePeerId);\n }\n this.connectionClosed.emit(remotePeerId);\n },\n });\n this._connections.set(remotePeerId, extension);\n\n return extension;\n }\n\n postMessage(channel: string, payload: any) {\n if (channel.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {\n log('skipping YJS gossip message due to timeouts');\n return;\n }\n for (const extension of this._connections.values()) {\n this._sendAnnounceWithTimeoutTracking(extension, {\n peerId: this._params.localPeerId,\n messageId: PublicKey.random(),\n channelId: channel,\n timestamp: new Date(),\n payload,\n }).catch(async (err) => {\n if (err instanceof RpcClosedError) {\n log('sendAnnounce failed because of RpcClosedError', { err });\n } else if (\n err instanceof TimeoutError ||\n err.constructor.name === 'TimeoutError' ||\n err.message.startsWith('Timeout')\n ) {\n log('sendAnnounce failed because of TimeoutError', { err });\n } else {\n log.catch(err);\n }\n });\n }\n }\n\n listen(channel: string, callback: (message: GossipMessage) => void) {\n if (!this._listeners.has(channel)) {\n this._listeners.set(channel, new Set());\n }\n this._listeners.get(channel)!.add(callback);\n\n return {\n unsubscribe: () => {\n this._listeners.get(channel)!.delete(callback);\n },\n };\n }\n\n private _callListeners(message: GossipMessage) {\n if (this._listeners.has(message.channelId)) {\n this._listeners.get(message.channelId)!.forEach((callback) => {\n callback(message);\n });\n }\n }\n\n private _propagateAnnounce(message: GossipMessage) {\n return Promise.all(\n [...this._connections.entries()].map(async ([remotePeerId, extension]) => {\n if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {\n return;\n }\n return this._sendAnnounceWithTimeoutTracking(extension, message).catch((err) => log(err));\n }),\n );\n }\n\n private _performGc() {\n const start = performance.now();\n\n for (const key of this._toClear.keys()) {\n this._receivedMessages.delete(key);\n }\n this._toClear.clear();\n for (const key of this._receivedMessages.keys()) {\n this._toClear.add(key);\n }\n\n const elapsed = performance.now() - start;\n if (elapsed > 100) {\n log.warn('GC took too long', { elapsed });\n }\n }\n\n private _addYjsTimeout() {\n this._yjs_timeout[this._yjs_timeout_index] = Date.now();\n this._yjs_timeout_index = (this._yjs_timeout_index + 1) % YJS_TIMEOUT_THRESHOLD;\n }\n\n private _oldestYjsTimeoutInWindow(): boolean {\n const lastTS = this._yjs_timeout[(this._yjs_timeout_index + YJS_TIMEOUT_THRESHOLD - 1) % YJS_TIMEOUT_THRESHOLD];\n if (!lastTS) {\n return false;\n }\n\n return Date.now() - lastTS < YJS_TIMEOUT_WINDOW;\n }\n\n private _sendAnnounceWithTimeoutTracking(extension: GossipExtension, message: GossipMessage) {\n return extension.sendAnnounce(message).catch((err) => {\n if (err instanceof TimeoutError || err.constructor.name === 'TimeoutError' || err.message.startsWith('Timeout')) {\n if (message.channelId.startsWith(YJS_CHANNEL_PREFIX)) {\n this._addYjsTimeout();\n }\n }\n });\n }\n}\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Trigger } from '@dxos/async';\nimport { invariant } from '@dxos/invariant';\nimport { log } from '@dxos/log';\nimport { schema } from '@dxos/protocols';\nimport { type GossipMessage, type GossipService } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { createProtoRpcPeer, type ProtoRpcPeer } from '@dxos/rpc';\nimport { type ExtensionContext, type TeleportExtension } from '@dxos/teleport';\n\nexport type GossipCallbacks = {\n /**\n * Callback to be called when a new announce is received.\n */\n onAnnounce?: (message: GossipMessage) => Promise<void>;\n\n /**\n * Callback to be called when the extension is closed.\n */\n onClose?: (err?: Error) => Promise<void>;\n};\n\n/**\n * Sends announces between two peers for a single teleport session.\n */\nexport class GossipExtension implements TeleportExtension {\n private readonly _opened = new Trigger();\n private _closed = false;\n\n private _rpc?: ProtoRpcPeer<ServiceBundle>;\n\n constructor(private readonly _callbacks: GossipCallbacks = {}) {}\n\n async onOpen(context: ExtensionContext): Promise<void> {\n log('onOpen', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId });\n\n this._rpc = createProtoRpcPeer<ServiceBundle, ServiceBundle>({\n requested: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n exposed: {\n GossipService: schema.getService('dxos.mesh.teleport.gossip.GossipService'),\n },\n handlers: {\n GossipService: {\n announce: async (message: GossipMessage) => {\n log('received announce', { localPeerId: context.localPeerId, remotePeerId: context.remotePeerId, message });\n await this._callbacks.onAnnounce?.(message);\n },\n },\n },\n port: await context.createPort('rpc', { contentType: 'application/x-protobuf; messageType=\"dxos.rpc.Message\"' }),\n });\n await this._rpc.open();\n this._opened.wake();\n }\n\n async onClose(err?: Error): Promise<void> {\n log('close', { err });\n await this._rpc?.close();\n await this._callbacks.onClose?.(err);\n this._closed = true;\n }\n\n async onAbort(err?: Error): Promise<void> {\n log('abort', { err });\n try {\n await this._rpc?.abort();\n } catch (err) {\n log.catch(err);\n } finally {\n await this._callbacks.onClose?.(err);\n }\n this._closed = true;\n }\n\n async sendAnnounce(message: GossipMessage) {\n if (this._closed) {\n return;\n }\n await this._opened.wait();\n invariant(this._rpc, 'RPC not initialized');\n await this._rpc.rpc.GossipService.announce(message);\n }\n}\n\ntype ServiceBundle = {\n GossipService: GossipService;\n};\n", "//\n// Copyright 2022 DXOS.org\n//\n\nimport { Event, scheduleTaskInterval } from '@dxos/async';\nimport { type WithTypeUrl } from '@dxos/codec-protobuf';\nimport { Context } from '@dxos/context';\nimport { invariant } from '@dxos/invariant';\nimport { PublicKey } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { type PeerState } from '@dxos/protocols/proto/dxos/mesh/presence';\nimport { type GossipMessage } from '@dxos/protocols/proto/dxos/mesh/teleport/gossip';\nimport { ComplexMap } from '@dxos/util';\n\nimport { type Gossip } from './gossip';\n\nexport type PresenceParams = {\n /**\n * Interval between presence announces.\n */\n announceInterval: number;\n /**\n * Timeout after which a peer is considered offline.\n * Should be greater than announceInterval.\n */\n offlineTimeout: number;\n\n /**\n * Identity key of the local peer.\n */\n identityKey: PublicKey; // TODO(mykola): Remove once IdentityKey can be obtained from DeviceKey.\n\n gossip: Gossip;\n};\n\nconst PRESENCE_CHANNEL_ID = 'dxos.mesh.presence.Presence';\n\n/**\n * Presence manager.\n * Keeps track of all peers that are connected to the local peer.\n * Routes received presence announces to all connected peers.\n * Exposes API to get the list of peers that are online.\n */\nexport class Presence {\n public readonly updated = new Event<void>();\n private readonly _ctx = new Context({\n onError: (err) => {\n log.catch(err);\n },\n });\n\n private readonly _peerStates = new ComplexMap<PublicKey, GossipMessage>(PublicKey.hash);\n\n // remotePeerId -> PresenceExtension\n\n constructor(private readonly _params: PresenceParams) {\n invariant(\n this._params.announceInterval < this._params.offlineTimeout,\n 'Announce interval should be less than offline timeout.',\n );\n\n this._params.gossip.listen(PRESENCE_CHANNEL_ID, (message) => {\n this._receiveAnnounces(message);\n });\n\n // Send announce to all connected peers.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n const peerState: WithTypeUrl<PeerState> = {\n '@type': 'dxos.mesh.presence.PeerState',\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n };\n await this._params.gossip.postMessage(PRESENCE_CHANNEL_ID, peerState);\n },\n _params.announceInterval,\n );\n\n // Emit updated event in case some peers went offline.\n scheduleTaskInterval(\n this._ctx,\n async () => {\n this.updated.emit();\n },\n _params.offlineTimeout,\n );\n\n // Remove peer state when connection is closed.\n this._params.gossip.connectionClosed.on((peerId) => {\n this._peerStates.delete(peerId);\n this.updated.emit();\n });\n }\n\n getPeers(): PeerState[] {\n return Array.from(this._peerStates.values()).map((message) => message.payload);\n }\n\n getPeersOnline(): PeerState[] {\n return Array.from(this._peerStates.values())\n .filter((message) => message.timestamp.getTime() > Date.now() - this._params.offlineTimeout)\n .map((message) => message.payload);\n }\n\n getLocalState(): PeerState {\n return {\n identityKey: this._params.identityKey,\n connections: this._params.gossip.getConnections(),\n peerId: this._params.gossip.localPeerId,\n };\n }\n\n async destroy() {\n await this._ctx.dispose();\n }\n\n private _receiveAnnounces(message: GossipMessage) {\n invariant(message.channelId === PRESENCE_CHANNEL_ID, `Invalid channel ID: ${message.channelId}`);\n const oldPeerState = this._peerStates.get(message.peerId);\n if (!oldPeerState || oldPeerState.timestamp.getTime() < message.timestamp.getTime()) {\n // Assign peer id to payload.\n (message.payload as PeerState).peerId = message.peerId;\n\n this._peerStates.set(message.peerId, message);\n this.updated.emit();\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_async", "import_log", "import_protocols", "GossipExtension", "constructor", "_callbacks", "_opened", "Trigger", "_closed", "onOpen", "context", "log", "localPeerId", "remotePeerId", "_rpc", "createProtoRpcPeer", "requested", "GossipService", "schema", "getService", "exposed", "handlers", "announce", "message", "onAnnounce", "port", "createPort", "contentType", "open", "wake", "onClose", "err", "close", "onAbort", "abort", "catch", "sendAnnounce", "wait", "invariant", "rpc", "RECEIVED_MESSAGES_GC_INTERVAL", "YJS_CHANNEL_PREFIX", "YJS_TIMEOUT_THRESHOLD", "YJS_TIMEOUT_WINDOW", "MAX_CTX_TASKS", "Gossip", "constructor", "_params", "_ctx", "Context", "onError", "err", "log", "catch", "_listeners", "Map", "_receivedMessages", "ComplexSet", "PublicKey", "hash", "_toClear", "_connections", "ComplexMap", "connectionClosed", "Event", "_yjs_timeout", "Array", "_yjs_timeout_index", "localPeerId", "open", "scheduleTaskInterval", "_performGc", "close", "dispose", "getConnections", "from", "keys", "createExtension", "remotePeerId", "extension", "GossipExtension", "onAnnounce", "message", "has", "messageId", "add", "_callListeners", "channelId", "startsWith", "_oldestYjsTimeoutInWindow", "disposeCallbacksLength", "scheduleTask", "_propagateAnnounce", "onClose", "delete", "emit", "set", "postMessage", "channel", "payload", "values", "_sendAnnounceWithTimeoutTracking", "peerId", "random", "timestamp", "Date", "RpcClosedError", "TimeoutError", "name", "listen", "callback", "Set", "get", "unsubscribe", "forEach", "Promise", "all", "entries", "map", "equals", "start", "performance", "now", "key", "clear", "elapsed", "warn", "_addYjsTimeout", "lastTS", "sendAnnounce", "import_async", "import_context", "import_invariant", "import_keys", "import_log", "import_util", "PRESENCE_CHANNEL_ID", "Presence", "constructor", "_params", "updated", "Event", "_ctx", "Context", "onError", "err", "log", "catch", "_peerStates", "ComplexMap", "PublicKey", "hash", "invariant", "announceInterval", "offlineTimeout", "gossip", "listen", "message", "_receiveAnnounces", "scheduleTaskInterval", "peerState", "identityKey", "connections", "getConnections", "postMessage", "emit", "connectionClosed", "on", "peerId", "delete", "getPeers", "Array", "from", "values", "map", "payload", "getPeersOnline", "filter", "timestamp", "getTime", "Date", "now", "getLocalState", "localPeerId", "destroy", "dispose", "channelId", "oldPeerState", "get", "set"]
|
|
7
7
|
}
|
package/dist/lib/node/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts":{"bytes":9930,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/protocols","kind":"import-statement","external":true},{"path":"@dxos/rpc","kind":"import-statement","external":true}],"format":"esm"},"packages/core/mesh/teleport-extension-gossip/src/gossip.ts":{"bytes":25396,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/protocols","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts","kind":"import-statement","original":"./gossip-extension"}],"format":"esm"},"packages/core/mesh/teleport-extension-gossip/src/presence.ts":{"bytes":13151,"imports":[{"path":"@dxos/async","kind":"import-statement","external":true},{"path":"@dxos/context","kind":"import-statement","external":true},{"path":"@dxos/invariant","kind":"import-statement","external":true},{"path":"@dxos/keys","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"format":"esm"},"packages/core/mesh/teleport-extension-gossip/src/index.ts":{"bytes":673,"imports":[{"path":"packages/core/mesh/teleport-extension-gossip/src/gossip.ts","kind":"import-statement","original":"./gossip"},{"path":"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts","kind":"import-statement","original":"./gossip-extension"},{"path":"packages/core/mesh/teleport-extension-gossip/src/presence.ts","kind":"import-statement","original":"./presence"}],"format":"esm"}},"outputs":{"packages/core/mesh/teleport-extension-gossip/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":23097},"packages/core/mesh/teleport-extension-gossip/dist/lib/node/index.cjs":{"imports":[{"path":"@dxos/async","kind":"require-call","external":true},{"path":"@dxos/context","kind":"require-call","external":true},{"path":"@dxos/keys","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true},{"path":"@dxos/protocols","kind":"require-call","external":true},{"path":"@dxos/util","kind":"require-call","external":true},{"path":"@dxos/async","kind":"require-call","external":true},{"path":"@dxos/invariant","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true},{"path":"@dxos/protocols","kind":"require-call","external":true},{"path":"@dxos/rpc","kind":"require-call","external":true},{"path":"@dxos/async","kind":"require-call","external":true},{"path":"@dxos/context","kind":"require-call","external":true},{"path":"@dxos/invariant","kind":"require-call","external":true},{"path":"@dxos/keys","kind":"require-call","external":true},{"path":"@dxos/log","kind":"require-call","external":true},{"path":"@dxos/util","kind":"require-call","external":true}],"exports":[],"entryPoint":"packages/core/mesh/teleport-extension-gossip/src/index.ts","inputs":{"packages/core/mesh/teleport-extension-gossip/src/index.ts":{"bytesInOutput":187},"packages/core/mesh/teleport-extension-gossip/src/gossip.ts":{"bytesInOutput":7098},"packages/core/mesh/teleport-extension-gossip/src/gossip-extension.ts":{"bytesInOutput":2844},"packages/core/mesh/teleport-extension-gossip/src/presence.ts":{"bytesInOutput":3224}},"bytes":14644}}}
|
|
@@ -22,6 +22,8 @@ export declare class Gossip {
|
|
|
22
22
|
private readonly _toClear;
|
|
23
23
|
private readonly _connections;
|
|
24
24
|
readonly connectionClosed: Event<PublicKey>;
|
|
25
|
+
private _yjs_timeout;
|
|
26
|
+
private _yjs_timeout_index;
|
|
25
27
|
constructor(_params: GossipParams);
|
|
26
28
|
get localPeerId(): PublicKey;
|
|
27
29
|
open(): Promise<void>;
|
|
@@ -37,5 +39,8 @@ export declare class Gossip {
|
|
|
37
39
|
private _callListeners;
|
|
38
40
|
private _propagateAnnounce;
|
|
39
41
|
private _performGc;
|
|
42
|
+
private _addYjsTimeout;
|
|
43
|
+
private _oldestYjsTimeoutInWindow;
|
|
44
|
+
private _sendAnnounceWithTimeoutTracking;
|
|
40
45
|
}
|
|
41
46
|
//# sourceMappingURL=gossip.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gossip.d.ts","sourceRoot":"","sources":["../../../src/gossip.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,EAAwB,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAGrF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,SAAS,CAAC;CACxB,CAAC;
|
|
1
|
+
{"version":3,"file":"gossip.d.ts","sourceRoot":"","sources":["../../../src/gossip.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,KAAK,EAAwB,MAAM,aAAa,CAAC;AAExE,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,iDAAiD,CAAC;AAGrF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,EAAE,SAAS,CAAC;CACxB,CAAC;AASF;;;;;GAKG;AACH,qBAAa,MAAM;IAyBL,OAAO,CAAC,QAAQ,CAAC,OAAO;IAxBpC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAIlB;IAEH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA4D;IAEvF,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA6C;IAE/E;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6C;IAGtE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA8D;IAE3F,SAAgB,gBAAgB,mBAA0B;IAG1D,OAAO,CAAC,YAAY,CAAoC;IACxD,OAAO,CAAC,kBAAkB,CAAK;gBAEF,OAAO,EAAE,YAAY;IAElD,IAAI,WAAW,cAEd;IAEK,IAAI;IAWJ,KAAK;IAIX,cAAc;IAId,eAAe,CAAC,EAAE,YAAY,EAAE,EAAE;QAAE,YAAY,EAAE,SAAS,CAAA;KAAE,GAAG,eAAe;IAmC/E,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG;IA4BzC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI;;;IAalE,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,UAAU;IAiBlB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,yBAAyB;IASjC,OAAO,CAAC,gCAAgC;CASzC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/teleport-extension-gossip",
|
|
3
|
-
"version": "0.3.7
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Gossip extension for teleport.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,17 +16,17 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@dxos/async": "0.3.7
|
|
20
|
-
"@dxos/
|
|
21
|
-
"@dxos/
|
|
22
|
-
"@dxos/invariant": "0.3.7
|
|
23
|
-
"@dxos/keys": "0.3.7
|
|
24
|
-
"@dxos/log": "0.3.7
|
|
25
|
-
"@dxos/node-std": "0.3.7
|
|
26
|
-
"@dxos/protocols": "0.3.7
|
|
27
|
-
"@dxos/rpc": "0.3.7
|
|
28
|
-
"@dxos/
|
|
29
|
-
"@dxos/
|
|
19
|
+
"@dxos/async": "0.3.7",
|
|
20
|
+
"@dxos/codec-protobuf": "0.3.7",
|
|
21
|
+
"@dxos/context": "0.3.7",
|
|
22
|
+
"@dxos/invariant": "0.3.7",
|
|
23
|
+
"@dxos/keys": "0.3.7",
|
|
24
|
+
"@dxos/log": "0.3.7",
|
|
25
|
+
"@dxos/node-std": "0.3.7",
|
|
26
|
+
"@dxos/protocols": "0.3.7",
|
|
27
|
+
"@dxos/rpc": "0.3.7",
|
|
28
|
+
"@dxos/teleport": "0.3.7",
|
|
29
|
+
"@dxos/util": "0.3.7"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "^5.2.2"
|
package/src/gossip.ts
CHANGED
|
@@ -18,6 +18,11 @@ export type GossipParams = {
|
|
|
18
18
|
|
|
19
19
|
const RECEIVED_MESSAGES_GC_INTERVAL = 120_000;
|
|
20
20
|
|
|
21
|
+
const YJS_CHANNEL_PREFIX = 'user-channel/yjs.awareness';
|
|
22
|
+
const YJS_TIMEOUT_THRESHOLD = 20;
|
|
23
|
+
const YJS_TIMEOUT_WINDOW = 1000 * 30;
|
|
24
|
+
const MAX_CTX_TASKS = 50;
|
|
25
|
+
|
|
21
26
|
/**
|
|
22
27
|
* Gossip extensions manager.
|
|
23
28
|
* Keeps track of all peers that are connected to the local peer.
|
|
@@ -45,6 +50,10 @@ export class Gossip {
|
|
|
45
50
|
|
|
46
51
|
public readonly connectionClosed = new Event<PublicKey>();
|
|
47
52
|
|
|
53
|
+
// ringbuffer for yjs timeouts
|
|
54
|
+
private _yjs_timeout = new Array(YJS_TIMEOUT_THRESHOLD);
|
|
55
|
+
private _yjs_timeout_index = 0;
|
|
56
|
+
|
|
48
57
|
constructor(private readonly _params: GossipParams) {}
|
|
49
58
|
|
|
50
59
|
get localPeerId() {
|
|
@@ -78,6 +87,14 @@ export class Gossip {
|
|
|
78
87
|
}
|
|
79
88
|
this._receivedMessages.add(message.messageId);
|
|
80
89
|
this._callListeners(message);
|
|
90
|
+
if (message.channelId.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {
|
|
91
|
+
log('skipping propagating YJS gossip message due to timeouts');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (this._ctx.disposeCallbacksLength > MAX_CTX_TASKS) {
|
|
95
|
+
log('skipping propagating YJS gossip message due to exessive tasks');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
81
98
|
scheduleTask(this._ctx, async () => {
|
|
82
99
|
await this._propagateAnnounce(message);
|
|
83
100
|
});
|
|
@@ -98,29 +115,30 @@ export class Gossip {
|
|
|
98
115
|
}
|
|
99
116
|
|
|
100
117
|
postMessage(channel: string, payload: any) {
|
|
118
|
+
if (channel.startsWith(YJS_CHANNEL_PREFIX) && this._oldestYjsTimeoutInWindow()) {
|
|
119
|
+
log('skipping YJS gossip message due to timeouts');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
101
122
|
for (const extension of this._connections.values()) {
|
|
102
|
-
|
|
103
|
-
.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
log.catch(err);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
123
|
+
this._sendAnnounceWithTimeoutTracking(extension, {
|
|
124
|
+
peerId: this._params.localPeerId,
|
|
125
|
+
messageId: PublicKey.random(),
|
|
126
|
+
channelId: channel,
|
|
127
|
+
timestamp: new Date(),
|
|
128
|
+
payload,
|
|
129
|
+
}).catch(async (err) => {
|
|
130
|
+
if (err instanceof RpcClosedError) {
|
|
131
|
+
log('sendAnnounce failed because of RpcClosedError', { err });
|
|
132
|
+
} else if (
|
|
133
|
+
err instanceof TimeoutError ||
|
|
134
|
+
err.constructor.name === 'TimeoutError' ||
|
|
135
|
+
err.message.startsWith('Timeout')
|
|
136
|
+
) {
|
|
137
|
+
log('sendAnnounce failed because of TimeoutError', { err });
|
|
138
|
+
} else {
|
|
139
|
+
log.catch(err);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
124
142
|
}
|
|
125
143
|
}
|
|
126
144
|
|
|
@@ -151,7 +169,7 @@ export class Gossip {
|
|
|
151
169
|
if (this._params.localPeerId.equals(message.peerId) || remotePeerId.equals(message.peerId)) {
|
|
152
170
|
return;
|
|
153
171
|
}
|
|
154
|
-
return
|
|
172
|
+
return this._sendAnnounceWithTimeoutTracking(extension, message).catch((err) => log(err));
|
|
155
173
|
}),
|
|
156
174
|
);
|
|
157
175
|
}
|
|
@@ -172,4 +190,28 @@ export class Gossip {
|
|
|
172
190
|
log.warn('GC took too long', { elapsed });
|
|
173
191
|
}
|
|
174
192
|
}
|
|
193
|
+
|
|
194
|
+
private _addYjsTimeout() {
|
|
195
|
+
this._yjs_timeout[this._yjs_timeout_index] = Date.now();
|
|
196
|
+
this._yjs_timeout_index = (this._yjs_timeout_index + 1) % YJS_TIMEOUT_THRESHOLD;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private _oldestYjsTimeoutInWindow(): boolean {
|
|
200
|
+
const lastTS = this._yjs_timeout[(this._yjs_timeout_index + YJS_TIMEOUT_THRESHOLD - 1) % YJS_TIMEOUT_THRESHOLD];
|
|
201
|
+
if (!lastTS) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return Date.now() - lastTS < YJS_TIMEOUT_WINDOW;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private _sendAnnounceWithTimeoutTracking(extension: GossipExtension, message: GossipMessage) {
|
|
209
|
+
return extension.sendAnnounce(message).catch((err) => {
|
|
210
|
+
if (err instanceof TimeoutError || err.constructor.name === 'TimeoutError' || err.message.startsWith('Timeout')) {
|
|
211
|
+
if (message.channelId.startsWith(YJS_CHANNEL_PREFIX)) {
|
|
212
|
+
this._addYjsTimeout();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
}
|
|
175
217
|
}
|