topazcube 0.1.21 → 0.1.22
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 +69 -36
- 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 +69 -36
- 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 +8 -14
- package/src/client.ts +100 -70
- 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 ===================================================================*/
|
|
@@ -354,7 +376,7 @@ export default class TopazCubeClient {
|
|
|
354
376
|
}
|
|
355
377
|
this.isConnecting = true
|
|
356
378
|
this._clear()
|
|
357
|
-
|
|
379
|
+
this.log('connecting...')
|
|
358
380
|
|
|
359
381
|
this.socket = new WebSocket(this.url)
|
|
360
382
|
|
|
@@ -475,7 +497,7 @@ export default class TopazCubeClient {
|
|
|
475
497
|
this.socket.send(enc)
|
|
476
498
|
}
|
|
477
499
|
} catch (e) {
|
|
478
|
-
|
|
500
|
+
this.error('send failed', e)
|
|
479
501
|
}
|
|
480
502
|
}
|
|
481
503
|
|
|
@@ -487,7 +509,7 @@ export default class TopazCubeClient {
|
|
|
487
509
|
async _onMessage(message: Message) {
|
|
488
510
|
let time = Date.now()
|
|
489
511
|
if (message.c == 'full') {
|
|
490
|
-
//
|
|
512
|
+
//this.log('full:', message)
|
|
491
513
|
let name:string = ''+message.n
|
|
492
514
|
let doc = message.doc
|
|
493
515
|
this.documents[name] = doc
|
|
@@ -517,7 +539,7 @@ export default class TopazCubeClient {
|
|
|
517
539
|
try {
|
|
518
540
|
applyOperation(this.documents[name], dop)
|
|
519
541
|
} catch (e) {
|
|
520
|
-
|
|
542
|
+
this.error('applyOperation failed for', name, 'with op', dop, e)
|
|
521
543
|
}
|
|
522
544
|
}
|
|
523
545
|
this.isPatched = false
|
|
@@ -526,7 +548,7 @@ export default class TopazCubeClient {
|
|
|
526
548
|
this.onChange(name, this.documents[name], message.doc)
|
|
527
549
|
}
|
|
528
550
|
} else if (message.c == 'chunk') {
|
|
529
|
-
//
|
|
551
|
+
//this.log('chunk', message)
|
|
530
552
|
this._chunks[message.mid+'-'+message.seq] = message
|
|
531
553
|
if (message.last) {
|
|
532
554
|
let cfound = 0
|
|
@@ -542,25 +564,25 @@ export default class TopazCubeClient {
|
|
|
542
564
|
delete this._chunks[cid]
|
|
543
565
|
}
|
|
544
566
|
}
|
|
545
|
-
//
|
|
567
|
+
//this.log('found chunks ', cfound, 'of', message.seq + 1)
|
|
546
568
|
if (cfound == message.seq + 1) {
|
|
547
569
|
try {
|
|
548
570
|
let cdec = await decompress(cdata)
|
|
549
571
|
let cdecu = new Uint8Array(cdec)
|
|
550
572
|
let nmessage = decode(cdecu)
|
|
551
|
-
//
|
|
573
|
+
//this.log('decoded message', nmessage)
|
|
552
574
|
this._onMessage(nmessage)
|
|
553
575
|
} catch (error) {
|
|
554
|
-
|
|
576
|
+
this.error('Error decoding chunks:', error)
|
|
555
577
|
}
|
|
556
578
|
} else {
|
|
557
|
-
|
|
579
|
+
this.warn('missing chunks', cfound, 'of', message.seq + 1)
|
|
558
580
|
}
|
|
559
581
|
}
|
|
560
582
|
} else if (message.c == 'fpatch') {
|
|
561
583
|
time = Date.now()
|
|
562
584
|
let name = message.n
|
|
563
|
-
//
|
|
585
|
+
//this.log('fpatch', message)
|
|
564
586
|
let doPatch = true
|
|
565
587
|
if (!this._lastUpdateId[name]) {
|
|
566
588
|
this._lastUpdateId[name] = message.u
|
|
@@ -568,13 +590,13 @@ export default class TopazCubeClient {
|
|
|
568
590
|
if (this._lastUpdateId[name] < message.u) {
|
|
569
591
|
let lp = message.u - this._lastUpdateId[name] - 1
|
|
570
592
|
if (lp > 0) {
|
|
571
|
-
|
|
593
|
+
this.warn('Lost ' + lp + ' updates')
|
|
572
594
|
}
|
|
573
595
|
this._lastUpdateId[name] = message.u
|
|
574
596
|
} else if (this._lastUpdateId[name] > message.u) {
|
|
575
597
|
// Handle the case where the server's update ID is older than the client's
|
|
576
598
|
// This could be due to a network issue or a clock skew
|
|
577
|
-
|
|
599
|
+
this.warn(`Received outdated update ID for document ${name}: ${message.u} < ${this._lastUpdateId[name]}`)
|
|
578
600
|
doPatch = false
|
|
579
601
|
}
|
|
580
602
|
}
|
|
@@ -590,12 +612,12 @@ export default class TopazCubeClient {
|
|
|
590
612
|
this.send({ c: 'peng', ct: Date.now(), st: stime })
|
|
591
613
|
this.stats.stdiff = stime + ping / 2 - time
|
|
592
614
|
this.stats.ping = ping
|
|
593
|
-
|
|
615
|
+
this.log('ping', ping, 'ms', 'stdiff', this.stats.stdiff, 'ms')
|
|
594
616
|
} else if (message.c == 'rtc-offer') {
|
|
595
|
-
|
|
617
|
+
this.log("RTC: offer received:", message);
|
|
596
618
|
// You might need to handle this if the server sends offers
|
|
597
619
|
} else if (message.c == 'rtc-answer') {
|
|
598
|
-
|
|
620
|
+
this.log("RTC: answer received:", message);
|
|
599
621
|
try {
|
|
600
622
|
const sessionDesc = new RTCSessionDescription({
|
|
601
623
|
type: message.type,
|
|
@@ -604,39 +626,36 @@ export default class TopazCubeClient {
|
|
|
604
626
|
if (this._peerConnection) {
|
|
605
627
|
await this._peerConnection.setRemoteDescription(sessionDesc)
|
|
606
628
|
}
|
|
607
|
-
|
|
629
|
+
this.log("RTC: Remote description set successfully");
|
|
608
630
|
|
|
609
631
|
// Log the current state after setting remote description
|
|
610
|
-
//
|
|
611
|
-
//
|
|
632
|
+
//
|
|
633
|
+
//this.log("RTC: Current connection state:", this._peerConnection.connectionState);
|
|
634
|
+
//this.log("RTC: Current ICE connection state:", this._peerConnection.iceConnectionState);
|
|
635
|
+
for (let candidate of this._remoteCandidates) {
|
|
636
|
+
try {
|
|
637
|
+
await this._peerConnection?.addIceCandidate(candidate);
|
|
638
|
+
this.log("RTC: Added remote ICE candidate:", candidate);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
this.error("RTC: Error adding remote ICE candidate:", error);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
612
643
|
} catch (error) {
|
|
613
|
-
|
|
644
|
+
this.error('RTC: Error setting remote description:', error)
|
|
614
645
|
}
|
|
615
646
|
} else if (message.c == 'rtc-candidate') {
|
|
616
|
-
|
|
617
|
-
try {
|
|
647
|
+
this.log("RTC: candidate received", message);
|
|
618
648
|
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
|
-
)
|
|
649
|
+
this._remoteCandidates.push(message.candidate);
|
|
628
650
|
}
|
|
629
|
-
} catch (error) {
|
|
630
|
-
//console.error("RTC: Error adding ICE candidate:", error);
|
|
631
|
-
}
|
|
632
651
|
} else {
|
|
633
652
|
this.onMessage(message)
|
|
634
653
|
}
|
|
635
654
|
}
|
|
636
655
|
|
|
637
656
|
_onDocumentChange(name: string, op: any, target: any, path: string, value: any) {
|
|
638
|
-
if (this.
|
|
639
|
-
|
|
657
|
+
if (this.DEBUG) {
|
|
658
|
+
this.log('Document change:', name, op, target, path, value)
|
|
640
659
|
}
|
|
641
660
|
if (this.isPatched || !this.allowSync) {
|
|
642
661
|
return
|
|
@@ -712,12 +731,12 @@ export default class TopazCubeClient {
|
|
|
712
731
|
offset += 4
|
|
713
732
|
let e: Entity = entities[id]
|
|
714
733
|
if (!e) {
|
|
715
|
-
//
|
|
734
|
+
//this.log('Entity not found:', id)
|
|
716
735
|
continue
|
|
717
736
|
}
|
|
718
737
|
let value = rdict[did]
|
|
719
738
|
e[key] = value
|
|
720
|
-
//
|
|
739
|
+
//this.log('FCHANGE', key, id, did, value, rdict)
|
|
721
740
|
e['_changed_'+key] = time
|
|
722
741
|
}
|
|
723
742
|
} else {
|
|
@@ -830,13 +849,13 @@ export default class TopazCubeClient {
|
|
|
830
849
|
}
|
|
831
850
|
|
|
832
851
|
_onRTCConnect() {
|
|
833
|
-
|
|
852
|
+
this.log('RTC: Connected')
|
|
834
853
|
this.send({ c: 'test', message: 'Hello RTC from client' })
|
|
835
854
|
}
|
|
836
855
|
|
|
837
856
|
_onRTCDisconnect() {
|
|
838
857
|
this._webRTCConnected = false
|
|
839
|
-
|
|
858
|
+
this.log('RTC: Disconnected')
|
|
840
859
|
}
|
|
841
860
|
|
|
842
861
|
async _onRTCMessage(data: ArrayBuffer) {
|
|
@@ -849,8 +868,11 @@ export default class TopazCubeClient {
|
|
|
849
868
|
}
|
|
850
869
|
|
|
851
870
|
async _initializeWebRTC() {
|
|
852
|
-
//
|
|
871
|
+
//this.log("RTC: _initializeWebRTC")
|
|
853
872
|
this._peerConnection = null
|
|
873
|
+
this._candidates = []
|
|
874
|
+
this._remoteCandidates = []
|
|
875
|
+
this._offerSent = false
|
|
854
876
|
try {
|
|
855
877
|
// Create RTCPeerConnection with more comprehensive STUN server list
|
|
856
878
|
this._peerConnection = new RTCPeerConnection({
|
|
@@ -862,27 +884,24 @@ export default class TopazCubeClient {
|
|
|
862
884
|
iceCandidatePoolSize: 10,
|
|
863
885
|
})
|
|
864
886
|
|
|
865
|
-
//
|
|
887
|
+
//this.log("RTC: peerConnection created", this._peerConnection)
|
|
866
888
|
|
|
867
889
|
// Handle ICE candidates
|
|
868
890
|
this._peerConnection.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
|
|
869
|
-
//
|
|
891
|
+
//this.log("RTC: onicecandidate", event.candidate)
|
|
870
892
|
if (event.candidate) {
|
|
871
|
-
this.
|
|
872
|
-
c: 'rtc-candidate',
|
|
873
|
-
type: 'ice-candidate',
|
|
874
|
-
candidate: event.candidate,
|
|
875
|
-
})
|
|
893
|
+
this._candidates.push(event.candidate)
|
|
876
894
|
} else {
|
|
877
|
-
|
|
895
|
+
this.log("RTC: ICE candidate gathering complete")
|
|
878
896
|
}
|
|
879
897
|
}
|
|
880
898
|
|
|
881
899
|
// Log connection state changes
|
|
882
900
|
this._peerConnection.onconnectionstatechange = () => {
|
|
883
|
-
//
|
|
901
|
+
//this.log(`RTC: Connection state changed: ${this._peerConnection.connectionState}`)
|
|
884
902
|
if (this._peerConnection && this._peerConnection.connectionState === 'connected') {
|
|
885
903
|
this._webRTCConnected = true
|
|
904
|
+
this.log('RTC: Peer connection established!')
|
|
886
905
|
} else if (
|
|
887
906
|
this._peerConnection && (
|
|
888
907
|
this._peerConnection.connectionState === 'failed' ||
|
|
@@ -890,15 +909,25 @@ export default class TopazCubeClient {
|
|
|
890
909
|
this._peerConnection.connectionState === 'closed')
|
|
891
910
|
) {
|
|
892
911
|
this._webRTCConnected = false
|
|
912
|
+
this.log('RTC: Peer connection closed or failed')
|
|
893
913
|
}
|
|
894
914
|
}
|
|
895
915
|
|
|
896
916
|
this._peerConnection.onicegatheringstatechange = () => {
|
|
897
|
-
|
|
917
|
+
this.log(`RTC: ICE gathering state. _candidates:`, this._candidates.length, this._peerConnection?.iceGatheringState)
|
|
918
|
+
if (this._peerConnection?.iceGatheringState == 'complete' && this._offerSent) {
|
|
919
|
+
for (let candidate of this._candidates) {
|
|
920
|
+
this.send({
|
|
921
|
+
c: 'rtc-candidate',
|
|
922
|
+
type: 'ice-candidate',
|
|
923
|
+
candidate: candidate,
|
|
924
|
+
})
|
|
925
|
+
}
|
|
926
|
+
}
|
|
898
927
|
}
|
|
899
928
|
|
|
900
929
|
this._peerConnection.oniceconnectionstatechange = () => {
|
|
901
|
-
//
|
|
930
|
+
//this.log(`RTC: ICE connection state: ${this._peerConnection.iceConnectionState}`)
|
|
902
931
|
|
|
903
932
|
// This is critical - when ICE succeeds, the connection should be established
|
|
904
933
|
if (
|
|
@@ -906,7 +935,7 @@ export default class TopazCubeClient {
|
|
|
906
935
|
this._peerConnection.iceConnectionState === 'connected' ||
|
|
907
936
|
this._peerConnection.iceConnectionState === 'completed')
|
|
908
937
|
) {
|
|
909
|
-
//
|
|
938
|
+
//this.log("RTC: ICE connection established!")
|
|
910
939
|
}
|
|
911
940
|
}
|
|
912
941
|
|
|
@@ -928,25 +957,25 @@ export default class TopazCubeClient {
|
|
|
928
957
|
}
|
|
929
958
|
|
|
930
959
|
this._dataChannel.onerror = (_error: Event) => {
|
|
931
|
-
|
|
960
|
+
this.error('RTC: Client data channel error', _error)
|
|
932
961
|
}
|
|
933
962
|
|
|
934
963
|
// Handle data channels created by the server
|
|
935
964
|
this._peerConnection.ondatachannel = (event: RTCDataChannelEvent) => {
|
|
936
|
-
//
|
|
965
|
+
//this.log("RTC: Server data channel received", event.channel.label);
|
|
937
966
|
const dataChannel = event.channel
|
|
938
967
|
this._serverDataChannel = dataChannel
|
|
939
968
|
|
|
940
969
|
dataChannel.onopen = () => {
|
|
941
|
-
//
|
|
970
|
+
//this.log("RTC: Server data channel open");
|
|
942
971
|
}
|
|
943
972
|
|
|
944
973
|
dataChannel.onclose = () => {
|
|
945
|
-
//
|
|
974
|
+
//this.log("RTC: Server data channel closed");
|
|
946
975
|
}
|
|
947
976
|
|
|
948
977
|
dataChannel.onerror = (_error: Event) => {
|
|
949
|
-
//
|
|
978
|
+
//this.error("RTC: Server data channel error", error);
|
|
950
979
|
}
|
|
951
980
|
|
|
952
981
|
dataChannel.onmessage = (event: MessageEvent) => {
|
|
@@ -962,14 +991,14 @@ export default class TopazCubeClient {
|
|
|
962
991
|
}
|
|
963
992
|
|
|
964
993
|
const offer = await this._peerConnection.createOffer(offerOptions)
|
|
965
|
-
//
|
|
994
|
+
//this.log("RTC: our offer:", offer);
|
|
966
995
|
await this._peerConnection.setLocalDescription(offer)
|
|
967
996
|
|
|
968
997
|
// Wait a moment to ensure the local description is set
|
|
969
998
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
|
970
999
|
|
|
971
1000
|
let ld = this._peerConnection.localDescription
|
|
972
|
-
//
|
|
1001
|
+
//this.log("RTC: our localDescription", ld);
|
|
973
1002
|
|
|
974
1003
|
if (ld) {
|
|
975
1004
|
const offerPayload = {
|
|
@@ -977,28 +1006,29 @@ export default class TopazCubeClient {
|
|
|
977
1006
|
type: ld.type,
|
|
978
1007
|
sdp: ld.sdp,
|
|
979
1008
|
}
|
|
980
|
-
//
|
|
1009
|
+
//this.log("RTC: our offer payload", offerPayload);
|
|
981
1010
|
this.send(offerPayload)
|
|
1011
|
+
this._offerSent = true
|
|
982
1012
|
}
|
|
983
1013
|
|
|
984
1014
|
// Set a timeout to check connection status
|
|
985
1015
|
setTimeout(() => {
|
|
986
1016
|
if (!this._webRTCConnected && this._peerConnection) {
|
|
987
1017
|
/*
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1018
|
+
this.log("RTC: Connection not established after timeout, current states:");
|
|
1019
|
+
this.log("Connection state:", this._peerConnection.connectionState);
|
|
1020
|
+
this.log("ICE connection state:", this._peerConnection.iceConnectionState);
|
|
1021
|
+
this.log("ICE gathering state:", this._peerConnection.iceGatheringState);
|
|
992
1022
|
*/
|
|
993
1023
|
// Attempt to restart ICE if needed
|
|
994
1024
|
if (this._peerConnection.iceConnectionState === 'failed') {
|
|
995
|
-
|
|
1025
|
+
this.log('RTC: Attempting ICE restart')
|
|
996
1026
|
this._restartIce()
|
|
997
1027
|
}
|
|
998
1028
|
}
|
|
999
1029
|
}, 5000)
|
|
1000
1030
|
} catch (error) {
|
|
1001
|
-
|
|
1031
|
+
this.error('RTC: error:', error)
|
|
1002
1032
|
}
|
|
1003
1033
|
}
|
|
1004
1034
|
|
|
@@ -1019,10 +1049,10 @@ export default class TopazCubeClient {
|
|
|
1019
1049
|
type: offer?.type,
|
|
1020
1050
|
sdp: offer?.sdp,
|
|
1021
1051
|
}
|
|
1022
|
-
//
|
|
1052
|
+
//this.log("RTC: ICE restart offer payload", offerPayload);
|
|
1023
1053
|
this.send(offerPayload)
|
|
1024
1054
|
} catch (error) {
|
|
1025
|
-
//
|
|
1055
|
+
//this.error("RTC: Error during ICE restart:", error);
|
|
1026
1056
|
}
|
|
1027
1057
|
}
|
|
1028
1058
|
|
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}`)
|