@peers-app/peers-device 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/chunk-download-manager.js +3 -3
  2. package/dist/chunk-download-manager.js.map +1 -1
  3. package/dist/connection-manager/connection-manager.d.ts +5 -19
  4. package/dist/connection-manager/connection-manager.js +25 -466
  5. package/dist/connection-manager/connection-manager.js.map +1 -1
  6. package/dist/connection-manager/connection-manager.test.js +195 -0
  7. package/dist/connection-manager/connection-manager.test.js.map +1 -1
  8. package/dist/connection-manager/device-messages.d.ts +51 -0
  9. package/dist/connection-manager/device-messages.js +516 -0
  10. package/dist/connection-manager/device-messages.js.map +1 -0
  11. package/dist/connection-manager/hops-map.d.ts +38 -0
  12. package/dist/connection-manager/hops-map.js +149 -0
  13. package/dist/connection-manager/hops-map.js.map +1 -0
  14. package/dist/connection-manager/hops-map.test.d.ts +1 -0
  15. package/dist/connection-manager/hops-map.test.js +225 -0
  16. package/dist/connection-manager/hops-map.test.js.map +1 -0
  17. package/dist/connection-manager/network-manager.js +1 -1
  18. package/dist/connection-manager/network-manager.js.map +1 -1
  19. package/dist/local.data-source.d.ts +12 -0
  20. package/dist/local.data-source.js +37 -3
  21. package/dist/local.data-source.js.map +1 -1
  22. package/dist/sync-group.js +5 -9
  23. package/dist/sync-group.js.map +1 -1
  24. package/dist/tracked-data-source.d.ts +5 -0
  25. package/dist/tracked-data-source.js +141 -33
  26. package/dist/tracked-data-source.js.map +1 -1
  27. package/dist/tracked-data-source.test.js +159 -0
  28. package/dist/tracked-data-source.test.js.map +1 -1
  29. package/package.json +3 -3
@@ -0,0 +1,516 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DeviceMessages = exports.TTL0 = void 0;
4
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
5
+ const lodash_1 = require("lodash");
6
+ const hops_map_1 = require("./hops-map");
7
+ exports.TTL0 = "TTL0";
8
+ const ACK_DUP = "ACK_DUP";
9
+ class DeviceMessages {
10
+ deps;
11
+ MAX_CONNECTIONS_TO_FORWARD_MESSAGES_TO = 6;
12
+ MAX_DEVICE_MESSAGE_SIZE = 64 * 1024; // 64 KB
13
+ deviceMessagesSeen = {};
14
+ deviceAliases = new Map();
15
+ deviceAliasesExpires = new Map();
16
+ deviceMessageHandlers = new Map();
17
+ userConnectCodeOfferSubscription;
18
+ userConnectCodeAnswerSubscription;
19
+ cleanOldDeviceMessagesSeenInterval;
20
+ cleanupExpiredAliasesInterval;
21
+ hopsMap = new hops_map_1.HopsMap();
22
+ constructor(deps) {
23
+ this.deps = deps;
24
+ this.cleanOldDeviceMessagesSeenInterval = setInterval(() => {
25
+ this.cleanOldDeviceMessagesSeen();
26
+ }, 60 * 60 * 1000); // every hour
27
+ this.cleanupExpiredAliasesInterval = setInterval(() => {
28
+ this.cleanupExpiredAliases();
29
+ }, 60_0000);
30
+ // setup user connect code subscriptions
31
+ this.userConnectCodeOfferSubscription = peers_sdk_1.userConnectCodeOffer.subscribe(() => {
32
+ if ((0, peers_sdk_1.userConnectCodeOffer)()) {
33
+ const { alias } = (0, peers_sdk_1.parseConnectionCode)((0, peers_sdk_1.userConnectCodeOffer)());
34
+ this.sendDeviceMessage({
35
+ toDeviceId: 'device-alias',
36
+ dataContextId: '',
37
+ payload: alias,
38
+ ttl: 3,
39
+ });
40
+ }
41
+ });
42
+ this.userConnectCodeAnswerSubscription = peers_sdk_1.userConnectCodeAnswer.subscribe(() => {
43
+ if ((0, peers_sdk_1.userConnectCodeAnswer)()) {
44
+ this.userConnectOffer((0, peers_sdk_1.userConnectCodeAnswer)());
45
+ }
46
+ });
47
+ }
48
+ /**
49
+ * Register a handler for device messages with a specific payload type
50
+ * @param deviceMessageType The payload type to handle (e.g., 'webrtc-offer')
51
+ * @param handler Function to process the message and return a response
52
+ * @returns Unsubscribe function
53
+ */
54
+ on(deviceMessageType, handler) {
55
+ if (this.deviceMessageHandlers.has(deviceMessageType)) {
56
+ throw new Error(`Handler already registered for device message type: ${deviceMessageType}`);
57
+ }
58
+ this.deviceMessageHandlers.set(deviceMessageType, handler);
59
+ return () => {
60
+ this.deviceMessageHandlers.delete(deviceMessageType);
61
+ };
62
+ }
63
+ async userConnectOffer(userConnectCode) {
64
+ const { alias, secret } = (0, peers_sdk_1.parseConnectionCode)(userConnectCode);
65
+ const me = await (0, peers_sdk_1.getMe)();
66
+ const myUserConnectInfo = {
67
+ userId: me.userId,
68
+ name: me.name,
69
+ publicKey: me.publicKey,
70
+ publicBoxKey: me.publicBoxKey,
71
+ deviceId: this.deps.userContext.deviceId(),
72
+ };
73
+ const response = await this.sendDeviceMessage({
74
+ toDeviceId: alias,
75
+ dataContextId: 'user-connect',
76
+ payload: (0, peers_sdk_1.encryptWithSecret)(myUserConnectInfo, secret),
77
+ });
78
+ if (response === exports.TTL0) {
79
+ (0, peers_sdk_1.userConnectStatus)('Error: Could not find device, check connection code and try again');
80
+ }
81
+ else {
82
+ const remoteUserConnectInfo = (0, peers_sdk_1.decryptWithSecret)(response.payload, secret);
83
+ const usersTable = (0, peers_sdk_1.Users)(this.deps.userContext.userDataContext);
84
+ await usersTable.save({
85
+ userId: remoteUserConnectInfo.userId,
86
+ name: remoteUserConnectInfo.name || remoteUserConnectInfo.userId,
87
+ publicKey: remoteUserConnectInfo.publicKey,
88
+ publicBoxKey: remoteUserConnectInfo.publicBoxKey,
89
+ }, { restoreIfDeleted: true, weakInsert: true });
90
+ const devicesTable = (0, peers_sdk_1.Devices)(this.deps.userContext.userDataContext);
91
+ await devicesTable.save({
92
+ deviceId: remoteUserConnectInfo.deviceId,
93
+ userId: remoteUserConnectInfo.userId,
94
+ firstSeen: new Date(),
95
+ lastSeen: new Date(),
96
+ trustLevel: peers_sdk_1.TrustLevel.NewDevice,
97
+ }, { restoreIfDeleted: true, weakInsert: true });
98
+ this.deps.onUserConnectExchangeFinished(remoteUserConnectInfo.deviceId);
99
+ (0, peers_sdk_1.userConnectStatus)(remoteUserConnectInfo.userId);
100
+ }
101
+ }
102
+ async userConnectAnswer(message) {
103
+ try {
104
+ const userConnectCode = (0, peers_sdk_1.userConnectCodeOffer)();
105
+ if (!userConnectCode) {
106
+ throw new Error('Device is not waiting for a user connection, check connection code and try again');
107
+ }
108
+ const parsedCode = (0, peers_sdk_1.parseConnectionCode)(userConnectCode);
109
+ const remoteUserConnectInfo = (0, peers_sdk_1.decryptWithSecret)(message.payload, parsedCode.secret);
110
+ const usersTable = (0, peers_sdk_1.Users)(this.deps.userContext.userDataContext);
111
+ await usersTable.save({
112
+ userId: remoteUserConnectInfo.userId,
113
+ name: remoteUserConnectInfo.name || remoteUserConnectInfo.userId,
114
+ publicKey: remoteUserConnectInfo.publicKey,
115
+ publicBoxKey: remoteUserConnectInfo.publicBoxKey,
116
+ }, { restoreIfDeleted: true, weakInsert: true });
117
+ const devicesTable = (0, peers_sdk_1.Devices)(this.deps.userContext.userDataContext);
118
+ await devicesTable.save({
119
+ deviceId: remoteUserConnectInfo.deviceId,
120
+ userId: remoteUserConnectInfo.userId,
121
+ firstSeen: new Date(),
122
+ lastSeen: new Date(),
123
+ trustLevel: peers_sdk_1.TrustLevel.NewDevice,
124
+ }, { restoreIfDeleted: true, weakInsert: true });
125
+ (0, peers_sdk_1.userConnectStatus)(remoteUserConnectInfo.userId);
126
+ const me = await (0, peers_sdk_1.getMe)();
127
+ const myUserConnectInfo = {
128
+ userId: me.userId,
129
+ name: me.name,
130
+ publicKey: me.publicKey,
131
+ publicBoxKey: me.publicBoxKey,
132
+ deviceId: this.deps.userContext.deviceId(),
133
+ };
134
+ return {
135
+ statusCode: 200,
136
+ payload: (0, peers_sdk_1.encryptWithSecret)(myUserConnectInfo, parsedCode.secret),
137
+ };
138
+ }
139
+ catch (err) {
140
+ return {
141
+ isError: true,
142
+ statusCode: 400,
143
+ statusMessage: `Error handling user-connect device message: ${err}`
144
+ };
145
+ }
146
+ }
147
+ cleanOldDeviceMessagesSeen() {
148
+ // clear out deviceMessageIds older than 24 hours
149
+ const twentyFourHours = 24 * 60 * 60 * 1000;
150
+ for (const deviceMessageId of Object.keys(this.deviceMessagesSeen)) {
151
+ const time = (0, peers_sdk_1.idTime)(deviceMessageId);
152
+ if (Date.now() - time > twentyFourHours) {
153
+ delete this.deviceMessagesSeen[deviceMessageId];
154
+ }
155
+ }
156
+ // cleanup stale hop observations
157
+ this.hopsMap.cleanupStaleEntries();
158
+ }
159
+ cleanupExpiredAliases() {
160
+ const now = Date.now();
161
+ for (const [alias, expires] of this.deviceAliasesExpires) {
162
+ if (expires < now) {
163
+ this.deviceAliases.delete(alias);
164
+ this.deviceAliasesExpires.delete(alias);
165
+ }
166
+ }
167
+ }
168
+ async secureDeviceMessagePayload(message) {
169
+ let toPublicBoxKey = '';
170
+ // try to lookup public box key from device
171
+ if (!toPublicBoxKey && message.toDeviceId) {
172
+ const device = await (0, peers_sdk_1.Devices)(this.deps.userContext.userDataContext).get(message.toDeviceId);
173
+ if (device) {
174
+ const user = await (0, peers_sdk_1.getUserById)(device.userId, { userContext: this.deps.userContext });
175
+ if (user) {
176
+ toPublicBoxKey = user.publicBoxKey;
177
+ }
178
+ }
179
+ }
180
+ // try to lookup public box key by user (it's not uncommon to send a message to user's personal data context)
181
+ if (!toPublicBoxKey) {
182
+ if (message.dataContextId === this.deps.userContext.userId) {
183
+ toPublicBoxKey = this.deps.localDevice.publicBoxKey;
184
+ }
185
+ else {
186
+ const user = await (0, peers_sdk_1.getUserById)(message.dataContextId, { userContext: this.deps.userContext });
187
+ if (user) {
188
+ toPublicBoxKey = user.publicBoxKey;
189
+ }
190
+ }
191
+ }
192
+ // try to lookup public box key by group
193
+ if (!toPublicBoxKey) {
194
+ const group = await (0, peers_sdk_1.Groups)(this.deps.userContext.userDataContext).get(message.dataContextId);
195
+ if (group) {
196
+ toPublicBoxKey = group.publicBoxKey;
197
+ }
198
+ }
199
+ // sign and box (or just sign if we couldn't find a box key)
200
+ if (toPublicBoxKey) {
201
+ message.payload = this.deps.localDevice.signAndBoxDataForKey(message.payload, toPublicBoxKey);
202
+ }
203
+ else {
204
+ console.warn(`WARNING! Could not establish a publicBoxKey to encrypt message with, sending as signed plain text`, message.payload);
205
+ message.payload = this.deps.localDevice.signObjectWithSecretKey(message.payload);
206
+ }
207
+ }
208
+ async validateDeviceMessagePayload(message) {
209
+ if (!message.payload)
210
+ return;
211
+ const fromDevice = await (0, peers_sdk_1.Devices)(this.deps.userContext.userDataContext).get(message.fromDeviceId);
212
+ const fromUser = fromDevice?.userId && await (0, peers_sdk_1.getUserById)(fromDevice.userId, { userContext: this.deps.userContext }) || undefined;
213
+ if ((0, peers_sdk_1.isBoxedData)(message.payload)) {
214
+ try {
215
+ const fromBoxKey = message.payload.fromPublicKey;
216
+ if (fromUser && fromUser.publicBoxKey !== fromBoxKey) {
217
+ throw new Error(`Box key used does not match sending device's user`);
218
+ }
219
+ message.payload = this.deps.localDevice.openBoxedAndSignedData(message.payload);
220
+ }
221
+ catch (err) {
222
+ // if I couldn't open it with mine, maybe it's boxed with the group's secret key
223
+ try {
224
+ const groupSecretKey = await this.deps.getGroupSecretKey(message.dataContextId);
225
+ message.payload = (0, peers_sdk_1.openBoxWithSecretKey)(message.payload, groupSecretKey);
226
+ }
227
+ catch (err) {
228
+ throw new Error(`Could not open boxed data`, { cause: err });
229
+ }
230
+ }
231
+ }
232
+ else {
233
+ try {
234
+ const fromPublicKey = message.payload.publicKey;
235
+ if (fromUser && fromUser.publicKey !== fromPublicKey) {
236
+ throw new Error(`public key of signature does not match sending device's user`);
237
+ }
238
+ (0, peers_sdk_1.isObjectSignatureValid)(message.payload);
239
+ }
240
+ catch (err) {
241
+ throw new Error(`Could not validate signature`, { cause: err });
242
+ }
243
+ }
244
+ }
245
+ async sendDeviceMessage(partialMessage) {
246
+ const userContext = this.deps.userContext;
247
+ const allConnections = this.deps.getAllConnections();
248
+ const connectionStates = this.deps.getConnectionStates();
249
+ const syncGroups = this.deps.getSyncGroups();
250
+ // Fill in defaults for optional fields
251
+ const message = {
252
+ deviceMessageId: partialMessage.deviceMessageId || (0, peers_sdk_1.newid)(),
253
+ fromDeviceId: partialMessage.fromDeviceId || userContext.deviceId(),
254
+ toDeviceId: partialMessage.toDeviceId,
255
+ dataContextId: partialMessage.dataContextId,
256
+ ttl: partialMessage.ttl ?? 5,
257
+ payload: partialMessage.payload,
258
+ hops: partialMessage.hops || [],
259
+ };
260
+ // don't process this again if I've already seen it
261
+ if (this.deviceMessagesSeen[message.deviceMessageId]) {
262
+ return ACK_DUP;
263
+ }
264
+ this.deviceMessagesSeen[message.deviceMessageId] = true;
265
+ // device-alias special logic
266
+ {
267
+ if (message.toDeviceId === 'device-alias' && message.payload) {
268
+ const alias = message.payload;
269
+ if (typeof alias !== 'string' || alias.length < 4 || alias.length > 12) {
270
+ return {
271
+ isError: true,
272
+ statusCode: 400,
273
+ statusMessage: 'Invalid alias',
274
+ };
275
+ }
276
+ if (this.deviceAliases.has(alias) && this.deviceAliases.get(alias) !== message.fromDeviceId) {
277
+ return {
278
+ isError: true,
279
+ statusCode: 400,
280
+ statusMessage: 'Alias already in use by another device',
281
+ };
282
+ }
283
+ this.deviceAliases.set(alias, message.fromDeviceId);
284
+ this.deviceAliasesExpires.set(alias, Date.now() + 600_000); // 10 minutes
285
+ message.ttl--;
286
+ if (message.ttl <= 0) {
287
+ return exports.TTL0;
288
+ }
289
+ // propagate the alias to all devices
290
+ return this.forwardDeviceMessageAndReturnFirstResponse(message, Object.values(allConnections));
291
+ }
292
+ const knownAlias = this.deviceAliases.get(message.toDeviceId);
293
+ if (knownAlias) {
294
+ message.toDeviceId = knownAlias;
295
+ }
296
+ // user-connect special logic
297
+ if (message.dataContextId === 'user-connect' && message.toDeviceId === userContext.deviceId()) {
298
+ return this.userConnectAnswer(message);
299
+ }
300
+ }
301
+ // if this is the source device, assume we need to secure the message
302
+ if (message.hops.length === 0 && message.payload && message.dataContextId !== 'user-connect') {
303
+ try {
304
+ await this.secureDeviceMessagePayload(message);
305
+ }
306
+ catch (err) {
307
+ throw new Error(`Error while securing device message payload`, { cause: err });
308
+ }
309
+ }
310
+ // add this device to the list of hops
311
+ message.hops = [...message.hops, userContext.deviceId()];
312
+ // Record hop observations for routing intelligence (do this early so all devices learn the path)
313
+ this.hopsMap.addHopsObservation(message.hops);
314
+ const dataContextIds = [userContext.userId, ...userContext.groupIds()];
315
+ // Device Discovery - if there is no deviceId, we assume they are just looking for any device that supports the given dataContextId
316
+ // TODO revisit this to make sure we're not creating a bunch of unnecessary noise by using an empty toDeviceId
317
+ if (message.toDeviceId === '' && dataContextIds.includes(message.dataContextId) && message.fromDeviceId !== userContext.deviceId() && !message.payload) {
318
+ return {
319
+ statusCode: 200,
320
+ message: "OK",
321
+ hops: message.hops
322
+ };
323
+ }
324
+ // if it's meant for this device then process it
325
+ if (userContext.deviceId() === message.toDeviceId) {
326
+ try {
327
+ await this.validateDeviceMessagePayload(message);
328
+ }
329
+ catch (err) {
330
+ return {
331
+ isError: true,
332
+ statusCode: 400,
333
+ statusMessage: `Error while validating device message payload: ${err}`
334
+ };
335
+ }
336
+ // Check for device-level handler first (e.g., webrtc-signal)
337
+ if (message.payload?.type && this.deviceMessageHandlers.has(message.payload.type)) {
338
+ const handler = this.deviceMessageHandlers.get(message.payload.type);
339
+ return handler(message);
340
+ }
341
+ // check if this user has joined this group, if not return error
342
+ if (!dataContextIds.includes(message.dataContextId)) {
343
+ return {
344
+ isError: true,
345
+ statusCode: 400,
346
+ statusMessage: 'Device does not support specified dataContext: ' + message.dataContextId
347
+ };
348
+ }
349
+ const dataContext = userContext.getDataContext(message.dataContextId);
350
+ const syncDevice = this.deps.getPeerGroupDevice(dataContext);
351
+ // give it to groupDevice to process it
352
+ return syncDevice.sendDeviceMessage(message);
353
+ }
354
+ // decrement ttl
355
+ message.ttl--;
356
+ if (message.ttl <= 0) {
357
+ return exports.TTL0;
358
+ }
359
+ // TODO maybe use hash chain to validate hopes are append only
360
+ // try to forward to the exact device
361
+ if (allConnections[message.toDeviceId]) {
362
+ const conn = allConnections[message.toDeviceId];
363
+ return conn.emit("sendDeviceMessage", message);
364
+ }
365
+ // if we don't have a direct connection to the device and the ttl is 1, we know it'll fail so short circuit
366
+ if (message.ttl <= 1) {
367
+ return exports.TTL0;
368
+ }
369
+ // before flooding the network make sure the message size isn't too big and TTL is reasonable
370
+ if (JSON.stringify(message).length > this.MAX_DEVICE_MESSAGE_SIZE) {
371
+ return {
372
+ isError: true,
373
+ statusCode: 413,
374
+ statusMessage: "Payload Too Large"
375
+ };
376
+ }
377
+ if (message.ttl > 4) {
378
+ throw new Error('Max TTL is 5 but message has TTL of ' + (message.ttl + 1));
379
+ }
380
+ // start with connections to all devices that are not in the current list of hops
381
+ let connectionsToTry = Array.from(connectionStates.keys())
382
+ .filter(c => !message.hops.includes(c.remoteDeviceInfo.deviceId));
383
+ const alreadySeenDeviceIds = (0, lodash_1.uniq)([userContext.deviceId(), ...Object.keys(allConnections), ...message.hops]);
384
+ // try to find an indirect connection from cached networkInfos (connection to a device that is then connected to target device)
385
+ for (const syncGroup of Object.values(syncGroups)) {
386
+ const pathToDevice = syncGroup.getPathToDevice(message.toDeviceId);
387
+ if (pathToDevice.throughDeviceIds.length) {
388
+ const indirectConnections = connectionsToTry.filter(c => pathToDevice.throughDeviceIds.includes(c.remoteDeviceInfo.deviceId))
389
+ // before slicing, sort by connections by the ones with the most indirect connections to other devices
390
+ // QUESTION? do we really want to purposely send to "busy" devices or would it be better to send to less busy devices that just happen to have the right path?
391
+ .sort((a, b) => {
392
+ const aRemoteDeviceIds = this.getConnectionIndirectRemoteDeviceIds(a, alreadySeenDeviceIds);
393
+ const bRemoteDeviceIds = this.getConnectionIndirectRemoteDeviceIds(b, alreadySeenDeviceIds);
394
+ return bRemoteDeviceIds.length - aRemoteDeviceIds.length;
395
+ })
396
+ .slice(0, 2); // in the unlikely event we have many indirect connections, trim them down
397
+ const response = await this.forwardDeviceMessageAndReturnFirstResponse(message, indirectConnections);
398
+ if ((response !== ACK_DUP && response !== exports.TTL0)) {
399
+ return response;
400
+ }
401
+ else if (indirectConnections.length > 1) {
402
+ // if we had multiple indirect connections and they all returned TTL0 (which may have stemmed from ACK_DUP),
403
+ // don't continue trying other connections, it's very likely the message has already been delivered
404
+ return response;
405
+ }
406
+ connectionsToTry = connectionsToTry.filter(c => !indirectConnections.includes(c));
407
+ }
408
+ }
409
+ // Try hop-derived routing using previously observed message paths
410
+ const myConnectedDeviceIds = connectionsToTry.map(c => c.remoteDeviceInfo.deviceId);
411
+ const hopCandidates = this.hopsMap.getDevicesLikelyToReach(message.toDeviceId, myConnectedDeviceIds, alreadySeenDeviceIds);
412
+ if (hopCandidates.length) {
413
+ const hopConnections = hopCandidates
414
+ .map(deviceId => connectionsToTry.find(c => c.remoteDeviceInfo.deviceId === deviceId))
415
+ .filter(Boolean);
416
+ if (hopConnections.length) {
417
+ const response = await this.forwardDeviceMessageAndReturnFirstResponse(message, hopConnections.slice(0, 2));
418
+ if (response !== ACK_DUP && response !== exports.TTL0) {
419
+ return response;
420
+ }
421
+ connectionsToTry = connectionsToTry.filter(c => !hopConnections.includes(c));
422
+ }
423
+ }
424
+ // if we don't have a lot of connections just forward to them all
425
+ if (connectionsToTry.length <= this.MAX_CONNECTIONS_TO_FORWARD_MESSAGES_TO) {
426
+ return this.forwardDeviceMessageAndReturnFirstResponse(message, connectionsToTry);
427
+ }
428
+ // WARNING! at this point, we don't have a known path but we have a lot of connections
429
+ // we could end up choking this device or its network if we're not careful and it might time out anyway
430
+ // we want to find a good subset of connections that have the best chance of being connected to the device
431
+ // remove connections that don't have any new paths
432
+ // i.e. trim off this device's connections to leaf network devices, no point forwarding to them
433
+ connectionsToTry = connectionsToTry.filter(c => {
434
+ const remoteDeviceIds = this.getConnectionIndirectRemoteDeviceIds(c, alreadySeenDeviceIds);
435
+ return remoteDeviceIds.length > 0;
436
+ });
437
+ if (connectionsToTry.length <= this.MAX_CONNECTIONS_TO_FORWARD_MESSAGES_TO) {
438
+ return this.forwardDeviceMessageAndReturnFirstResponse(message, connectionsToTry);
439
+ }
440
+ // sort the connections by how many new paths they have (highest first) and whether they're in the target group
441
+ // example: if we allow 4 connections to be forwarded to, and we have 2 connections that are in the target group,
442
+ // those 2 will be prioritized and then the next 2 connections will be the ones with the highest number of new paths
443
+ // if we have more than 4 connections in the target group, we'll take the 4 with the highest number of new paths
444
+ const messageDataContextSyncGroup = syncGroups[message.dataContextId];
445
+ const groupConnectionDeviceIds = new Set((messageDataContextSyncGroup?.getConnections() || []).map(dc => dc.deviceId));
446
+ // TODO: change this to re-sort after each selection to account for overlap in indirect connections
447
+ connectionsToTry = connectionsToTry.sort((a, b) => {
448
+ // prioritize connections that are in the target group
449
+ const aIsGroupConnection = groupConnectionDeviceIds.has(a.remoteDeviceInfo.deviceId) ? 1 : 0;
450
+ const bIsGroupConnection = groupConnectionDeviceIds.has(b.remoteDeviceInfo.deviceId) ? 1 : 0;
451
+ if (aIsGroupConnection !== bIsGroupConnection) {
452
+ return bIsGroupConnection - aIsGroupConnection;
453
+ }
454
+ // for each connection, get the number of indirect connections it has to devices we haven't already seen
455
+ const aRemoteDeviceIds = this.getConnectionIndirectRemoteDeviceIds(a, alreadySeenDeviceIds);
456
+ const bRemoteDeviceIds = this.getConnectionIndirectRemoteDeviceIds(b, alreadySeenDeviceIds);
457
+ return bRemoteDeviceIds.length - aRemoteDeviceIds.length;
458
+ });
459
+ // finally, forward to the top connections
460
+ return this.forwardDeviceMessageAndReturnFirstResponse(message, connectionsToTry.slice(0, this.MAX_CONNECTIONS_TO_FORWARD_MESSAGES_TO));
461
+ }
462
+ forwardDeviceMessageAndReturnFirstResponse(message, connectionsToForwardTo) {
463
+ return new Promise((resolve) => {
464
+ const pendingDevices = new Set(connectionsToForwardTo.map(c => c.remoteDeviceInfo.deviceId));
465
+ if (pendingDevices.size === 0) {
466
+ return resolve(exports.TTL0);
467
+ }
468
+ let responseSent = false;
469
+ function checkResponse(deviceId, response) {
470
+ if (responseSent) {
471
+ return;
472
+ }
473
+ pendingDevices.delete(deviceId);
474
+ if (response !== ACK_DUP && response !== exports.TTL0) {
475
+ responseSent = true;
476
+ resolve(response);
477
+ }
478
+ if (pendingDevices.size === 0) {
479
+ responseSent = true;
480
+ resolve(exports.TTL0);
481
+ }
482
+ }
483
+ for (const conn of connectionsToForwardTo) {
484
+ const deviceId = conn.remoteDeviceInfo.deviceId;
485
+ conn.emit("sendDeviceMessage", message)
486
+ .then(response => checkResponse(deviceId, response))
487
+ .catch(err => checkResponse(deviceId, err));
488
+ }
489
+ });
490
+ }
491
+ getConnectionIndirectRemoteDeviceIds(connection, excludeDeviceIds) {
492
+ const syncGroups = this.deps.getSyncGroups();
493
+ const remoteDeviceId = connection.remoteDeviceInfo.deviceId;
494
+ const allSyncGroups = Object.values(syncGroups);
495
+ const connectedIds = new Set();
496
+ for (const syncDevice of allSyncGroups) {
497
+ syncDevice.reportRemoteDeviceConnectedIds(remoteDeviceId, connectedIds);
498
+ }
499
+ if (excludeDeviceIds?.length) {
500
+ for (const excludeId of excludeDeviceIds) {
501
+ connectedIds.delete(excludeId);
502
+ }
503
+ }
504
+ // remove self which will always be there but we should never care about
505
+ connectedIds.delete(this.deps.userContext.deviceId());
506
+ return Array.from(connectedIds);
507
+ }
508
+ dispose() {
509
+ this.userConnectCodeOfferSubscription.dispose();
510
+ this.userConnectCodeAnswerSubscription.dispose();
511
+ clearInterval(this.cleanOldDeviceMessagesSeenInterval);
512
+ clearInterval(this.cleanupExpiredAliasesInterval);
513
+ }
514
+ }
515
+ exports.DeviceMessages = DeviceMessages;
516
+ //# sourceMappingURL=device-messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-messages.js","sourceRoot":"","sources":["../../src/connection-manager/device-messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * HopsMap tracks observed device connections based on message hop paths.
3
+ * This enables smarter message routing by learning the network topology
4
+ * from messages that pass through this device.
5
+ */
6
+ export declare class HopsMap {
7
+ private static readonly DEFAULT_MAX_AGE_MS;
8
+ private networkMap;
9
+ /**
10
+ * Record observed connections from message hops.
11
+ * For hops [A, B, C, D], records bidirectional connections: A-B, B-C, C-D
12
+ * @param hops Array of deviceIds representing the path a message has taken
13
+ */
14
+ addHopsObservation(hops: string[]): void;
15
+ /**
16
+ * Find which of the provided connected devices are best for reaching the target.
17
+ * Uses BFS to find shortest paths through the observed network topology.
18
+ *
19
+ * @param targetDeviceId The device we want to reach
20
+ * @param myConnectedDeviceIds Devices we have direct connections to (candidates to forward through)
21
+ * @param excludeDeviceIds DeviceIds to exclude from path-finding (e.g., already in message hops)
22
+ * @returns Array of deviceIds from myConnectedDeviceIds, sorted by best path (shortest, most recent)
23
+ */
24
+ getDevicesLikelyToReach(targetDeviceId: string, myConnectedDeviceIds: string[], excludeDeviceIds: string[]): string[];
25
+ /**
26
+ * Remove entries older than the specified threshold.
27
+ * @param maxAgeMs Maximum age in milliseconds (default: 30 minutes)
28
+ */
29
+ cleanupStaleEntries(maxAgeMs?: number): void;
30
+ /**
31
+ * Get the current size of the network map (for testing/debugging)
32
+ */
33
+ get size(): number;
34
+ /**
35
+ * Clear all entries (for testing)
36
+ */
37
+ clear(): void;
38
+ }