teleportxr 1.0.55 → 1.0.57

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
@@ -73,19 +73,29 @@ class Client {
73
73
  console.log("[T+"+this.elapsedMsSinceStart()+"ms] Connection state is "+newState.toString());
74
74
  if(newState=="connected")
75
75
  {
76
- //this.webRtcConnection.sendGeometry("test");
77
76
  this.webRtcConnected=true;
78
77
  this.webRtcConnectedAtMs=Date.now();
79
- console.log("[T+"+this.elapsedMsSinceStart()+"ms] WebRTC CONNECTED for client "+this.clientID+" — triggering immediate UpdateStreaming tick.");
80
- // Kick an immediate tick so SetOriginNode and the first resource batch are
81
- // sent without waiting for the next periodic interval (up to 1000 ms away).
82
- setImmediate(this.UpdateStreaming.bind(this));
78
+ console.log("[T+"+this.elapsedMsSinceStart()+"ms] WebRTC CONNECTED for client "+this.clientID+" — awaiting data channels.");
79
+ // Do NOT call UpdateStreaming() here. The PeerConnection reaching
80
+ // 'connected' does not guarantee that the geometry/reliable data
81
+ // channels are in 'open' state yet; sends issued now will throw
82
+ // InvalidStateError. Instead, UpdateStreaming is triggered from the
83
+ // dataChannelsOpen callback registered in StartStreaming().
83
84
  }
84
85
  else
85
86
  {
86
87
  this.webRtcConnected=false;
87
88
  }
88
89
  }
90
+ // Invoked once per WebRTC connection, the moment both reliable and geometry
91
+ // data channels are simultaneously in 'open' state. Kicks an immediate
92
+ // streaming tick so the first node/resource batch goes out without waiting
93
+ // for the next periodic interval (up to 1000 ms away).
94
+ onDataChannelsOpen()
95
+ {
96
+ console.log("[T+"+this.elapsedMsSinceStart()+"ms, conn+"+this.elapsedMsSinceConnected()+"ms] data channels open for client "+this.clientID+" — triggering immediate UpdateStreaming tick.");
97
+ setImmediate(this.UpdateStreaming.bind(this));
98
+ }
89
99
  receivedMessageReliable(id,pkt)
90
100
  {
91
101
  var dataView=new DataView(pkt.data,0,1);
@@ -246,7 +256,8 @@ class Client {
246
256
  this.clientID
247
257
  ,this.streamingConnectionStateChanged.bind(this)
248
258
  ,this.receivedMessageReliable.bind(this)
249
- ,this.receivedMessageUnreliable.bind(this));
259
+ ,this.receivedMessageUnreliable.bind(this)
260
+ ,this.onDataChannelsOpen.bind(this));
250
261
  //.then(
251
262
  // function(value) {myDisplayer(value);},
252
263
  // function(error) {myDisplayer(error);}
@@ -486,21 +497,21 @@ class Client {
486
497
  const MAX_NODE_SIZE=500;
487
498
  const buffer = new ArrayBuffer(MAX_NODE_SIZE);
488
499
  const nodeSize=node_encoder.encodeNode(node,buffer);
489
- this.geometryService.EncodedResource(uid);
490
- const view2 = new DataView(buffer, 0, nodeSize);
491
- console.log("Sending node "+uid+" "+node.name+" to Client "+this.clientID+", size: "+nodeSize+" bytes");
500
+ const view2 = new DataView(buffer, 0, nodeSize);
492
501
  if(!this.webRtcConnection)
493
502
  {
494
503
  console.error("this.webRtcConnection is null");
495
504
  return;
496
505
  }
497
- if(!this.webRtcConnection.sendGeometry)
506
+ if(!this.webRtcConnection.isGeometryOpen())
498
507
  {
499
- console.error("this.webRtcConnection.sendGeometry is null");
500
- console.log(JSON.stringify(this.webRtcConnection));
508
+ // Channel not yet open; leave the resource unmarked so it retries on
509
+ // the next UpdateStreaming tick without waiting for the timeout.
501
510
  return;
502
511
  }
503
- this.webRtcConnection.sendGeometry(view2);
512
+ console.log("Sending node "+uid+" "+node.name+" to Client "+this.clientID+", size: "+nodeSize+" bytes");
513
+ if(this.webRtcConnection.sendGeometry(view2))
514
+ this.geometryService.EncodedResource(uid);
504
515
  }
505
516
  SendGenericResource(uid)
506
517
  {
@@ -513,21 +524,21 @@ class Client {
513
524
  const MAX_BUFFER_SIZE=resource.encodedSize();;
514
525
  const buffer = new ArrayBuffer(MAX_BUFFER_SIZE);
515
526
  const resourceSize=resource_encoder.EncodeResource(resource,buffer);
516
- this.geometryService.EncodedResource(uid);
517
527
  const view2 = new DataView(buffer, 0, resourceSize);
518
- console.log("Sending resource "+uid+" "+resource.url+" to Client "+this.clientID+", size: "+resourceSize+" bytes");
519
528
  if(!this.webRtcConnection)
520
529
  {
521
530
  console.error("this.webRtcConnection is null");
522
531
  return;
523
532
  }
524
- if(!this.webRtcConnection.sendGeometry)
533
+ if(!this.webRtcConnection.isGeometryOpen())
525
534
  {
526
- console.error("this.webRtcConnection.sendGeometry is null");
527
- console.log(JSON.stringify(this.webRtcConnection));
535
+ // Channel not yet open; leave the resource unmarked so it retries on
536
+ // the next UpdateStreaming tick without waiting for the timeout.
528
537
  return;
529
538
  }
530
- this.webRtcConnection.sendGeometry(view2);
539
+ console.log("Sending resource "+uid+" "+resource.url+" to Client "+this.clientID+", size: "+resourceSize+" bytes");
540
+ if(this.webRtcConnection.sendGeometry(view2))
541
+ this.geometryService.EncodedResource(uid);
531
542
  }
532
543
  SendMesh(uid)
533
544
  {
@@ -571,10 +582,13 @@ class Client {
571
582
  offset+=core.UID_SIZE;
572
583
  }
573
584
  }
585
+ // Kick WebRTC negotiation off first so the createOffer / setLocalDescription
586
+ // / ICE gathering pipeline overlaps the signaling round-trip for the ack
587
+ // below. The C++ client buffers offers and candidates that arrive while
588
+ // it is still processing the ack, so ordering is safe.
589
+ this.StartStreaming();
574
590
  var acknowledgeHandshakeCommand=new command.AcknowledgeHandshakeCommand;
575
591
  this.SendCommand(acknowledgeHandshakeCommand);
576
- // And now, setup is complete. On the next geometry update, we can send nodes/resources.
577
- this.StartStreaming();
578
592
  }
579
593
  receiveReliableBinaryMessage(data){
580
594
  const messageType=data[0];
@@ -56,7 +56,9 @@ class WebRtcConnection extends EventEmitter
56
56
  this.messageReceivedReliableCb =options.messageReceivedReliable;
57
57
  this.messageReceivedUnreliableCb =options.messageReceivedUnreliable;
58
58
  this.connectionStateChangedCb=options.connectionStateChanged;
59
+ this.dataChannelsOpenCb =options.dataChannelsOpen;
59
60
  this.sendConfigMessage =options.sendConfigMessage;
61
+ this._dataChannelsOpenFired =false;
60
62
 
61
63
  this._onIceConnectionStateChange = this.onIceConnectionStateChange.bind(this);
62
64
  this._onIceGatheringStateChange = this.onIceGatheringStateChange.bind(this);
@@ -348,25 +350,58 @@ class WebRtcConnection extends EventEmitter
348
350
  this.state = 'closed';
349
351
  this.emit('closed');
350
352
  };
351
- sendGeometry(buffer) {
353
+ isGeometryOpen() {
354
+ return !!(this.geometryDataChannel && this.geometryDataChannel.readyState === 'open');
355
+ }
356
+ isReliableOpen() {
357
+ return !!(this.reliableDataChannel && this.reliableDataChannel.readyState === 'open');
358
+ }
359
+ // Invoked from each data channel's onopen. Fires the dataChannelsOpen callback
360
+ // exactly once per PeerConnection, the moment both reliable and geometry
361
+ // channels are in 'open' state. This lets Client.UpdateStreaming run as soon
362
+ // as the channels can accept traffic rather than waiting for the next
363
+ // periodic tick (up to 1 s away).
364
+ _handleDataChannelOpen(label) {
365
+ if (this._dataChannelsOpenFired)
366
+ return;
367
+ if (!this.isGeometryOpen() || !this.isReliableOpen())
368
+ return;
369
+ this._dataChannelsOpenFired = true;
370
+ if (this.dataChannelsOpenCb)
371
+ {
372
+ try { this.dataChannelsOpenCb(); }
373
+ catch (e) { console.error('dataChannelsOpenCb threw: '+e.message); }
374
+ }
375
+ }
376
+ // Returns true if the buffer was handed to the underlying transport, false if the
377
+ // channel was not in the 'open' state or send() threw. Callers must use the
378
+ // return value to gate any "this resource has been transmitted" bookkeeping;
379
+ // otherwise a dropped send leaves the resource marked Sent and it won't be
380
+ // retried until geometry_service.timeout_us elapses (default 10 s).
381
+ sendGeometry(buffer) {
382
+ if (!this.isGeometryOpen())
383
+ return false;
352
384
  try {
353
- this.geometryDataChannel.send(buffer);
385
+ this.geometryDataChannel.send(buffer);
386
+ return true;
354
387
  }
355
388
  catch(exception) {
356
- console.error('datachannel.sendGeometry exception: '+exception.message);
389
+ console.error('datachannel.sendGeometry exception: '+exception.message);
390
+ return false;
357
391
  }
358
- }
359
- isReliableOpen() {
360
- return !!(this.reliableDataChannel && this.reliableDataChannel.readyState === 'open');
361
392
  }
362
- sendReliable(buffer) {
393
+ sendReliable(buffer) {
394
+ if (!this.isReliableOpen())
395
+ return false;
363
396
  try {
364
397
  this.reliableDataChannel.send(buffer);
398
+ return true;
365
399
  }
366
400
  catch(exception) {
367
- console.error('datachannel.sendReliable exception: '+exception.message);
401
+ console.error('datachannel.sendReliable exception: '+exception.message);
402
+ return false;
368
403
  }
369
- }
404
+ }
370
405
  beforeOffer() {
371
406
 
372
407
  this.videoDataChannel = this.createDataChannel("video",20);
@@ -425,6 +460,7 @@ class WebRtcConnection extends EventEmitter
425
460
 
426
461
  dc.onopen = (event) => {
427
462
  console.log('datachannel '+label+' open');
463
+ this._handleDataChannelOpen(label);
428
464
  //dc.send('XXXX');
429
465
  };
430
466
 
@@ -37,7 +37,7 @@ class WebRtcConnectionManager
37
37
  this.deleteConnection(connection);
38
38
  }
39
39
 
40
- createConnection(clientID,connectionStateChangedCb,messageReceivedReliableCb,messageReceivedUnreliableCb)
40
+ createConnection(clientID,connectionStateChangedCb,messageReceivedReliableCb,messageReceivedUnreliableCb,dataChannelsOpenCb)
41
41
  {
42
42
  const existing = this.connections.get(clientID);
43
43
  if (existing)
@@ -52,6 +52,7 @@ class WebRtcConnectionManager
52
52
  options.messageReceivedReliable =messageReceivedReliableCb;
53
53
  options.messageReceivedUnreliable =messageReceivedUnreliableCb;
54
54
  options.connectionStateChanged =connectionStateChangedCb;
55
+ options.dataChannelsOpen =dataChannelsOpenCb;
55
56
  const connection = new WebRtcConnection(clientID,options);
56
57
  // We will not add a "closed" listener, because only the client object will be permitted to close its connection.
57
58
  //this.createConnection = (clientID) => this.closedListeners.set(connection, this.closedListener);
package/package.json CHANGED
@@ -11,11 +11,12 @@
11
11
  "ws": "^8.13.0"
12
12
  },
13
13
  "scripts": {
14
- "start": "node server.js"
14
+ "start": "node server.js",
15
+ "test": "node --test test/"
15
16
  },
16
17
  "name": "teleportxr",
17
18
  "description": "Teleport Spatial Server on node.js",
18
- "version": "1.0.55",
19
+ "version": "1.0.57",
19
20
  "repository": {
20
21
  "type": "git",
21
22
  "url": "git+https://github.com/simul/teleport-nodejs.git"
@@ -0,0 +1,129 @@
1
+ 'use strict';
2
+ // Regression tests for Client.SendNode / Client.SendGenericResource. Before the
3
+ // fix, EncodedResource(uid) was called unconditionally before sendGeometry, so
4
+ // a failed send still marked the resource as transmitted and the
5
+ // geometry_service refused to retry it until its 10 s timeout elapsed.
6
+
7
+ const test = require('node:test');
8
+ const assert = require('node:assert');
9
+ const { Client } = require('../client/client');
10
+ const resources = require('../scene/resources');
11
+
12
+ function makeStubClient(opts) {
13
+ const c = Object.create(Client.prototype);
14
+ c.clientID = 7;
15
+ c.scene = {
16
+ GetNode: () => ({
17
+ name: 'stub-node',
18
+ encodeIntoDataView: (_dv, off) => off + 1,
19
+ }),
20
+ };
21
+ c.geometryService = {
22
+ EncodedResource: (uid) => { c._encodedCalls.push(uid); },
23
+ };
24
+ c._encodedCalls = [];
25
+ c.webRtcConnection = opts.webRtcConnection;
26
+ return c;
27
+ }
28
+
29
+ test('SendNode does not mark resource encoded when geometry channel is not open', () => {
30
+ const c = makeStubClient({
31
+ webRtcConnection: {
32
+ isGeometryOpen: () => false,
33
+ sendGeometry: () => { throw new Error('sendGeometry must not be called when not open'); },
34
+ },
35
+ });
36
+ c.SendNode(42n);
37
+ assert.strictEqual(c._encodedCalls.length, 0);
38
+ });
39
+
40
+ test('SendNode marks resource encoded when sendGeometry returns true', () => {
41
+ const c = makeStubClient({
42
+ webRtcConnection: {
43
+ isGeometryOpen: () => true,
44
+ sendGeometry: () => true,
45
+ },
46
+ });
47
+ c.SendNode(42n);
48
+ assert.deepStrictEqual(c._encodedCalls, [42n]);
49
+ });
50
+
51
+ test('SendNode does not mark resource encoded when sendGeometry returns false', () => {
52
+ // Simulates the race where the channel was "open" at the isGeometryOpen()
53
+ // check but transitioned to closing/closed before send() ran (or wrtc
54
+ // returned false synchronously). The resource must remain unmarked so the
55
+ // next UpdateStreaming tick re-attempts it.
56
+ const c = makeStubClient({
57
+ webRtcConnection: {
58
+ isGeometryOpen: () => true,
59
+ sendGeometry: () => false,
60
+ },
61
+ });
62
+ c.SendNode(42n);
63
+ assert.strictEqual(c._encodedCalls.length, 0);
64
+ });
65
+
66
+ test('SendNode short-circuits when webRtcConnection is null', () => {
67
+ const c = makeStubClient({ webRtcConnection: null });
68
+ // Must not throw; must not record an encoded resource.
69
+ c.SendNode(42n);
70
+ assert.strictEqual(c._encodedCalls.length, 0);
71
+ });
72
+
73
+ // SendGenericResource shares the same gating shape. Build a minimal Resource so
74
+ // the encoder doesn't blow up.
75
+ class StubResource {
76
+ constructor() {
77
+ this.url = 'stub://resource';
78
+ this.uid = 99n;
79
+ }
80
+ encodedSize() { return 64; }
81
+ encodeIntoDataView(_dv, off) { return off + 1; }
82
+ }
83
+
84
+ test('SendGenericResource does not mark resource encoded when channel is not open', () => {
85
+ const stub = new StubResource();
86
+ const originalGet = resources.GetResourceFromUid;
87
+ resources.GetResourceFromUid = () => stub;
88
+ try {
89
+ const c = makeStubClient({
90
+ webRtcConnection: {
91
+ isGeometryOpen: () => false,
92
+ sendGeometry: () => { throw new Error('must not be called'); },
93
+ },
94
+ });
95
+ c.SendGenericResource(99n);
96
+ assert.strictEqual(c._encodedCalls.length, 0);
97
+ }
98
+ finally {
99
+ resources.GetResourceFromUid = originalGet;
100
+ }
101
+ });
102
+
103
+ test('SendGenericResource marks resource encoded only after a successful send', () => {
104
+ const stub = new StubResource();
105
+ const originalGet = resources.GetResourceFromUid;
106
+ resources.GetResourceFromUid = () => stub;
107
+ try {
108
+ const c = makeStubClient({
109
+ webRtcConnection: {
110
+ isGeometryOpen: () => true,
111
+ sendGeometry: () => true,
112
+ },
113
+ });
114
+ c.SendGenericResource(99n);
115
+ assert.deepStrictEqual(c._encodedCalls, [99n]);
116
+
117
+ const c2 = makeStubClient({
118
+ webRtcConnection: {
119
+ isGeometryOpen: () => true,
120
+ sendGeometry: () => false,
121
+ },
122
+ });
123
+ c2.SendGenericResource(99n);
124
+ assert.strictEqual(c2._encodedCalls.length, 0);
125
+ }
126
+ finally {
127
+ resources.GetResourceFromUid = originalGet;
128
+ }
129
+ });
@@ -0,0 +1,69 @@
1
+ 'use strict';
2
+ // Regression tests for _handleDataChannelOpen. After both reliable and geometry
3
+ // data channels are simultaneously in 'open' state, the dataChannelsOpenCb must
4
+ // fire exactly once for the lifetime of the PeerConnection — additional channel
5
+ // opens (e.g. the unreliable channel) must not retrigger it.
6
+
7
+ const test = require('node:test');
8
+ const assert = require('node:assert');
9
+ const WebRtcConnection = require('../connections/webrtcconnection');
10
+
11
+ function makeCtx() {
12
+ const ctx = Object.create(WebRtcConnection.prototype);
13
+ ctx._dataChannelsOpenFired = false;
14
+ return ctx;
15
+ }
16
+
17
+ test('callback fires once when both required channels reach "open"', () => {
18
+ let calls = 0;
19
+ const ctx = makeCtx();
20
+ ctx.dataChannelsOpenCb = () => { calls++; };
21
+ ctx.geometryDataChannel = { readyState: 'connecting' };
22
+ ctx.reliableDataChannel = { readyState: 'connecting' };
23
+
24
+ // First channel opens — callback must not fire yet.
25
+ ctx.geometryDataChannel.readyState = 'open';
26
+ ctx._handleDataChannelOpen('geometry_unframed');
27
+ assert.strictEqual(calls, 0, 'callback fired with only geometry open');
28
+
29
+ // Second channel opens — callback fires.
30
+ ctx.reliableDataChannel.readyState = 'open';
31
+ ctx._handleDataChannelOpen('reliable');
32
+ assert.strictEqual(calls, 1, 'callback did not fire when both channels open');
33
+
34
+ // Later channels opening must not retrigger.
35
+ ctx._handleDataChannelOpen('unreliable');
36
+ ctx._handleDataChannelOpen('video');
37
+ assert.strictEqual(calls, 1, 'callback fired a second time on later channel open');
38
+ });
39
+
40
+ test('callback never fires while only one of the required channels is open', () => {
41
+ let calls = 0;
42
+ const ctx = makeCtx();
43
+ ctx.dataChannelsOpenCb = () => { calls++; };
44
+ ctx.geometryDataChannel = { readyState: 'open' };
45
+ ctx.reliableDataChannel = { readyState: 'connecting' };
46
+
47
+ ctx._handleDataChannelOpen('geometry_unframed');
48
+ ctx._handleDataChannelOpen('video');
49
+ assert.strictEqual(calls, 0);
50
+ });
51
+
52
+ test('absent dataChannelsOpenCb does not throw when both channels open', () => {
53
+ const ctx = makeCtx();
54
+ ctx.geometryDataChannel = { readyState: 'open' };
55
+ ctx.reliableDataChannel = { readyState: 'open' };
56
+ // Must not throw.
57
+ ctx._handleDataChannelOpen('reliable');
58
+ assert.strictEqual(ctx._dataChannelsOpenFired, true);
59
+ });
60
+
61
+ test('callback that throws is caught and the fired flag remains set', () => {
62
+ const ctx = makeCtx();
63
+ ctx.dataChannelsOpenCb = () => { throw new Error('handler boom'); };
64
+ ctx.geometryDataChannel = { readyState: 'open' };
65
+ ctx.reliableDataChannel = { readyState: 'open' };
66
+ // Must not propagate the error to the caller.
67
+ ctx._handleDataChannelOpen('reliable');
68
+ assert.strictEqual(ctx._dataChannelsOpenFired, true);
69
+ });
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+ // Regression tests for the send-gating behaviour added to WebRtcConnection.
3
+ // Before the fix, sendGeometry/sendReliable swallowed InvalidStateError when
4
+ // the data channel's readyState was still 'connecting' but returned no signal
5
+ // to the caller, so client.js was free to mark resources as transmitted even
6
+ // though nothing had been written. The geometry_service then refused to retry
7
+ // them until its 10 s timeout elapsed.
8
+ //
9
+ // These tests don't construct a real RTCPeerConnection; they invoke the
10
+ // methods on a stub object that inherits from WebRtcConnection.prototype.
11
+
12
+ const test = require('node:test');
13
+ const assert = require('node:assert');
14
+ const WebRtcConnection = require('../connections/webrtcconnection');
15
+
16
+ function makeCtx() {
17
+ return Object.create(WebRtcConnection.prototype);
18
+ }
19
+
20
+ test('sendGeometry returns false and does not call dc.send when channel is connecting', () => {
21
+ let sendCalls = 0;
22
+ const ctx = makeCtx();
23
+ ctx.geometryDataChannel = {
24
+ readyState: 'connecting',
25
+ send: () => { sendCalls++; },
26
+ };
27
+ const result = ctx.sendGeometry(new Uint8Array([1, 2, 3]));
28
+ assert.strictEqual(result, false);
29
+ assert.strictEqual(sendCalls, 0, 'send() must not be called while readyState is "connecting"');
30
+ });
31
+
32
+ test('sendGeometry returns false when geometryDataChannel is missing entirely', () => {
33
+ const ctx = makeCtx();
34
+ assert.strictEqual(ctx.sendGeometry(new Uint8Array([1])), false);
35
+ });
36
+
37
+ test('sendGeometry returns true when channel is open and forwards the buffer', () => {
38
+ let captured = null;
39
+ const ctx = makeCtx();
40
+ ctx.geometryDataChannel = {
41
+ readyState: 'open',
42
+ send: (b) => { captured = b; },
43
+ };
44
+ const buf = new Uint8Array([4, 5, 6]);
45
+ assert.strictEqual(ctx.sendGeometry(buf), true);
46
+ assert.strictEqual(captured, buf);
47
+ });
48
+
49
+ test('sendGeometry returns false when an open channel throws inside send()', () => {
50
+ const ctx = makeCtx();
51
+ ctx.geometryDataChannel = {
52
+ readyState: 'open',
53
+ send: () => { throw new Error('simulated wrtc failure'); },
54
+ };
55
+ assert.strictEqual(ctx.sendGeometry(new Uint8Array([1])), false);
56
+ });
57
+
58
+ test('sendReliable returns false and does not call dc.send when channel is connecting', () => {
59
+ let sendCalls = 0;
60
+ const ctx = makeCtx();
61
+ ctx.reliableDataChannel = {
62
+ readyState: 'connecting',
63
+ send: () => { sendCalls++; },
64
+ };
65
+ assert.strictEqual(ctx.sendReliable(new Uint8Array([1])), false);
66
+ assert.strictEqual(sendCalls, 0);
67
+ });
68
+
69
+ test('sendReliable returns true when channel is open and forwards the buffer', () => {
70
+ let captured = null;
71
+ const ctx = makeCtx();
72
+ ctx.reliableDataChannel = {
73
+ readyState: 'open',
74
+ send: (b) => { captured = b; },
75
+ };
76
+ const buf = new Uint8Array([7, 8, 9]);
77
+ assert.strictEqual(ctx.sendReliable(buf), true);
78
+ assert.strictEqual(captured, buf);
79
+ });
80
+
81
+ test('isGeometryOpen reports true only when readyState equals "open"', () => {
82
+ const ctx = makeCtx();
83
+ assert.strictEqual(ctx.isGeometryOpen(), false, 'no channel');
84
+ ctx.geometryDataChannel = { readyState: 'connecting' };
85
+ assert.strictEqual(ctx.isGeometryOpen(), false, 'connecting');
86
+ ctx.geometryDataChannel.readyState = 'open';
87
+ assert.strictEqual(ctx.isGeometryOpen(), true, 'open');
88
+ ctx.geometryDataChannel.readyState = 'closing';
89
+ assert.strictEqual(ctx.isGeometryOpen(), false, 'closing');
90
+ });