topazcube 0.1.21 → 0.1.23
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/README.md +20 -3
- package/dist/client.cjs +89 -44
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +9 -2
- package/dist/client.d.ts +9 -2
- package/dist/client.js +89 -44
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +34 -1420
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +3 -3
- package/dist/server.d.ts +3 -3
- package/dist/server.js +34 -1449
- package/dist/server.js.map +1 -1
- package/package.json +9 -14
- package/src/client.ts +122 -79
- package/src/server.ts +32 -33
package/README.md
CHANGED
|
@@ -23,7 +23,9 @@ const server = new TopazCubeServer({
|
|
|
23
23
|
|
|
24
24
|
### WebRTC Support (Optional)
|
|
25
25
|
|
|
26
|
-
WebRTC functionality is optional and requires platform-specific binary packages.
|
|
26
|
+
WebRTC functionality is optional and requires platform-specific binary packages. **Note: WebRTC is only available when using the CommonJS build due to native module limitations.**
|
|
27
|
+
|
|
28
|
+
If you want to use WebRTC features:
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
31
|
# Install the appropriate platform package
|
|
@@ -32,15 +34,30 @@ npm install @roamhq/wrtc-linux-x64 # for Linux x64
|
|
|
32
34
|
npm install @roamhq/wrtc-win32-x64 # for Windows x64
|
|
33
35
|
```
|
|
34
36
|
|
|
35
|
-
Then
|
|
37
|
+
Then use the CommonJS build and enable WebRTC:
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
// Use require() and .cjs build for WebRTC support
|
|
41
|
+
const TopazCubeServer = require('topazcube/server').default;
|
|
36
42
|
|
|
37
|
-
```typescript
|
|
38
43
|
const server = new TopazCubeServer({
|
|
39
44
|
port: 8799,
|
|
40
45
|
allowWebRTC: true // Enable WebRTC functionality
|
|
41
46
|
})
|
|
42
47
|
```
|
|
43
48
|
|
|
49
|
+
For ESM imports without WebRTC:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// ESM import works fine without WebRTC
|
|
53
|
+
import TopazCubeServer from 'topazcube/server'
|
|
54
|
+
|
|
55
|
+
const server = new TopazCubeServer({
|
|
56
|
+
port: 8799,
|
|
57
|
+
allowWebRTC: false // WebRTC not available in ESM mode
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
44
61
|
### Client Setup
|
|
45
62
|
|
|
46
63
|
```typescript
|
package/dist/client.cjs
CHANGED
|
@@ -174,7 +174,7 @@ async function decompress(buffer) {
|
|
|
174
174
|
// src/client.ts
|
|
175
175
|
var import_gl_matrix = require("gl-matrix");
|
|
176
176
|
var TopazCubeClient = class {
|
|
177
|
-
|
|
177
|
+
DEBUG = false;
|
|
178
178
|
CYCLE = 200;
|
|
179
179
|
// update/patch rate in ms
|
|
180
180
|
url = "";
|
|
@@ -205,6 +205,9 @@ var TopazCubeClient = class {
|
|
|
205
205
|
ID = 0;
|
|
206
206
|
socket = null;
|
|
207
207
|
_peerConnection = null;
|
|
208
|
+
_candidates = [];
|
|
209
|
+
_remoteCandidates = [];
|
|
210
|
+
_offerSent = false;
|
|
208
211
|
_dataChannel = null;
|
|
209
212
|
// our data channel
|
|
210
213
|
_serverDataChannel = null;
|
|
@@ -228,15 +231,30 @@ var TopazCubeClient = class {
|
|
|
228
231
|
// auto reconnect on disconnect
|
|
229
232
|
allowSync = true,
|
|
230
233
|
// allow sync on connect
|
|
231
|
-
allowWebRTC = false
|
|
234
|
+
allowWebRTC = false,
|
|
235
|
+
DEBUG = false
|
|
232
236
|
}) {
|
|
233
237
|
this.url = url;
|
|
234
238
|
this.autoReconnect = autoReconnect;
|
|
235
239
|
this.allowSync = allowSync;
|
|
236
240
|
this.allowWebRTC = allowWebRTC;
|
|
237
241
|
this.socket = null;
|
|
242
|
+
this.DEBUG = DEBUG;
|
|
238
243
|
this._startLoop();
|
|
239
|
-
|
|
244
|
+
this.log("Client initialized");
|
|
245
|
+
}
|
|
246
|
+
log(...args) {
|
|
247
|
+
if (this.DEBUG) {
|
|
248
|
+
console.log(...args);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
warn(...args) {
|
|
252
|
+
if (this.DEBUG) {
|
|
253
|
+
console.warn(...args);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
error(...args) {
|
|
257
|
+
console.error(...args);
|
|
240
258
|
}
|
|
241
259
|
/*= UPDATE ===================================================================*/
|
|
242
260
|
_startLoop() {
|
|
@@ -307,20 +325,32 @@ var TopazCubeClient = class {
|
|
|
307
325
|
let t2 = e._lpostime2;
|
|
308
326
|
const interval = t2 - t1;
|
|
309
327
|
const elapsed = now - t1;
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
328
|
+
e.pelapsed = elapsed;
|
|
329
|
+
if (elapsed > 1e3) {
|
|
330
|
+
import_gl_matrix.vec3.copy(e.position, e._lpos2);
|
|
331
|
+
e._changed_position = now;
|
|
332
|
+
} else {
|
|
333
|
+
const alpha = Math.max(0, elapsed / interval);
|
|
334
|
+
import_gl_matrix.vec3.lerp(this._dpos, e._lpos1, e._lpos2, alpha);
|
|
335
|
+
import_gl_matrix.vec3.lerp(e.position, e.position, this._dpos, 0.07);
|
|
336
|
+
e._changed_position = now;
|
|
337
|
+
}
|
|
314
338
|
}
|
|
315
339
|
if (e._lrottime1 && e._lrottime2) {
|
|
316
340
|
let t1 = e._lrottime1;
|
|
317
341
|
let t2 = e._lrottime2;
|
|
318
342
|
const interval = t2 - t1;
|
|
319
343
|
const elapsed = now - t1;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
344
|
+
e.relapsed = elapsed;
|
|
345
|
+
if (elapsed > 1e3) {
|
|
346
|
+
import_gl_matrix.quat.copy(e.rotation, e._lrot2);
|
|
347
|
+
e._changed_rotation = now;
|
|
348
|
+
} else {
|
|
349
|
+
const alpha = Math.max(0, elapsed / interval);
|
|
350
|
+
import_gl_matrix.quat.slerp(this._drot, e._lrot1, e._lrot2, alpha);
|
|
351
|
+
import_gl_matrix.quat.slerp(e.rotation, e.rotation, this._drot, 0.07);
|
|
352
|
+
e._changed_rotation = now;
|
|
353
|
+
}
|
|
324
354
|
}
|
|
325
355
|
}
|
|
326
356
|
}
|
|
@@ -341,7 +371,7 @@ var TopazCubeClient = class {
|
|
|
341
371
|
}
|
|
342
372
|
this.isConnecting = true;
|
|
343
373
|
this._clear();
|
|
344
|
-
|
|
374
|
+
this.log("connecting...");
|
|
345
375
|
this.socket = new WebSocket(this.url);
|
|
346
376
|
this.socket.onmessage = async (event) => {
|
|
347
377
|
let buffer = await event.data.arrayBuffer();
|
|
@@ -448,7 +478,7 @@ var TopazCubeClient = class {
|
|
|
448
478
|
this.socket.send(enc);
|
|
449
479
|
}
|
|
450
480
|
} catch (e) {
|
|
451
|
-
|
|
481
|
+
this.error("send failed", e);
|
|
452
482
|
}
|
|
453
483
|
}
|
|
454
484
|
get document() {
|
|
@@ -486,7 +516,7 @@ var TopazCubeClient = class {
|
|
|
486
516
|
try {
|
|
487
517
|
(0, import_fast_json_patch.applyOperation)(this.documents[name], dop);
|
|
488
518
|
} catch (e) {
|
|
489
|
-
|
|
519
|
+
this.error("applyOperation failed for", name, "with op", dop, e);
|
|
490
520
|
}
|
|
491
521
|
}
|
|
492
522
|
this.isPatched = false;
|
|
@@ -517,10 +547,10 @@ var TopazCubeClient = class {
|
|
|
517
547
|
let nmessage = decode(cdecu);
|
|
518
548
|
this._onMessage(nmessage);
|
|
519
549
|
} catch (error) {
|
|
520
|
-
|
|
550
|
+
this.error("Error decoding chunks:", error);
|
|
521
551
|
}
|
|
522
552
|
} else {
|
|
523
|
-
|
|
553
|
+
this.warn("missing chunks", cfound, "of", message.seq + 1);
|
|
524
554
|
}
|
|
525
555
|
}
|
|
526
556
|
} else if (message.c == "fpatch") {
|
|
@@ -533,11 +563,11 @@ var TopazCubeClient = class {
|
|
|
533
563
|
if (this._lastUpdateId[name] < message.u) {
|
|
534
564
|
let lp = message.u - this._lastUpdateId[name] - 1;
|
|
535
565
|
if (lp > 0) {
|
|
536
|
-
|
|
566
|
+
this.warn("Lost " + lp + " updates");
|
|
537
567
|
}
|
|
538
568
|
this._lastUpdateId[name] = message.u;
|
|
539
569
|
} else if (this._lastUpdateId[name] > message.u) {
|
|
540
|
-
|
|
570
|
+
this.warn(`Received outdated update ID for document ${name}: ${message.u} < ${this._lastUpdateId[name]}`);
|
|
541
571
|
doPatch = false;
|
|
542
572
|
}
|
|
543
573
|
}
|
|
@@ -553,9 +583,11 @@ var TopazCubeClient = class {
|
|
|
553
583
|
this.send({ c: "peng", ct: Date.now(), st: stime });
|
|
554
584
|
this.stats.stdiff = stime + ping / 2 - time;
|
|
555
585
|
this.stats.ping = ping;
|
|
556
|
-
|
|
586
|
+
this.log("ping", ping, "ms", "stdiff", this.stats.stdiff, "ms");
|
|
557
587
|
} else if (message.c == "rtc-offer") {
|
|
588
|
+
this.log("RTC: offer received:", message);
|
|
558
589
|
} else if (message.c == "rtc-answer") {
|
|
590
|
+
this.log("RTC: answer received:", message);
|
|
559
591
|
try {
|
|
560
592
|
const sessionDesc = new RTCSessionDescription({
|
|
561
593
|
type: message.type,
|
|
@@ -564,30 +596,30 @@ var TopazCubeClient = class {
|
|
|
564
596
|
if (this._peerConnection) {
|
|
565
597
|
await this._peerConnection.setRemoteDescription(sessionDesc);
|
|
566
598
|
}
|
|
599
|
+
this.log("RTC: Remote description set successfully");
|
|
600
|
+
for (let candidate of this._remoteCandidates) {
|
|
601
|
+
try {
|
|
602
|
+
await this._peerConnection?.addIceCandidate(candidate);
|
|
603
|
+
this.log("RTC: Added remote ICE candidate:", candidate);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
this.error("RTC: Error adding remote ICE candidate:", error);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
567
608
|
} catch (error) {
|
|
568
|
-
|
|
609
|
+
this.error("RTC: Error setting remote description:", error);
|
|
569
610
|
}
|
|
570
611
|
} else if (message.c == "rtc-candidate") {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
//new RTCIceCandidate(message.candidate)
|
|
575
|
-
message.candidate
|
|
576
|
-
);
|
|
577
|
-
} else {
|
|
578
|
-
console.warn(
|
|
579
|
-
"RTC: Received candidate but peerConnection not ready or candidate missing"
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
} catch (error) {
|
|
612
|
+
this.log("RTC: candidate received", message);
|
|
613
|
+
if (this._peerConnection && message.candidate) {
|
|
614
|
+
this._remoteCandidates.push(message.candidate);
|
|
583
615
|
}
|
|
584
616
|
} else {
|
|
585
617
|
this.onMessage(message);
|
|
586
618
|
}
|
|
587
619
|
}
|
|
588
620
|
_onDocumentChange(name, op, target, path, value) {
|
|
589
|
-
if (this.
|
|
590
|
-
|
|
621
|
+
if (this.DEBUG) {
|
|
622
|
+
this.log("Document change:", name, op, target, path, value);
|
|
591
623
|
}
|
|
592
624
|
if (this.isPatched || !this.allowSync) {
|
|
593
625
|
return;
|
|
@@ -770,12 +802,12 @@ var TopazCubeClient = class {
|
|
|
770
802
|
}
|
|
771
803
|
}
|
|
772
804
|
_onRTCConnect() {
|
|
773
|
-
|
|
805
|
+
this.log("RTC: Connected");
|
|
774
806
|
this.send({ c: "test", message: "Hello RTC from client" });
|
|
775
807
|
}
|
|
776
808
|
_onRTCDisconnect() {
|
|
777
809
|
this._webRTCConnected = false;
|
|
778
|
-
|
|
810
|
+
this.log("RTC: Disconnected");
|
|
779
811
|
}
|
|
780
812
|
async _onRTCMessage(data) {
|
|
781
813
|
this.stats.recRTC += data.byteLength;
|
|
@@ -787,6 +819,9 @@ var TopazCubeClient = class {
|
|
|
787
819
|
}
|
|
788
820
|
async _initializeWebRTC() {
|
|
789
821
|
this._peerConnection = null;
|
|
822
|
+
this._candidates = [];
|
|
823
|
+
this._remoteCandidates = [];
|
|
824
|
+
this._offerSent = false;
|
|
790
825
|
try {
|
|
791
826
|
this._peerConnection = new RTCPeerConnection({
|
|
792
827
|
iceServers: [
|
|
@@ -798,22 +833,31 @@ var TopazCubeClient = class {
|
|
|
798
833
|
});
|
|
799
834
|
this._peerConnection.onicecandidate = (event) => {
|
|
800
835
|
if (event.candidate) {
|
|
801
|
-
this.
|
|
802
|
-
c: "rtc-candidate",
|
|
803
|
-
type: "ice-candidate",
|
|
804
|
-
candidate: event.candidate
|
|
805
|
-
});
|
|
836
|
+
this._candidates.push(event.candidate);
|
|
806
837
|
} else {
|
|
838
|
+
this.log("RTC: ICE candidate gathering complete");
|
|
807
839
|
}
|
|
808
840
|
};
|
|
809
841
|
this._peerConnection.onconnectionstatechange = () => {
|
|
810
842
|
if (this._peerConnection && this._peerConnection.connectionState === "connected") {
|
|
811
843
|
this._webRTCConnected = true;
|
|
844
|
+
this.log("RTC: Peer connection established!");
|
|
812
845
|
} else if (this._peerConnection && (this._peerConnection.connectionState === "failed" || this._peerConnection.connectionState === "disconnected" || this._peerConnection.connectionState === "closed")) {
|
|
813
846
|
this._webRTCConnected = false;
|
|
847
|
+
this.log("RTC: Peer connection closed or failed");
|
|
814
848
|
}
|
|
815
849
|
};
|
|
816
850
|
this._peerConnection.onicegatheringstatechange = () => {
|
|
851
|
+
this.log(`RTC: ICE gathering state. _candidates:`, this._candidates.length, this._peerConnection?.iceGatheringState);
|
|
852
|
+
if (this._peerConnection?.iceGatheringState == "complete" && this._offerSent) {
|
|
853
|
+
for (let candidate of this._candidates) {
|
|
854
|
+
this.send({
|
|
855
|
+
c: "rtc-candidate",
|
|
856
|
+
type: "ice-candidate",
|
|
857
|
+
candidate
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
817
861
|
};
|
|
818
862
|
this._peerConnection.oniceconnectionstatechange = () => {
|
|
819
863
|
if (this._peerConnection && (this._peerConnection.iceConnectionState === "connected" || this._peerConnection.iceConnectionState === "completed")) {
|
|
@@ -833,7 +877,7 @@ var TopazCubeClient = class {
|
|
|
833
877
|
this._onRTCDisconnect();
|
|
834
878
|
};
|
|
835
879
|
this._dataChannel.onerror = (_error) => {
|
|
836
|
-
|
|
880
|
+
this.error("RTC: Client data channel error", _error);
|
|
837
881
|
};
|
|
838
882
|
this._peerConnection.ondatachannel = (event) => {
|
|
839
883
|
const dataChannel = event.channel;
|
|
@@ -864,17 +908,18 @@ var TopazCubeClient = class {
|
|
|
864
908
|
sdp: ld.sdp
|
|
865
909
|
};
|
|
866
910
|
this.send(offerPayload);
|
|
911
|
+
this._offerSent = true;
|
|
867
912
|
}
|
|
868
913
|
setTimeout(() => {
|
|
869
914
|
if (!this._webRTCConnected && this._peerConnection) {
|
|
870
915
|
if (this._peerConnection.iceConnectionState === "failed") {
|
|
871
|
-
|
|
916
|
+
this.log("RTC: Attempting ICE restart");
|
|
872
917
|
this._restartIce();
|
|
873
918
|
}
|
|
874
919
|
}
|
|
875
920
|
}, 5e3);
|
|
876
921
|
} catch (error) {
|
|
877
|
-
|
|
922
|
+
this.error("RTC: error:", error);
|
|
878
923
|
}
|
|
879
924
|
}
|
|
880
925
|
// Add this method to restart ICE if needed
|