@livedesk/hub 0.1.25 → 0.1.27
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/package.json +1 -1
- package/src/remote-hub.js +1176 -78
- package/src/server.js +159 -5
- package/src/transport/udp-hub-transport.js +139 -14
- package/src/transport/udp-rendezvous.js +6 -2
package/src/server.js
CHANGED
|
@@ -1303,7 +1303,7 @@ function isPrivateHubHostname(value) {
|
|
|
1303
1303
|
return !!match && Number(match[2]) >= 16 && Number(match[2]) <= 31;
|
|
1304
1304
|
}
|
|
1305
1305
|
|
|
1306
|
-
function isLoopbackAddress(value) {
|
|
1306
|
+
function isLoopbackAddress(value) {
|
|
1307
1307
|
const address = String(value || '').trim().toLowerCase();
|
|
1308
1308
|
return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1';
|
|
1309
1309
|
}
|
|
@@ -1449,6 +1449,31 @@ function normalizeDeviceIds(value) {
|
|
|
1449
1449
|
return ids;
|
|
1450
1450
|
}
|
|
1451
1451
|
|
|
1452
|
+
function requireLocalTransportDiagnosticRequest(req, res) {
|
|
1453
|
+
const forwardedFor = String(req.headers['x-forwarded-for'] || '').trim();
|
|
1454
|
+
const forwardedHost = String(req.headers['x-forwarded-host'] || '').trim();
|
|
1455
|
+
const browserOrigin = String(req.headers.origin || '').trim();
|
|
1456
|
+
const browserFetchSite = String(req.headers['sec-fetch-site'] || '').trim();
|
|
1457
|
+
if (!isLoopbackAddress(req.socket?.remoteAddress)
|
|
1458
|
+
|| !isLoopbackHostname(requestHostname(req))
|
|
1459
|
+
|| forwardedFor
|
|
1460
|
+
|| forwardedHost
|
|
1461
|
+
|| browserOrigin
|
|
1462
|
+
|| browserFetchSite) {
|
|
1463
|
+
res.status(403).json({ ok: false, error: 'transport-diagnostic-loopback-only' });
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
if (runtimeRole !== 'hub') {
|
|
1467
|
+
res.status(403).json({ ok: false, error: 'role-not-allowed' });
|
|
1468
|
+
return false;
|
|
1469
|
+
}
|
|
1470
|
+
return true;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function transportDiagnosticToken(req) {
|
|
1474
|
+
return String(req.headers['x-livedesk-diagnostic-token'] || '').trim();
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1452
1477
|
function rejectRetiredAgentTaskRequest(req, res) {
|
|
1453
1478
|
const body = req.body && typeof req.body === 'object' ? req.body : {};
|
|
1454
1479
|
const approvalLevel = String(body.approvalLevel || '').trim().toLowerCase();
|
|
@@ -2112,6 +2137,10 @@ function buildRemoteFrameBinaryPacket(frameEvent, diagnostics = {}) {
|
|
|
2112
2137
|
? frame.nativeEncoderHardwareAccelerated
|
|
2113
2138
|
: null,
|
|
2114
2139
|
nativeEncoderHardwareQueryStatus: frame.nativeEncoderHardwareQueryStatus || '',
|
|
2140
|
+
nativeCaptureDisplayId: Number.isSafeInteger(Number(frame.nativeCaptureDisplayId))
|
|
2141
|
+
&& Number(frame.nativeCaptureDisplayId) > 0
|
|
2142
|
+
? Number(frame.nativeCaptureDisplayId)
|
|
2143
|
+
: 0,
|
|
2115
2144
|
nativeCaptureFps: Number(frame.nativeCaptureFps || 0) || 0,
|
|
2116
2145
|
nativeCompressionFps: Number(frame.nativeCompressionFps || 0) || 0,
|
|
2117
2146
|
nativeEncodedFps: Number(frame.nativeEncodedFps || 0) || 0,
|
|
@@ -3093,7 +3122,7 @@ app.post('/api/settings/agent/summary', async (req, res) => {
|
|
|
3093
3122
|
}
|
|
3094
3123
|
});
|
|
3095
3124
|
|
|
3096
|
-
app.get('/api/remote/status', (_req, res) => {
|
|
3125
|
+
app.get('/api/remote/status', (_req, res) => {
|
|
3097
3126
|
noStore(res);
|
|
3098
3127
|
if (runtimeRole !== 'hub') {
|
|
3099
3128
|
res.status(403).json({ ok: false, error: 'role-not-allowed' });
|
|
@@ -3111,9 +3140,134 @@ app.get('/api/remote/status', (_req, res) => {
|
|
|
3111
3140
|
agentPackage: '@livedesk/client',
|
|
3112
3141
|
update: getLiveDeskUpdateStatus()
|
|
3113
3142
|
});
|
|
3114
|
-
});
|
|
3115
|
-
|
|
3116
|
-
app.
|
|
3143
|
+
});
|
|
3144
|
+
|
|
3145
|
+
app.post('/api/remote/diagnostics/transport/start', async (req, res) => {
|
|
3146
|
+
noStore(res);
|
|
3147
|
+
if (!requireLocalTransportDiagnosticRequest(req, res)) return;
|
|
3148
|
+
const requestController = new AbortController();
|
|
3149
|
+
let responseFinished = false;
|
|
3150
|
+
let issuedLease = null;
|
|
3151
|
+
let releaseIssuedLeasePromise = null;
|
|
3152
|
+
const detachResponseLifecycle = () => {
|
|
3153
|
+
res.off('finish', markResponseFinished);
|
|
3154
|
+
res.off('close', abortPendingStart);
|
|
3155
|
+
};
|
|
3156
|
+
const releaseIssuedLease = reason => {
|
|
3157
|
+
if (!issuedLease?.leaseId || !issuedLease?.token) {
|
|
3158
|
+
return releaseIssuedLeasePromise || Promise.resolve(null);
|
|
3159
|
+
}
|
|
3160
|
+
if (!releaseIssuedLeasePromise) {
|
|
3161
|
+
const lease = issuedLease;
|
|
3162
|
+
releaseIssuedLeasePromise = Promise.resolve(remoteHub.stopTransportDiagnostic(
|
|
3163
|
+
lease.leaseId,
|
|
3164
|
+
lease.token,
|
|
3165
|
+
reason
|
|
3166
|
+
)).catch(() => null);
|
|
3167
|
+
}
|
|
3168
|
+
return releaseIssuedLeasePromise;
|
|
3169
|
+
};
|
|
3170
|
+
const markResponseFinished = () => {
|
|
3171
|
+
responseFinished = true;
|
|
3172
|
+
issuedLease = null;
|
|
3173
|
+
detachResponseLifecycle();
|
|
3174
|
+
};
|
|
3175
|
+
const abortPendingStart = () => {
|
|
3176
|
+
if (responseFinished) return;
|
|
3177
|
+
requestController.abort();
|
|
3178
|
+
void releaseIssuedLease('start-response-closed');
|
|
3179
|
+
detachResponseLifecycle();
|
|
3180
|
+
};
|
|
3181
|
+
res.once('finish', markResponseFinished);
|
|
3182
|
+
res.once('close', abortPendingStart);
|
|
3183
|
+
try {
|
|
3184
|
+
const result = await remoteHub.startTransportDiagnostic(
|
|
3185
|
+
String(req.body?.deviceId || ''),
|
|
3186
|
+
Number(req.body?.ttlMs || 0),
|
|
3187
|
+
{ signal: requestController.signal });
|
|
3188
|
+
if (result?.leaseId && result?.token) {
|
|
3189
|
+
issuedLease = {
|
|
3190
|
+
leaseId: result.leaseId,
|
|
3191
|
+
token: result.token
|
|
3192
|
+
};
|
|
3193
|
+
}
|
|
3194
|
+
if (requestController.signal.aborted) {
|
|
3195
|
+
await releaseIssuedLease('start-response-closed');
|
|
3196
|
+
return;
|
|
3197
|
+
}
|
|
3198
|
+
res.status(result.ok ? 200 : 409).json(result);
|
|
3199
|
+
} catch (error) {
|
|
3200
|
+
if (requestController.signal.aborted) return;
|
|
3201
|
+
res.status(500).json({
|
|
3202
|
+
ok: false,
|
|
3203
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3204
|
+
});
|
|
3205
|
+
} finally {
|
|
3206
|
+
if (res.writableFinished) {
|
|
3207
|
+
markResponseFinished();
|
|
3208
|
+
} else if (requestController.signal.aborted) {
|
|
3209
|
+
detachResponseLifecycle();
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
});
|
|
3213
|
+
|
|
3214
|
+
app.get('/api/remote/diagnostics/transport/:leaseId', (req, res) => {
|
|
3215
|
+
noStore(res);
|
|
3216
|
+
if (!requireLocalTransportDiagnosticRequest(req, res)) return;
|
|
3217
|
+
const token = transportDiagnosticToken(req);
|
|
3218
|
+
if (!token) {
|
|
3219
|
+
res.status(401).json({ ok: false, error: 'transport-diagnostic-token-required' });
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
const result = remoteHub.getTransportDiagnostic(req.params.leaseId, token);
|
|
3223
|
+
res.status(result.ok ? 200 : 404).json(result);
|
|
3224
|
+
});
|
|
3225
|
+
|
|
3226
|
+
app.post('/api/remote/diagnostics/transport/:leaseId/phase', async (req, res) => {
|
|
3227
|
+
noStore(res);
|
|
3228
|
+
if (!requireLocalTransportDiagnosticRequest(req, res)) return;
|
|
3229
|
+
const token = transportDiagnosticToken(req);
|
|
3230
|
+
if (!token) {
|
|
3231
|
+
res.status(401).json({ ok: false, error: 'transport-diagnostic-token-required' });
|
|
3232
|
+
return;
|
|
3233
|
+
}
|
|
3234
|
+
try {
|
|
3235
|
+
const result = await remoteHub.setTransportDiagnosticPhase(
|
|
3236
|
+
req.params.leaseId,
|
|
3237
|
+
token,
|
|
3238
|
+
String(req.body?.phase || ''));
|
|
3239
|
+
res.status(result.ok ? 200 : 409).json(result);
|
|
3240
|
+
} catch (error) {
|
|
3241
|
+
res.status(500).json({
|
|
3242
|
+
ok: false,
|
|
3243
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3244
|
+
});
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
|
|
3248
|
+
app.delete('/api/remote/diagnostics/transport/:leaseId', async (req, res) => {
|
|
3249
|
+
noStore(res);
|
|
3250
|
+
if (!requireLocalTransportDiagnosticRequest(req, res)) return;
|
|
3251
|
+
const token = transportDiagnosticToken(req);
|
|
3252
|
+
if (!token) {
|
|
3253
|
+
res.status(401).json({ ok: false, error: 'transport-diagnostic-token-required' });
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
try {
|
|
3257
|
+
const result = await remoteHub.stopTransportDiagnostic(
|
|
3258
|
+
req.params.leaseId,
|
|
3259
|
+
token,
|
|
3260
|
+
String(req.body?.reason || 'runner-complete'));
|
|
3261
|
+
res.status(result.ok ? 200 : 409).json(result);
|
|
3262
|
+
} catch (error) {
|
|
3263
|
+
res.status(500).json({
|
|
3264
|
+
ok: false,
|
|
3265
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3266
|
+
});
|
|
3267
|
+
}
|
|
3268
|
+
});
|
|
3269
|
+
|
|
3270
|
+
app.get('/api/runtime/status', (_req, res) => {
|
|
3117
3271
|
noStore(res);
|
|
3118
3272
|
res.json({ ok: true, ...hubRuntimeStatus(runtimeManager, { role: runtimeRole, deviceId: runtimeDeviceId, deviceName: runtimeDeviceName, roleSource: runtimeRoleSource }) });
|
|
3119
3273
|
});
|
|
@@ -36,6 +36,27 @@ function normalizedAddress(value) {
|
|
|
36
36
|
return address.startsWith('::ffff:') ? address.slice(7) : address;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function isPublicIPv4(value) {
|
|
40
|
+
const address = normalizedAddress(value);
|
|
41
|
+
const octets = address.split('.').map(Number);
|
|
42
|
+
if (octets.length !== 4
|
|
43
|
+
|| octets.some(octet => !Number.isInteger(octet) || octet < 0 || octet > 255)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const [a, b] = octets;
|
|
47
|
+
const c = octets[2];
|
|
48
|
+
return a !== 0
|
|
49
|
+
&& a !== 10
|
|
50
|
+
&& a !== 127
|
|
51
|
+
&& !(a === 100 && b >= 64 && b <= 127)
|
|
52
|
+
&& !(a === 169 && b === 254)
|
|
53
|
+
&& !(a === 172 && b >= 16 && b <= 31)
|
|
54
|
+
&& !(a === 192 && (b === 0 || b === 168 || (b === 88 && c === 99)))
|
|
55
|
+
&& !(a === 198 && (b === 18 || b === 19 || (b === 51 && c === 100)))
|
|
56
|
+
&& !(a === 203 && b === 0 && c === 113)
|
|
57
|
+
&& !(a >= 224);
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
export function createHubUdpTransport({ env = process.env, logEvent = () => {}, logWarn = () => {} } = {}) {
|
|
40
61
|
const enabled = isEnabled(env.LIVEDESK_UDP_ENABLED, true);
|
|
41
62
|
const preferP2p = isEnabled(env.LIVEDESK_UDP_PREFER_P2P, false);
|
|
@@ -182,9 +203,21 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
182
203
|
const session = [...sessions.values()].find(item => item.sessionId === safeText(message.roomId, 160));
|
|
183
204
|
if (!session || message.protocol !== UDP_P2P_PROTOCOL || !isTrustedRendezvousSource(rinfo)) return;
|
|
184
205
|
const address = safeText(message.host, 128);
|
|
185
|
-
const port = Number(message.port);
|
|
186
|
-
if (!address || !Number.isInteger(port) || port < 1 || port > 65535) return;
|
|
187
|
-
|
|
206
|
+
const port = Number(message.port);
|
|
207
|
+
if (!address || !Number.isInteger(port) || port < 1 || port > 65535) return;
|
|
208
|
+
const observedAddress = normalizedAddress(message.observedHost);
|
|
209
|
+
const observedPort = Number(message.observedPort);
|
|
210
|
+
if (observedAddress
|
|
211
|
+
&& Number.isInteger(observedPort)
|
|
212
|
+
&& observedPort >= 1
|
|
213
|
+
&& observedPort <= 65535) {
|
|
214
|
+
session.hubRendezvousEndpoint = {
|
|
215
|
+
address: observedAddress,
|
|
216
|
+
port: observedPort
|
|
217
|
+
};
|
|
218
|
+
session.hubRendezvousObservedAt = Date.now();
|
|
219
|
+
}
|
|
220
|
+
session.peerEndpoint = { address, port };
|
|
188
221
|
session.lastPeerAt = Date.now();
|
|
189
222
|
sendPacket(session, 'probe', { punch: true }, Buffer.alloc(0), session.peerEndpoint);
|
|
190
223
|
emit('UdpPeerEndpointObserved', { deviceId: session.deviceId, endpoint: `${address}:${port}` });
|
|
@@ -230,7 +263,7 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
230
263
|
if (!complete) return;
|
|
231
264
|
session.framesReceived += 1;
|
|
232
265
|
const reassembly = session.reassembler.getStats();
|
|
233
|
-
frameHandler({
|
|
266
|
+
const handled = frameHandler({
|
|
234
267
|
deviceId: session.deviceId,
|
|
235
268
|
sessionId: session.sessionId,
|
|
236
269
|
header: {
|
|
@@ -243,11 +276,34 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
243
276
|
},
|
|
244
277
|
payload: complete.payload,
|
|
245
278
|
transport: 'udp-p2p'
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
279
|
+
});
|
|
280
|
+
const accepted = handled === true || handled?.accepted === true;
|
|
281
|
+
const proofId = safeText(complete.header?.frameTransportProofId, 96);
|
|
282
|
+
if (proofId) {
|
|
283
|
+
if (handled?.suppressProofResponse === true) return;
|
|
284
|
+
const proofAccepted = accepted && handled?.proofAccepted === true;
|
|
285
|
+
sendPacket(session, 'pong', {
|
|
286
|
+
state: proofAccepted ? 'frame-proof-ack' : 'frame-proof-reject',
|
|
287
|
+
proofId,
|
|
288
|
+
protocol: 'livedesk.frame-transport-proof.v1',
|
|
289
|
+
streamId: safeText(complete.header?.streamId, 128),
|
|
290
|
+
commandId: safeText(complete.header?.commandId, 128),
|
|
291
|
+
captureGeneration: Number(complete.header?.captureGeneration || 0) || 0,
|
|
292
|
+
monitorIndex: Number(complete.header?.monitorIndex ?? -1),
|
|
293
|
+
error: proofAccepted ? '' : safeText(handled?.error || 'frame-proof-rejected', 160)
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (accepted) {
|
|
298
|
+
sendPacket(session, 'pong', {
|
|
299
|
+
state: 'frame-ack',
|
|
300
|
+
frameSeq: Number(complete.header?.frameSeq || 0) || 0,
|
|
301
|
+
streamId: safeText(complete.header?.streamId, 128),
|
|
302
|
+
commandId: safeText(complete.header?.commandId, 128),
|
|
303
|
+
captureGeneration: Number(complete.header?.captureGeneration || 0) || 0,
|
|
304
|
+
monitorIndex: Number(complete.header?.monitorIndex ?? -1)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
251
307
|
});
|
|
252
308
|
socket.on('error', error => logWarn('udp', `UDP socket error: ${error?.message || error}`));
|
|
253
309
|
|
|
@@ -290,6 +346,8 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
290
346
|
sendControl,
|
|
291
347
|
clientEndpoint: null,
|
|
292
348
|
peerEndpoint: null,
|
|
349
|
+
hubRendezvousEndpoint: null,
|
|
350
|
+
hubRendezvousObservedAt: 0,
|
|
293
351
|
replay: new UdpReplayWindow(),
|
|
294
352
|
reassembler: new UdpFrameReassembler({ maxPending: maxPendingFrames, timeoutMs: frameTimeoutMs }),
|
|
295
353
|
ready: false,
|
|
@@ -327,7 +385,7 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
327
385
|
emit('UdpSessionClosed', { deviceId: id, reason: 'tcp-session-closed' });
|
|
328
386
|
}
|
|
329
387
|
|
|
330
|
-
function getDeviceStatus(deviceId) {
|
|
388
|
+
function getDeviceStatus(deviceId) {
|
|
331
389
|
const session = sessions.get(safeText(deviceId, 160));
|
|
332
390
|
if (!session) return { enabled, state: enabled ? 'disabled' : 'tcp-only', ready: false };
|
|
333
391
|
return {
|
|
@@ -340,7 +398,72 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
340
398
|
framesReceived: session.framesReceived,
|
|
341
399
|
...session.reassembler.getStats()
|
|
342
400
|
};
|
|
343
|
-
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function refreshDeviceDiagnosticNetwork(deviceId, { timeoutMs = 3_000 } = {}) {
|
|
404
|
+
const session = sessions.get(safeText(deviceId, 160));
|
|
405
|
+
if (!session || !started || !rendezvousHost) return false;
|
|
406
|
+
const requestedAt = Date.now();
|
|
407
|
+
await resolveRendezvousAddresses();
|
|
408
|
+
if (!sendRendezvousRegister(session)) return false;
|
|
409
|
+
const deadline = requestedAt + numberEnv(timeoutMs, 250, 5_000, 3_000);
|
|
410
|
+
while (Date.now() < deadline) {
|
|
411
|
+
if (session.hubRendezvousObservedAt >= requestedAt) return true;
|
|
412
|
+
await new Promise(resolve => {
|
|
413
|
+
const timer = setTimeout(resolve, 40);
|
|
414
|
+
timer.unref?.();
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function getDeviceDiagnosticNetwork(deviceId, { salt = '' } = {}) {
|
|
421
|
+
const session = sessions.get(safeText(deviceId, 160));
|
|
422
|
+
const clientAddress = normalizedAddress(session?.clientEndpoint?.address);
|
|
423
|
+
const normalizedHubAddress = normalizedAddress(session?.hubRendezvousEndpoint?.address);
|
|
424
|
+
const hubAddressObservedAt = session?.hubRendezvousObservedAt
|
|
425
|
+
? new Date(session.hubRendezvousObservedAt).toISOString()
|
|
426
|
+
: '';
|
|
427
|
+
const digest = value => value
|
|
428
|
+
? crypto.createHash('sha256')
|
|
429
|
+
.update(`${safeText(salt, 160)}:${value}`)
|
|
430
|
+
.digest('hex')
|
|
431
|
+
.slice(0, 16)
|
|
432
|
+
: '';
|
|
433
|
+
if (!session?.ready || !clientAddress) {
|
|
434
|
+
return {
|
|
435
|
+
evidence: 'authenticated-udp-endpoint-unavailable',
|
|
436
|
+
differentPublicEgress: null,
|
|
437
|
+
clientAddressHash: '',
|
|
438
|
+
hubAddressHash: digest(normalizedHubAddress),
|
|
439
|
+
hubAddressObservedAt,
|
|
440
|
+
hubEndpointSource: normalizedHubAddress
|
|
441
|
+
? 'trusted-rendezvous-observation'
|
|
442
|
+
: 'unavailable'
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const clientAddressPublic = isPublicIPv4(clientAddress);
|
|
446
|
+
const hubAddressPublic = isPublicIPv4(normalizedHubAddress);
|
|
447
|
+
return {
|
|
448
|
+
evidence: normalizedHubAddress
|
|
449
|
+
? 'authenticated-udp-endpoint-rendezvous-observed'
|
|
450
|
+
: 'authenticated-udp-endpoint-hub-egress-unavailable',
|
|
451
|
+
differentPublicEgress: clientAddressPublic && hubAddressPublic
|
|
452
|
+
? clientAddress !== normalizedHubAddress
|
|
453
|
+
: null,
|
|
454
|
+
clientAddressHash: digest(clientAddress),
|
|
455
|
+
hubAddressHash: digest(normalizedHubAddress),
|
|
456
|
+
clientAddressPublic,
|
|
457
|
+
hubAddressPublic,
|
|
458
|
+
endpointAuthenticated: true,
|
|
459
|
+
udpReady: session.ready === true,
|
|
460
|
+
observedAt: session.lastPacketAt ? new Date(session.lastPacketAt).toISOString() : '',
|
|
461
|
+
hubAddressObservedAt,
|
|
462
|
+
hubEndpointSource: normalizedHubAddress
|
|
463
|
+
? 'trusted-rendezvous-observation'
|
|
464
|
+
: 'unavailable'
|
|
465
|
+
};
|
|
466
|
+
}
|
|
344
467
|
|
|
345
468
|
async function close() {
|
|
346
469
|
stopRendezvousRefreshTimer();
|
|
@@ -354,9 +477,11 @@ export function createHubUdpTransport({ env = process.env, logEvent = () => {},
|
|
|
354
477
|
start,
|
|
355
478
|
close,
|
|
356
479
|
registerDevice,
|
|
357
|
-
unregisterDevice,
|
|
358
|
-
getDeviceStatus,
|
|
359
|
-
|
|
480
|
+
unregisterDevice,
|
|
481
|
+
getDeviceStatus,
|
|
482
|
+
refreshDeviceDiagnosticNetwork,
|
|
483
|
+
getDeviceDiagnosticNetwork,
|
|
484
|
+
getStatus,
|
|
360
485
|
setFrameHandler: handler => { frameHandler = typeof handler === 'function' ? handler : () => {}; },
|
|
361
486
|
setEventHandler: handler => { eventHandler = typeof handler === 'function' ? handler : () => {}; }
|
|
362
487
|
};
|
|
@@ -300,7 +300,9 @@ export function createUdpRendezvousServer({
|
|
|
300
300
|
roomId,
|
|
301
301
|
role: 'client',
|
|
302
302
|
host: client.address,
|
|
303
|
-
port: client.port
|
|
303
|
+
port: client.port,
|
|
304
|
+
observedHost: hub.address,
|
|
305
|
+
observedPort: hub.port
|
|
304
306
|
}, hub.address, hub.port);
|
|
305
307
|
send({
|
|
306
308
|
type: 'rendezvous.peer',
|
|
@@ -308,7 +310,9 @@ export function createUdpRendezvousServer({
|
|
|
308
310
|
roomId,
|
|
309
311
|
role: 'hub',
|
|
310
312
|
host: hub.address,
|
|
311
|
-
port: hub.port
|
|
313
|
+
port: hub.port,
|
|
314
|
+
observedHost: client.address,
|
|
315
|
+
observedPort: client.port
|
|
312
316
|
}, client.address, client.port);
|
|
313
317
|
}
|
|
314
318
|
|