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/src/client.ts
CHANGED
|
@@ -32,6 +32,7 @@ interface ConstructorParams {
|
|
|
32
32
|
autoReconnect?: boolean; // auto reconnect on disconnect
|
|
33
33
|
allowSync?: boolean; // allow sync on connect
|
|
34
34
|
allowWebRTC?: boolean;
|
|
35
|
+
DEBUG?: boolean;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
interface Message {
|
|
@@ -180,7 +181,7 @@ declare global {
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
export default class TopazCubeClient {
|
|
183
|
-
|
|
184
|
+
DEBUG = false
|
|
184
185
|
CYCLE = 200 // update/patch rate in ms
|
|
185
186
|
url = ''
|
|
186
187
|
documents: { [key: string]: Document } = {}
|
|
@@ -211,6 +212,9 @@ export default class TopazCubeClient {
|
|
|
211
212
|
ID = 0
|
|
212
213
|
socket: WebSocket | null = null
|
|
213
214
|
_peerConnection: RTCPeerConnection | null = null
|
|
215
|
+
_candidates: RTCIceCandidate[] = []
|
|
216
|
+
_remoteCandidates: RTCIceCandidateInit[] = []
|
|
217
|
+
_offerSent: boolean = false
|
|
214
218
|
_dataChannel: RTCDataChannel | null = null // our data channel
|
|
215
219
|
_serverDataChannel: RTCDataChannel | null = null // server data channel
|
|
216
220
|
_webRTCConnected: boolean = false
|
|
@@ -233,14 +237,32 @@ export default class TopazCubeClient {
|
|
|
233
237
|
autoReconnect = true, // auto reconnect on disconnect
|
|
234
238
|
allowSync = true, // allow sync on connect
|
|
235
239
|
allowWebRTC = false,
|
|
240
|
+
DEBUG = false
|
|
236
241
|
}: ConstructorParams) {
|
|
237
242
|
this.url = url
|
|
238
243
|
this.autoReconnect = autoReconnect
|
|
239
244
|
this.allowSync = allowSync
|
|
240
245
|
this.allowWebRTC = allowWebRTC
|
|
241
246
|
this.socket = null
|
|
247
|
+
this.DEBUG = DEBUG
|
|
242
248
|
this._startLoop()
|
|
243
|
-
|
|
249
|
+
this.log('Client initialized')
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
log(...args: any[]) {
|
|
253
|
+
if (this.DEBUG) {
|
|
254
|
+
console.log(...args);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
warn(...args: any[]) {
|
|
259
|
+
if (this.DEBUG) {
|
|
260
|
+
console.warn(...args);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
error(...args: any[]) {
|
|
265
|
+
console.error(...args);
|
|
244
266
|
}
|
|
245
267
|
|
|
246
268
|
/*= UPDATE ===================================================================*/
|
|
@@ -314,21 +336,34 @@ export default class TopazCubeClient {
|
|
|
314
336
|
let t2 = e._lpostime2
|
|
315
337
|
const interval = t2 - t1;
|
|
316
338
|
const elapsed = now - t1;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
339
|
+
e.pelapsed = elapsed
|
|
340
|
+
/*if (elapsed > 5000) {
|
|
341
|
+
} else */if (elapsed > 1000) {
|
|
342
|
+
vec3.copy(e.position!, e._lpos2!)
|
|
343
|
+
e._changed_position = now
|
|
344
|
+
} else {
|
|
345
|
+
const alpha = Math.max(0, elapsed / interval)
|
|
346
|
+
vec3.lerp(this._dpos, e._lpos1!, e._lpos2!, alpha)
|
|
347
|
+
vec3.lerp(e.position!, e.position!, this._dpos, 0.07)
|
|
348
|
+
e._changed_position = now
|
|
349
|
+
}
|
|
321
350
|
}
|
|
322
351
|
if (e._lrottime1 && e._lrottime2) {
|
|
323
|
-
|
|
324
352
|
let t1 = e._lrottime1
|
|
325
353
|
let t2 = e._lrottime2
|
|
326
354
|
const interval = t2 - t1;
|
|
327
355
|
const elapsed = now - t1;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
356
|
+
e.relapsed = elapsed
|
|
357
|
+
/*if (elapsed > 5000) {
|
|
358
|
+
} else */if (elapsed > 1000) {
|
|
359
|
+
quat.copy(e.rotation!, e._lrot2!)
|
|
360
|
+
e._changed_rotation = now
|
|
361
|
+
} else {
|
|
362
|
+
const alpha = Math.max(0, elapsed / interval)
|
|
363
|
+
quat.slerp(this._drot, e._lrot1!, e._lrot2!, alpha)
|
|
364
|
+
quat.slerp(e.rotation!, e.rotation!, this._drot, 0.07)
|
|
365
|
+
e._changed_rotation = now
|
|
366
|
+
}
|
|
332
367
|
}
|
|
333
368
|
|
|
334
369
|
}
|
|
@@ -354,7 +389,7 @@ export default class TopazCubeClient {
|
|
|
354
389
|
}
|
|
355
390
|
this.isConnecting = true
|
|
356
391
|
this._clear()
|
|
357
|
-
|
|
392
|
+
this.log('connecting...')
|
|
358
393
|
|
|
359
394
|
this.socket = new WebSocket(this.url)
|
|
360
395
|
|
|
@@ -475,7 +510,7 @@ export default class TopazCubeClient {
|
|
|
475
510
|
this.socket.send(enc)
|
|
476
511
|
}
|
|
477
512
|
} catch (e) {
|
|
478
|
-
|
|
513
|
+
this.error('send failed', e)
|
|
479
514
|
}
|
|
480
515
|
}
|
|
481
516
|
|
|
@@ -487,7 +522,7 @@ export default class TopazCubeClient {
|
|
|
487
522
|
async _onMessage(message: Message) {
|
|
488
523
|
let time = Date.now()
|
|
489
524
|
if (message.c == 'full') {
|
|
490
|
-
//
|
|
525
|
+
//this.log('full:', message)
|
|
491
526
|
let name:string = ''+message.n
|
|
492
527
|
let doc = message.doc
|
|
493
528
|
this.documents[name] = doc
|
|
@@ -517,7 +552,7 @@ export default class TopazCubeClient {
|
|
|
517
552
|
try {
|
|
518
553
|
applyOperation(this.documents[name], dop)
|
|
519
554
|
} catch (e) {
|
|
520
|
-
|
|
555
|
+
this.error('applyOperation failed for', name, 'with op', dop, e)
|
|
521
556
|
}
|
|
522
557
|
}
|
|
523
558
|
this.isPatched = false
|
|
@@ -526,7 +561,7 @@ export default class TopazCubeClient {
|
|
|
526
561
|
this.onChange(name, this.documents[name], message.doc)
|
|
527
562
|
}
|
|
528
563
|
} else if (message.c == 'chunk') {
|
|
529
|
-
//
|
|
564
|
+
//this.log('chunk', message)
|
|
530
565
|
this._chunks[message.mid+'-'+message.seq] = message
|
|
531
566
|
if (message.last) {
|
|
532
567
|
let cfound = 0
|
|
@@ -542,25 +577,25 @@ export default class TopazCubeClient {
|
|
|
542
577
|
delete this._chunks[cid]
|
|
543
578
|
}
|
|
544
579
|
}
|
|
545
|
-
//
|
|
580
|
+
//this.log('found chunks ', cfound, 'of', message.seq + 1)
|
|
546
581
|
if (cfound == message.seq + 1) {
|
|
547
582
|
try {
|
|
548
583
|
let cdec = await decompress(cdata)
|
|
549
584
|
let cdecu = new Uint8Array(cdec)
|
|
550
585
|
let nmessage = decode(cdecu)
|
|
551
|
-
//
|
|
586
|
+
//this.log('decoded message', nmessage)
|
|
552
587
|
this._onMessage(nmessage)
|
|
553
588
|
} catch (error) {
|
|
554
|
-
|
|
589
|
+
this.error('Error decoding chunks:', error)
|
|
555
590
|
}
|
|
556
591
|
} else {
|
|
557
|
-
|
|
592
|
+
this.warn('missing chunks', cfound, 'of', message.seq + 1)
|
|
558
593
|
}
|
|
559
594
|
}
|
|
560
595
|
} else if (message.c == 'fpatch') {
|
|
561
596
|
time = Date.now()
|
|
562
597
|
let name = message.n
|
|
563
|
-
//
|
|
598
|
+
//this.log('fpatch', message)
|
|
564
599
|
let doPatch = true
|
|
565
600
|
if (!this._lastUpdateId[name]) {
|
|
566
601
|
this._lastUpdateId[name] = message.u
|
|
@@ -568,13 +603,13 @@ export default class TopazCubeClient {
|
|
|
568
603
|
if (this._lastUpdateId[name] < message.u) {
|
|
569
604
|
let lp = message.u - this._lastUpdateId[name] - 1
|
|
570
605
|
if (lp > 0) {
|
|
571
|
-
|
|
606
|
+
this.warn('Lost ' + lp + ' updates')
|
|
572
607
|
}
|
|
573
608
|
this._lastUpdateId[name] = message.u
|
|
574
609
|
} else if (this._lastUpdateId[name] > message.u) {
|
|
575
610
|
// Handle the case where the server's update ID is older than the client's
|
|
576
611
|
// This could be due to a network issue or a clock skew
|
|
577
|
-
|
|
612
|
+
this.warn(`Received outdated update ID for document ${name}: ${message.u} < ${this._lastUpdateId[name]}`)
|
|
578
613
|
doPatch = false
|
|
579
614
|
}
|
|
580
615
|
}
|
|
@@ -590,12 +625,12 @@ export default class TopazCubeClient {
|
|
|
590
625
|
this.send({ c: 'peng', ct: Date.now(), st: stime })
|
|
591
626
|
this.stats.stdiff = stime + ping / 2 - time
|
|
592
627
|
this.stats.ping = ping
|
|
593
|
-
|
|
628
|
+
this.log('ping', ping, 'ms', 'stdiff', this.stats.stdiff, 'ms')
|
|
594
629
|
} else if (message.c == 'rtc-offer') {
|
|
595
|
-
|
|
630
|
+
this.log("RTC: offer received:", message);
|
|
596
631
|
// You might need to handle this if the server sends offers
|
|
597
632
|
} else if (message.c == 'rtc-answer') {
|
|
598
|
-
|
|
633
|
+
this.log("RTC: answer received:", message);
|
|
599
634
|
try {
|
|
600
635
|
const sessionDesc = new RTCSessionDescription({
|
|
601
636
|
type: message.type,
|
|
@@ -604,39 +639,36 @@ export default class TopazCubeClient {
|
|
|
604
639
|
if (this._peerConnection) {
|
|
605
640
|
await this._peerConnection.setRemoteDescription(sessionDesc)
|
|
606
641
|
}
|
|
607
|
-
|
|
642
|
+
this.log("RTC: Remote description set successfully");
|
|
608
643
|
|
|
609
644
|
// Log the current state after setting remote description
|
|
610
|
-
//
|
|
611
|
-
//
|
|
645
|
+
//
|
|
646
|
+
//this.log("RTC: Current connection state:", this._peerConnection.connectionState);
|
|
647
|
+
//this.log("RTC: Current ICE connection state:", this._peerConnection.iceConnectionState);
|
|
648
|
+
for (let candidate of this._remoteCandidates) {
|
|
649
|
+
try {
|
|
650
|
+
await this._peerConnection?.addIceCandidate(candidate);
|
|
651
|
+
this.log("RTC: Added remote ICE candidate:", candidate);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
this.error("RTC: Error adding remote ICE candidate:", error);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
612
656
|
} catch (error) {
|
|
613
|
-
|
|
657
|
+
this.error('RTC: Error setting remote description:', error)
|
|
614
658
|
}
|
|
615
659
|
} else if (message.c == 'rtc-candidate') {
|
|
616
|
-
|
|
617
|
-
try {
|
|
660
|
+
this.log("RTC: candidate received", message);
|
|
618
661
|
if (this._peerConnection && message.candidate) {
|
|
619
|
-
|
|
620
|
-
//new RTCIceCandidate(message.candidate)
|
|
621
|
-
message.candidate
|
|
622
|
-
)
|
|
623
|
-
//console.log("RTC: ICE candidate added successfully");
|
|
624
|
-
} else {
|
|
625
|
-
console.warn(
|
|
626
|
-
'RTC: Received candidate but peerConnection not ready or candidate missing'
|
|
627
|
-
)
|
|
662
|
+
this._remoteCandidates.push(message.candidate);
|
|
628
663
|
}
|
|
629
|
-
} catch (error) {
|
|
630
|
-
//console.error("RTC: Error adding ICE candidate:", error);
|
|
631
|
-
}
|
|
632
664
|
} else {
|
|
633
665
|
this.onMessage(message)
|
|
634
666
|
}
|
|
635
667
|
}
|
|
636
668
|
|
|
637
669
|
_onDocumentChange(name: string, op: any, target: any, path: string, value: any) {
|
|
638
|
-
if (this.
|
|
639
|
-
|
|
670
|
+
if (this.DEBUG) {
|
|
671
|
+
this.log('Document change:', name, op, target, path, value)
|
|
640
672
|
}
|
|
641
673
|
if (this.isPatched || !this.allowSync) {
|
|
642
674
|
return
|
|
@@ -712,12 +744,12 @@ export default class TopazCubeClient {
|
|
|
712
744
|
offset += 4
|
|
713
745
|
let e: Entity = entities[id]
|
|
714
746
|
if (!e) {
|
|
715
|
-
//
|
|
747
|
+
//this.log('Entity not found:', id)
|
|
716
748
|
continue
|
|
717
749
|
}
|
|
718
750
|
let value = rdict[did]
|
|
719
751
|
e[key] = value
|
|
720
|
-
//
|
|
752
|
+
//this.log('FCHANGE', key, id, did, value, rdict)
|
|
721
753
|
e['_changed_'+key] = time
|
|
722
754
|
}
|
|
723
755
|
} else {
|
|
@@ -830,13 +862,13 @@ export default class TopazCubeClient {
|
|
|
830
862
|
}
|
|
831
863
|
|
|
832
864
|
_onRTCConnect() {
|
|
833
|
-
|
|
865
|
+
this.log('RTC: Connected')
|
|
834
866
|
this.send({ c: 'test', message: 'Hello RTC from client' })
|
|
835
867
|
}
|
|
836
868
|
|
|
837
869
|
_onRTCDisconnect() {
|
|
838
870
|
this._webRTCConnected = false
|
|
839
|
-
|
|
871
|
+
this.log('RTC: Disconnected')
|
|
840
872
|
}
|
|
841
873
|
|
|
842
874
|
async _onRTCMessage(data: ArrayBuffer) {
|
|
@@ -849,8 +881,11 @@ export default class TopazCubeClient {
|
|
|
849
881
|
}
|
|
850
882
|
|
|
851
883
|
async _initializeWebRTC() {
|
|
852
|
-
//
|
|
884
|
+
//this.log("RTC: _initializeWebRTC")
|
|
853
885
|
this._peerConnection = null
|
|
886
|
+
this._candidates = []
|
|
887
|
+
this._remoteCandidates = []
|
|
888
|
+
this._offerSent = false
|
|
854
889
|
try {
|
|
855
890
|
// Create RTCPeerConnection with more comprehensive STUN server list
|
|
856
891
|
this._peerConnection = new RTCPeerConnection({
|
|
@@ -862,27 +897,24 @@ export default class TopazCubeClient {
|
|
|
862
897
|
iceCandidatePoolSize: 10,
|
|
863
898
|
})
|
|
864
899
|
|
|
865
|
-
//
|
|
900
|
+
//this.log("RTC: peerConnection created", this._peerConnection)
|
|
866
901
|
|
|
867
902
|
// Handle ICE candidates
|
|
868
903
|
this._peerConnection.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
|
|
869
|
-
//
|
|
904
|
+
//this.log("RTC: onicecandidate", event.candidate)
|
|
870
905
|
if (event.candidate) {
|
|
871
|
-
this.
|
|
872
|
-
c: 'rtc-candidate',
|
|
873
|
-
type: 'ice-candidate',
|
|
874
|
-
candidate: event.candidate,
|
|
875
|
-
})
|
|
906
|
+
this._candidates.push(event.candidate)
|
|
876
907
|
} else {
|
|
877
|
-
|
|
908
|
+
this.log("RTC: ICE candidate gathering complete")
|
|
878
909
|
}
|
|
879
910
|
}
|
|
880
911
|
|
|
881
912
|
// Log connection state changes
|
|
882
913
|
this._peerConnection.onconnectionstatechange = () => {
|
|
883
|
-
//
|
|
914
|
+
//this.log(`RTC: Connection state changed: ${this._peerConnection.connectionState}`)
|
|
884
915
|
if (this._peerConnection && this._peerConnection.connectionState === 'connected') {
|
|
885
916
|
this._webRTCConnected = true
|
|
917
|
+
this.log('RTC: Peer connection established!')
|
|
886
918
|
} else if (
|
|
887
919
|
this._peerConnection && (
|
|
888
920
|
this._peerConnection.connectionState === 'failed' ||
|
|
@@ -890,15 +922,25 @@ export default class TopazCubeClient {
|
|
|
890
922
|
this._peerConnection.connectionState === 'closed')
|
|
891
923
|
) {
|
|
892
924
|
this._webRTCConnected = false
|
|
925
|
+
this.log('RTC: Peer connection closed or failed')
|
|
893
926
|
}
|
|
894
927
|
}
|
|
895
928
|
|
|
896
929
|
this._peerConnection.onicegatheringstatechange = () => {
|
|
897
|
-
|
|
930
|
+
this.log(`RTC: ICE gathering state. _candidates:`, this._candidates.length, this._peerConnection?.iceGatheringState)
|
|
931
|
+
if (this._peerConnection?.iceGatheringState == 'complete' && this._offerSent) {
|
|
932
|
+
for (let candidate of this._candidates) {
|
|
933
|
+
this.send({
|
|
934
|
+
c: 'rtc-candidate',
|
|
935
|
+
type: 'ice-candidate',
|
|
936
|
+
candidate: candidate,
|
|
937
|
+
})
|
|
938
|
+
}
|
|
939
|
+
}
|
|
898
940
|
}
|
|
899
941
|
|
|
900
942
|
this._peerConnection.oniceconnectionstatechange = () => {
|
|
901
|
-
//
|
|
943
|
+
//this.log(`RTC: ICE connection state: ${this._peerConnection.iceConnectionState}`)
|
|
902
944
|
|
|
903
945
|
// This is critical - when ICE succeeds, the connection should be established
|
|
904
946
|
if (
|
|
@@ -906,7 +948,7 @@ export default class TopazCubeClient {
|
|
|
906
948
|
this._peerConnection.iceConnectionState === 'connected' ||
|
|
907
949
|
this._peerConnection.iceConnectionState === 'completed')
|
|
908
950
|
) {
|
|
909
|
-
//
|
|
951
|
+
//this.log("RTC: ICE connection established!")
|
|
910
952
|
}
|
|
911
953
|
}
|
|
912
954
|
|
|
@@ -928,25 +970,25 @@ export default class TopazCubeClient {
|
|
|
928
970
|
}
|
|
929
971
|
|
|
930
972
|
this._dataChannel.onerror = (_error: Event) => {
|
|
931
|
-
|
|
973
|
+
this.error('RTC: Client data channel error', _error)
|
|
932
974
|
}
|
|
933
975
|
|
|
934
976
|
// Handle data channels created by the server
|
|
935
977
|
this._peerConnection.ondatachannel = (event: RTCDataChannelEvent) => {
|
|
936
|
-
//
|
|
978
|
+
//this.log("RTC: Server data channel received", event.channel.label);
|
|
937
979
|
const dataChannel = event.channel
|
|
938
980
|
this._serverDataChannel = dataChannel
|
|
939
981
|
|
|
940
982
|
dataChannel.onopen = () => {
|
|
941
|
-
//
|
|
983
|
+
//this.log("RTC: Server data channel open");
|
|
942
984
|
}
|
|
943
985
|
|
|
944
986
|
dataChannel.onclose = () => {
|
|
945
|
-
//
|
|
987
|
+
//this.log("RTC: Server data channel closed");
|
|
946
988
|
}
|
|
947
989
|
|
|
948
990
|
dataChannel.onerror = (_error: Event) => {
|
|
949
|
-
//
|
|
991
|
+
//this.error("RTC: Server data channel error", error);
|
|
950
992
|
}
|
|
951
993
|
|
|
952
994
|
dataChannel.onmessage = (event: MessageEvent) => {
|
|
@@ -962,14 +1004,14 @@ export default class TopazCubeClient {
|
|
|
962
1004
|
}
|
|
963
1005
|
|
|
964
1006
|
const offer = await this._peerConnection.createOffer(offerOptions)
|
|
965
|
-
//
|
|
1007
|
+
//this.log("RTC: our offer:", offer);
|
|
966
1008
|
await this._peerConnection.setLocalDescription(offer)
|
|
967
1009
|
|
|
968
1010
|
// Wait a moment to ensure the local description is set
|
|
969
1011
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
970
1012
|
|
|
971
1013
|
let ld = this._peerConnection.localDescription
|
|
972
|
-
//
|
|
1014
|
+
//this.log("RTC: our localDescription", ld);
|
|
973
1015
|
|
|
974
1016
|
if (ld) {
|
|
975
1017
|
const offerPayload = {
|
|
@@ -977,28 +1019,29 @@ export default class TopazCubeClient {
|
|
|
977
1019
|
type: ld.type,
|
|
978
1020
|
sdp: ld.sdp,
|
|
979
1021
|
}
|
|
980
|
-
//
|
|
1022
|
+
//this.log("RTC: our offer payload", offerPayload);
|
|
981
1023
|
this.send(offerPayload)
|
|
1024
|
+
this._offerSent = true
|
|
982
1025
|
}
|
|
983
1026
|
|
|
984
1027
|
// Set a timeout to check connection status
|
|
985
1028
|
setTimeout(() => {
|
|
986
1029
|
if (!this._webRTCConnected && this._peerConnection) {
|
|
987
1030
|
/*
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1031
|
+
this.log("RTC: Connection not established after timeout, current states:");
|
|
1032
|
+
this.log("Connection state:", this._peerConnection.connectionState);
|
|
1033
|
+
this.log("ICE connection state:", this._peerConnection.iceConnectionState);
|
|
1034
|
+
this.log("ICE gathering state:", this._peerConnection.iceGatheringState);
|
|
992
1035
|
*/
|
|
993
1036
|
// Attempt to restart ICE if needed
|
|
994
1037
|
if (this._peerConnection.iceConnectionState === 'failed') {
|
|
995
|
-
|
|
1038
|
+
this.log('RTC: Attempting ICE restart')
|
|
996
1039
|
this._restartIce()
|
|
997
1040
|
}
|
|
998
1041
|
}
|
|
999
1042
|
}, 5000)
|
|
1000
1043
|
} catch (error) {
|
|
1001
|
-
|
|
1044
|
+
this.error('RTC: error:', error)
|
|
1002
1045
|
}
|
|
1003
1046
|
}
|
|
1004
1047
|
|
|
@@ -1019,10 +1062,10 @@ export default class TopazCubeClient {
|
|
|
1019
1062
|
type: offer?.type,
|
|
1020
1063
|
sdp: offer?.sdp,
|
|
1021
1064
|
}
|
|
1022
|
-
//
|
|
1065
|
+
//this.log("RTC: ICE restart offer payload", offerPayload);
|
|
1023
1066
|
this.send(offerPayload)
|
|
1024
1067
|
} catch (error) {
|
|
1025
|
-
//
|
|
1068
|
+
//this.error("RTC: Error during ICE restart:", error);
|
|
1026
1069
|
}
|
|
1027
1070
|
}
|
|
1028
1071
|
|
package/src/server.ts
CHANGED
|
@@ -19,7 +19,7 @@ import fastjsonpatch from 'fast-json-patch'
|
|
|
19
19
|
import { WebSocketServer, WebSocket } from 'ws'
|
|
20
20
|
import { MongoClient, Db } from 'mongodb'
|
|
21
21
|
import { glMatrix, vec3, quat } from 'gl-matrix'
|
|
22
|
-
|
|
22
|
+
import { RTCPeerConnection, RTCDataChannel, RTCSessionDescription } from "werift";
|
|
23
23
|
|
|
24
24
|
glMatrix.setMatrixArrayType(Array)
|
|
25
25
|
|
|
@@ -73,6 +73,7 @@ interface StatsType {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export default class TopazCubeServer {
|
|
76
|
+
DEBUG = false
|
|
76
77
|
name = 'TopazCubeServer'
|
|
77
78
|
cycle = 100
|
|
78
79
|
patchCycleDivider = 1
|
|
@@ -91,7 +92,6 @@ export default class TopazCubeServer {
|
|
|
91
92
|
allowFastPatch = false
|
|
92
93
|
allowCompression = false
|
|
93
94
|
simulateLatency = 0
|
|
94
|
-
|
|
95
95
|
_lastUID = 100
|
|
96
96
|
clients: ClientType[] = []
|
|
97
97
|
documents: Record<string, any> = {}
|
|
@@ -118,11 +118,15 @@ export default class TopazCubeServer {
|
|
|
118
118
|
_exited = false
|
|
119
119
|
|
|
120
120
|
log(...args: any[]) {
|
|
121
|
+
if (this.DEBUG) {
|
|
121
122
|
console.log(this.name + ':', ...args);
|
|
123
|
+
}
|
|
122
124
|
}
|
|
123
125
|
|
|
124
126
|
warn(...args: any[]) {
|
|
127
|
+
if (this.DEBUG) {
|
|
125
128
|
console.warn(this.name + ':', ...args);
|
|
129
|
+
}
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
error(...args: any[]) {
|
|
@@ -145,6 +149,7 @@ export default class TopazCubeServer {
|
|
|
145
149
|
allowFastPatch = false,
|
|
146
150
|
allowCompression = false,
|
|
147
151
|
simulateLatency = 0,
|
|
152
|
+
DEBUG = false
|
|
148
153
|
}: {
|
|
149
154
|
name?: string
|
|
150
155
|
cycle?: number
|
|
@@ -161,6 +166,7 @@ export default class TopazCubeServer {
|
|
|
161
166
|
allowFastPatch?: boolean
|
|
162
167
|
allowCompression?: boolean
|
|
163
168
|
simulateLatency?: number
|
|
169
|
+
DEBUG?: boolean
|
|
164
170
|
} = {}) {
|
|
165
171
|
this.name = name
|
|
166
172
|
this.cycle = cycle
|
|
@@ -177,6 +183,7 @@ export default class TopazCubeServer {
|
|
|
177
183
|
this.allowFastPatch = allowFastPatch
|
|
178
184
|
this.allowCompression = allowCompression
|
|
179
185
|
this.simulateLatency = simulateLatency
|
|
186
|
+
this.DEBUG = DEBUG
|
|
180
187
|
|
|
181
188
|
this._initDB()
|
|
182
189
|
|
|
@@ -786,7 +793,7 @@ export default class TopazCubeServer {
|
|
|
786
793
|
n: name,
|
|
787
794
|
fdata: changes
|
|
788
795
|
}
|
|
789
|
-
|
|
796
|
+
this.broadcastRTC(record, sus)
|
|
790
797
|
let t3 = Date.now()
|
|
791
798
|
this.log(`_sendPatches: ${name} encode_changes: ${t2-t1}ms broadcast:${t3-t2}ms`)
|
|
792
799
|
}
|
|
@@ -813,19 +820,6 @@ export default class TopazCubeServer {
|
|
|
813
820
|
|
|
814
821
|
/*= WEBRTC ===================================================================*/
|
|
815
822
|
|
|
816
|
-
private _wrtc: any = null
|
|
817
|
-
|
|
818
|
-
private async _loadWebRTC(): Promise<any> {
|
|
819
|
-
if (!this._wrtc) {
|
|
820
|
-
try {
|
|
821
|
-
this._wrtc = await import('@roamhq/wrtc')
|
|
822
|
-
} catch (error) {
|
|
823
|
-
this.error('WebRTC module not available:', error)
|
|
824
|
-
throw new Error('WebRTC functionality requires @roamhq/wrtc and platform-specific binary packages')
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
return this._wrtc
|
|
828
|
-
}
|
|
829
823
|
|
|
830
824
|
async _processOffer(client: ClientType, data: any): Promise<void> {
|
|
831
825
|
if (!this.allowWebRTC) {
|
|
@@ -833,34 +827,34 @@ export default class TopazCubeServer {
|
|
|
833
827
|
return
|
|
834
828
|
}
|
|
835
829
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
const peerConnection = new
|
|
830
|
+
this.log("RTC: Processing offer from client", client.ID, data);
|
|
831
|
+
|
|
832
|
+
const peerConnection = new RTCPeerConnection({
|
|
839
833
|
iceServers: [
|
|
840
834
|
{ urls: 'stun:stun.l.google.com:19302' },
|
|
841
835
|
{ urls: 'stun:stun.cloudflare.com:3478' },
|
|
842
836
|
{ urls: 'stun:freestun.net:3478' },
|
|
843
837
|
],
|
|
844
|
-
iceCandidatePoolSize: 10,
|
|
838
|
+
//iceCandidatePoolSize: 10,
|
|
845
839
|
})
|
|
846
840
|
|
|
847
841
|
client.peerConnection = peerConnection
|
|
848
842
|
|
|
849
843
|
peerConnection.onicecandidate = (event: any) => {
|
|
850
844
|
if (event.candidate) {
|
|
851
|
-
|
|
845
|
+
this.log("RTC: ICE candidate generated", event.candidate.candidate.substring(0, 50) + "...");
|
|
852
846
|
this.send(client, {
|
|
853
847
|
c: 'rtc-candidate',
|
|
854
848
|
type: 'ice-candidate',
|
|
855
849
|
candidate: event.candidate, // .toJSON()
|
|
856
850
|
})
|
|
857
851
|
} else {
|
|
858
|
-
|
|
852
|
+
this.log("RTC: ICE candidate gathering complete");
|
|
859
853
|
}
|
|
860
854
|
}
|
|
861
855
|
|
|
862
856
|
peerConnection.onconnectionstatechange = () => {
|
|
863
|
-
|
|
857
|
+
this.log(`RTC: Connection state changed: ${peerConnection.connectionState}`);
|
|
864
858
|
if (peerConnection.connectionState === 'connected') {
|
|
865
859
|
client.webRTCConnected = true
|
|
866
860
|
this.log(`RTC: Connection established with client ${client.ID}`)
|
|
@@ -875,24 +869,26 @@ export default class TopazCubeServer {
|
|
|
875
869
|
}
|
|
876
870
|
|
|
877
871
|
peerConnection.onicegatheringstatechange = () => {
|
|
878
|
-
|
|
872
|
+
this.log(`RTC: ICE gathering state: ${peerConnection.iceGatheringState}`);
|
|
879
873
|
}
|
|
880
874
|
|
|
881
875
|
peerConnection.oniceconnectionstatechange = () => {
|
|
882
|
-
|
|
876
|
+
this.log(`RTC: ICE connection state: ${peerConnection.iceConnectionState}`);
|
|
883
877
|
if (
|
|
884
878
|
peerConnection.iceConnectionState === 'connected' ||
|
|
885
879
|
peerConnection.iceConnectionState === 'completed'
|
|
886
880
|
) {
|
|
887
|
-
|
|
881
|
+
this.log(`RTC: ICE connection established with client ${client.ID}`);
|
|
888
882
|
}
|
|
889
883
|
}
|
|
890
884
|
|
|
891
885
|
try {
|
|
886
|
+
this.log("RTC: Remote description set from data", data);
|
|
892
887
|
await peerConnection.setRemoteDescription(
|
|
893
|
-
|
|
888
|
+
//data
|
|
889
|
+
new RTCSessionDescription(data.sdp, data.type)
|
|
894
890
|
)
|
|
895
|
-
|
|
891
|
+
this.log("RTC: Remote description set successfully");
|
|
896
892
|
|
|
897
893
|
client.dataChannel = peerConnection.createDataChannel('serverchannel', {
|
|
898
894
|
ordered: true,
|
|
@@ -900,7 +896,7 @@ export default class TopazCubeServer {
|
|
|
900
896
|
})
|
|
901
897
|
|
|
902
898
|
client.dataChannel.onopen = () => {
|
|
903
|
-
|
|
899
|
+
this.log(`RTC: Data channel opened for client ${client.ID}`);
|
|
904
900
|
// Try sending a test message
|
|
905
901
|
try {
|
|
906
902
|
const testData = { c: 'test', message: 'Hello WebRTC' }
|
|
@@ -941,7 +937,7 @@ export default class TopazCubeServer {
|
|
|
941
937
|
const answer = await peerConnection.createAnswer()
|
|
942
938
|
await peerConnection.setLocalDescription(answer)
|
|
943
939
|
|
|
944
|
-
|
|
940
|
+
this.log(`RTC: Sending answer to client ${client.ID}`);
|
|
945
941
|
this.send(client, {
|
|
946
942
|
c: 'rtc-answer',
|
|
947
943
|
type: answer.type,
|
|
@@ -956,16 +952,19 @@ export default class TopazCubeServer {
|
|
|
956
952
|
}
|
|
957
953
|
|
|
958
954
|
async _processICECandidate(client: ClientType, data: any): Promise<void> {
|
|
959
|
-
|
|
955
|
+
this.log(`RTC: Processing ICE candidate from client ${client.ID}`);
|
|
960
956
|
try {
|
|
957
|
+
if (data.candidate && typeof(data.candidate) == 'object') {
|
|
958
|
+
data.candidate = data.candidate.candidate
|
|
959
|
+
}
|
|
961
960
|
if (client.peerConnection && data.candidate) {
|
|
962
961
|
await client.peerConnection.addIceCandidate(
|
|
963
962
|
data.candidate
|
|
964
963
|
//new wrtc.RTCIceCandidate(data.candidate)
|
|
965
964
|
)
|
|
966
|
-
|
|
965
|
+
this.log(`RTC: ICE candidate added successfully for client ${client.ID}`);
|
|
967
966
|
} else {
|
|
968
|
-
|
|
967
|
+
this.warn(`RTC: Cannot add ICE candidate for client ${client.ID} - peerConnection not ready or candidate missing`, client.peerConnection, data);
|
|
969
968
|
}
|
|
970
969
|
} catch (error) {
|
|
971
970
|
this.error(`RTC: Error adding ICE candidate for client ${client.ID}`)
|