@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.
- package/dist/chunk-download-manager.js +3 -3
- package/dist/chunk-download-manager.js.map +1 -1
- package/dist/connection-manager/connection-manager.d.ts +5 -19
- package/dist/connection-manager/connection-manager.js +25 -466
- package/dist/connection-manager/connection-manager.js.map +1 -1
- package/dist/connection-manager/connection-manager.test.js +195 -0
- package/dist/connection-manager/connection-manager.test.js.map +1 -1
- package/dist/connection-manager/device-messages.d.ts +51 -0
- package/dist/connection-manager/device-messages.js +516 -0
- package/dist/connection-manager/device-messages.js.map +1 -0
- package/dist/connection-manager/hops-map.d.ts +38 -0
- package/dist/connection-manager/hops-map.js +149 -0
- package/dist/connection-manager/hops-map.js.map +1 -0
- package/dist/connection-manager/hops-map.test.d.ts +1 -0
- package/dist/connection-manager/hops-map.test.js +225 -0
- package/dist/connection-manager/hops-map.test.js.map +1 -0
- package/dist/connection-manager/network-manager.js +1 -1
- package/dist/connection-manager/network-manager.js.map +1 -1
- package/dist/local.data-source.d.ts +12 -0
- package/dist/local.data-source.js +37 -3
- package/dist/local.data-source.js.map +1 -1
- package/dist/sync-group.js +5 -9
- package/dist/sync-group.js.map +1 -1
- package/dist/tracked-data-source.d.ts +5 -0
- package/dist/tracked-data-source.js +141 -33
- package/dist/tracked-data-source.js.map +1 -1
- package/dist/tracked-data-source.test.js +159 -0
- package/dist/tracked-data-source.test.js.map +1 -1
- 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
|
+
}
|