teleportxr 1.0.65 → 1.0.67
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/client/client.js
CHANGED
|
@@ -16,6 +16,11 @@ const { BackgroundMode } = require("../core/core.js");
|
|
|
16
16
|
// gives roughly MAX_ACK_RESENDS*3 seconds before we stop trying.
|
|
17
17
|
const MAX_ACK_RESENDS = 5;
|
|
18
18
|
|
|
19
|
+
// Timeout in milliseconds for WebRTC connection establishment.
|
|
20
|
+
// Clients that don't establish WebRTC within this time will be disconnected.
|
|
21
|
+
// Configurable via WEBRTC_CONNECT_TIMEOUT_MS environment variable.
|
|
22
|
+
const WEBRTC_CONNECT_TIMEOUT_MS = parseInt(process.env.WEBRTC_CONNECT_TIMEOUT_MS || '10000', 10);
|
|
23
|
+
|
|
19
24
|
class OriginState
|
|
20
25
|
{
|
|
21
26
|
constructor() {
|
|
@@ -56,6 +61,7 @@ class Client {
|
|
|
56
61
|
this.next_ack_id=BigInt(1);
|
|
57
62
|
this.clientStartMs=Date.now();
|
|
58
63
|
this.webRtcConnectedAtMs=0;
|
|
64
|
+
this.webRtcConnectionInitiatedAtMs=0; // Track when WebRTC connection attempt started
|
|
59
65
|
}
|
|
60
66
|
elapsedMsSinceStart(){
|
|
61
67
|
return Date.now()-this.clientStartMs;
|
|
@@ -63,6 +69,18 @@ class Client {
|
|
|
63
69
|
elapsedMsSinceConnected(){
|
|
64
70
|
return this.webRtcConnectedAtMs?Date.now()-this.webRtcConnectedAtMs:-1;
|
|
65
71
|
}
|
|
72
|
+
hasWebRtcConnectionTimedOut(){
|
|
73
|
+
// If WebRTC isn't connected yet and we initiated the connection attempt,
|
|
74
|
+
// check if we've exceeded the timeout
|
|
75
|
+
if(!this.webRtcConnected && this.webRtcConnectionInitiatedAtMs > 0) {
|
|
76
|
+
const elapsedMs = Date.now() - this.webRtcConnectionInitiatedAtMs;
|
|
77
|
+
if(elapsedMs > WEBRTC_CONNECT_TIMEOUT_MS) {
|
|
78
|
+
console.log("[T+"+this.elapsedMsSinceStart()+"ms] WebRTC connection timeout for client "+this.clientID+" ("+elapsedMs+"ms > "+WEBRTC_CONNECT_TIMEOUT_MS+"ms)");
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
66
84
|
tick(timestamp){
|
|
67
85
|
this.geometryService.GetNodesToSend();
|
|
68
86
|
}
|
|
@@ -250,6 +268,7 @@ class Client {
|
|
|
250
268
|
StartStreaming()
|
|
251
269
|
{
|
|
252
270
|
console.log("[T+"+this.elapsedMsSinceStart()+"ms] Client.StartStreaming() — creating WebRTC connection for client "+this.clientID);
|
|
271
|
+
this.webRtcConnectionInitiatedAtMs=Date.now(); // Record when we started trying to establish WebRTC
|
|
253
272
|
this.webRtcConnectionManager=WebRtcConnectionManager.getInstance();
|
|
254
273
|
// We make sure WebRTC has a connection for this client.
|
|
255
274
|
this.webRtcConnection = this.webRtcConnectionManager.createConnection(
|
|
@@ -274,6 +293,7 @@ class Client {
|
|
|
274
293
|
// Generic message acknowledgement. Certain kinds of message are expected to be ack'ed.
|
|
275
294
|
ReceiveAcknowledgement(data)
|
|
276
295
|
{
|
|
296
|
+
console.log("ReceiveAcknowledgement called with " + data.byteLength + " bytes");
|
|
277
297
|
if (data.byteLength!=message.AcknowledgementMessage.sizeof())
|
|
278
298
|
{
|
|
279
299
|
console.log("Client: Received malformed AcknowledgementMessage packet of length: ",data.length);
|
|
@@ -291,13 +311,16 @@ class Client {
|
|
|
291
311
|
?new DataView(data.buffer,data.byteOffset,data.byteLength)
|
|
292
312
|
:new DataView(data,0,data.byteLength);
|
|
293
313
|
core.decodeFromDataView(msg,dataView,0);
|
|
314
|
+
console.log("ReceiveAcknowledgement decoded ack_id="+msg.uint64_ackId+", waiting for origin="+this.currentOriginState.ackId+", lighting="+this.currentLightingState.ackId);
|
|
294
315
|
if(msg.uint64_ackId==this.currentOriginState.ackId)
|
|
295
316
|
{
|
|
317
|
+
console.log("ReceiveAcknowledgement: acknowledged SetOriginNodeCommand");
|
|
296
318
|
this.currentOriginState.acknowledged=true;
|
|
297
319
|
this.currentOriginState.resendCount=0;
|
|
298
320
|
}
|
|
299
321
|
if(msg.uint64_ackId==this.currentLightingState.ackId)
|
|
300
322
|
{
|
|
323
|
+
console.log("ReceiveAcknowledgement: acknowledged SetLightingCommand");
|
|
301
324
|
this.currentLightingState.acknowledged=true;
|
|
302
325
|
this.currentLightingState.resendCount=0;
|
|
303
326
|
}
|
package/client/client_manager.js
CHANGED
|
@@ -29,8 +29,26 @@ class ClientManager
|
|
|
29
29
|
clearInterval(this.geometryIntervalId);
|
|
30
30
|
}
|
|
31
31
|
UpdateStreaming() {
|
|
32
|
+
// Track clients to remove due to timeout (can't modify Map during iteration)
|
|
33
|
+
const clientsToRemove = [];
|
|
34
|
+
|
|
32
35
|
for (let [cl_id,cl] of this.clients) {
|
|
33
|
-
|
|
36
|
+
// Check if this client's WebRTC connection has timed out
|
|
37
|
+
if(cl.hasWebRtcConnectionTimedOut()) {
|
|
38
|
+
clientsToRemove.push(cl_id);
|
|
39
|
+
} else {
|
|
40
|
+
cl.UpdateStreaming();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Remove clients that timed out
|
|
45
|
+
for (const cl_id of clientsToRemove) {
|
|
46
|
+
console.log("Removing client "+cl_id+" due to WebRTC connection timeout");
|
|
47
|
+
const cl = this.clients.get(cl_id);
|
|
48
|
+
if(cl) {
|
|
49
|
+
cl.StopStreaming();
|
|
50
|
+
}
|
|
51
|
+
this.RemoveClient(cl_id);
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
54
|
GetOrCreateClient(clientID)
|
package/package.json
CHANGED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Tests for WebRTC connection timeout functionality.
|
|
3
|
+
// Verifies that clients that don't establish WebRTC within the configured
|
|
4
|
+
// timeout are automatically disconnected.
|
|
5
|
+
|
|
6
|
+
const test = require('node:test');
|
|
7
|
+
const assert = require('node:assert');
|
|
8
|
+
const { Client } = require('../client/client');
|
|
9
|
+
const { ClientManager } = require('../client/client_manager');
|
|
10
|
+
|
|
11
|
+
// Helper to create a stub client
|
|
12
|
+
function makeStubClient(opts = {}) {
|
|
13
|
+
const c = Object.create(Client.prototype);
|
|
14
|
+
c.clientID = opts.clientID || 1;
|
|
15
|
+
c.scene = { GetAllNodeUids: () => [] };
|
|
16
|
+
c.geometryService = {
|
|
17
|
+
StreamNode: () => {},
|
|
18
|
+
GetNodesToSend: () => [],
|
|
19
|
+
GetMeshesToSend: () => [],
|
|
20
|
+
GetCanvasesToSend: () => [],
|
|
21
|
+
GetFontAtlasesToSend: () => [],
|
|
22
|
+
GetTexturesToSend: () => [],
|
|
23
|
+
};
|
|
24
|
+
c.clientStartMs = opts.clientStartMs || Date.now();
|
|
25
|
+
c.webRtcConnectedAtMs = 0;
|
|
26
|
+
c.webRtcConnected = false;
|
|
27
|
+
c.webRtcConnection = null;
|
|
28
|
+
c.webRtcConnectionInitiatedAtMs = opts.webRtcConnectionInitiatedAtMs || 0;
|
|
29
|
+
c.StopStreaming = opts.StopStreaming || (() => {});
|
|
30
|
+
return c;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('hasWebRtcConnectionTimedOut returns false when no connection attempt', () => {
|
|
34
|
+
const c = makeStubClient();
|
|
35
|
+
assert.strictEqual(c.hasWebRtcConnectionTimedOut(), false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('hasWebRtcConnectionTimedOut returns false when already connected', () => {
|
|
39
|
+
const c = makeStubClient();
|
|
40
|
+
c.webRtcConnected = true;
|
|
41
|
+
c.webRtcConnectionInitiatedAtMs = Date.now() - 100000; // old time
|
|
42
|
+
assert.strictEqual(c.hasWebRtcConnectionTimedOut(), false);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('hasWebRtcConnectionTimedOut returns false when under timeout', () => {
|
|
46
|
+
const c = makeStubClient({
|
|
47
|
+
webRtcConnectionInitiatedAtMs: Date.now() - 5000, // 5 seconds ago
|
|
48
|
+
});
|
|
49
|
+
c.webRtcConnected = false;
|
|
50
|
+
assert.strictEqual(c.hasWebRtcConnectionTimedOut(), false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('hasWebRtcConnectionTimedOut returns true when over timeout', async () => {
|
|
54
|
+
const c = makeStubClient({
|
|
55
|
+
webRtcConnectionInitiatedAtMs: Date.now() - 15000, // 15 seconds ago
|
|
56
|
+
});
|
|
57
|
+
c.webRtcConnected = false;
|
|
58
|
+
assert.strictEqual(c.hasWebRtcConnectionTimedOut(), true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('ClientManager.UpdateStreaming removes timed-out clients', async () => {
|
|
62
|
+
const cm = new ClientManager();
|
|
63
|
+
const clientsRemoved = [];
|
|
64
|
+
|
|
65
|
+
// Create two stub clients
|
|
66
|
+
const client1 = makeStubClient({ clientID: 1 });
|
|
67
|
+
const client2 = makeStubClient({
|
|
68
|
+
clientID: 2,
|
|
69
|
+
webRtcConnectionInitiatedAtMs: Date.now() - 15000, // Will timeout
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
client2.webRtcConnected = false;
|
|
73
|
+
|
|
74
|
+
cm.clients.set(1, client1);
|
|
75
|
+
cm.clients.set(2, client2);
|
|
76
|
+
|
|
77
|
+
// Mock the RemoveClient to track removals
|
|
78
|
+
const originalRemoveClient = cm.RemoveClient.bind(cm);
|
|
79
|
+
cm.RemoveClient = (clientID) => {
|
|
80
|
+
clientsRemoved.push(clientID);
|
|
81
|
+
originalRemoveClient(clientID);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
cm.UpdateStreaming();
|
|
85
|
+
|
|
86
|
+
// Client 2 should have been removed due to timeout
|
|
87
|
+
assert.deepStrictEqual(clientsRemoved, [2]);
|
|
88
|
+
assert.strictEqual(cm.clients.has(1), true, 'Client 1 should still exist');
|
|
89
|
+
assert.strictEqual(cm.clients.has(2), false, 'Client 2 should be removed');
|
|
90
|
+
});
|