topazcube 0.1.15 → 0.1.17
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/dist/client.cjs.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +184 -234
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +184 -234
- package/dist/server.js.map +1 -1
- package/package.json +2 -1
- package/src/server.ts +9 -9
- package/src/utils.ts +2 -2
package/dist/server.js
CHANGED
|
@@ -88,9 +88,9 @@ function clonewo_(obj, excludeStart = "_") {
|
|
|
88
88
|
}
|
|
89
89
|
if (obj instanceof Map) {
|
|
90
90
|
const mapClone = /* @__PURE__ */ new Map();
|
|
91
|
-
|
|
91
|
+
Array.from(obj.entries()).forEach(([key, value]) => {
|
|
92
92
|
mapClone.set(clonewo_(key, excludeStart), clonewo_(value, excludeStart));
|
|
93
|
-
}
|
|
93
|
+
});
|
|
94
94
|
return mapClone;
|
|
95
95
|
}
|
|
96
96
|
let clone;
|
|
@@ -247,6 +247,7 @@ import fastjsonpatch from "fast-json-patch";
|
|
|
247
247
|
import { WebSocketServer } from "ws";
|
|
248
248
|
import { MongoClient } from "mongodb";
|
|
249
249
|
import { glMatrix } from "gl-matrix";
|
|
250
|
+
import wrtc from "@roamhq/wrtc";
|
|
250
251
|
glMatrix.setMatrixArrayType(Array);
|
|
251
252
|
var fastPatchProperties = {
|
|
252
253
|
"type": true,
|
|
@@ -291,6 +292,7 @@ var LITTLE_ENDIAN = (() => {
|
|
|
291
292
|
new DataView(buffer).setInt16(0, 256, true);
|
|
292
293
|
return new Int16Array(buffer)[0] === 256;
|
|
293
294
|
})();
|
|
295
|
+
var MAX_PACKAGE_SIZE = 65400;
|
|
294
296
|
var TopazCubeServer = class {
|
|
295
297
|
name = "TopazCubeServer";
|
|
296
298
|
cycle = 100;
|
|
@@ -584,8 +586,8 @@ var TopazCubeServer = class {
|
|
|
584
586
|
client.peerConnection = null;
|
|
585
587
|
this.log("client connected", client.ID);
|
|
586
588
|
this.clients.push(client);
|
|
587
|
-
client.on("error", () => {
|
|
588
|
-
this._onError(client,
|
|
589
|
+
client.on("error", (...args) => {
|
|
590
|
+
this._onError(client, args);
|
|
589
591
|
});
|
|
590
592
|
client.on("message", (message) => {
|
|
591
593
|
let dec = decode(message);
|
|
@@ -922,258 +924,206 @@ var TopazCubeServer = class {
|
|
|
922
924
|
e["__changed_" + property] = true;
|
|
923
925
|
}
|
|
924
926
|
/*= WEBRTC ===================================================================*/
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
//
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
type: 'ice-candidate',
|
|
945
|
-
candidate: event.candidate, // .toJSON()
|
|
946
|
-
})
|
|
947
|
-
} else {
|
|
948
|
-
//this.log("RTC: ICE candidate gathering complete");
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
peerConnection.onconnectionstatechange = () => {
|
|
953
|
-
//this.log(`RTC: Connection state changed: ${peerConnection.connectionState}`);
|
|
954
|
-
if (peerConnection.connectionState === 'connected') {
|
|
955
|
-
client.webRTCConnected = true
|
|
956
|
-
this.log(`RTC: Connection established with client ${client.ID}`)
|
|
957
|
-
} else if (
|
|
958
|
-
peerConnection.connectionState === 'failed' ||
|
|
959
|
-
peerConnection.connectionState === 'disconnected' ||
|
|
960
|
-
peerConnection.connectionState === 'closed'
|
|
961
|
-
) {
|
|
962
|
-
client.webRTCConnected = false
|
|
963
|
-
this.log(`RTC: Connection failed or closed with client ${client.ID}`)
|
|
964
|
-
}
|
|
927
|
+
async _processOffer(client, data) {
|
|
928
|
+
const peerConnection = new wrtc.RTCPeerConnection({
|
|
929
|
+
iceServers: [
|
|
930
|
+
{ urls: "stun:stun.l.google.com:19302" },
|
|
931
|
+
{ urls: "stun:stun.cloudflare.com:3478" },
|
|
932
|
+
{ urls: "stun:freestun.net:3478" }
|
|
933
|
+
],
|
|
934
|
+
iceCandidatePoolSize: 10
|
|
935
|
+
});
|
|
936
|
+
client.peerConnection = peerConnection;
|
|
937
|
+
peerConnection.onicecandidate = (event) => {
|
|
938
|
+
if (event.candidate) {
|
|
939
|
+
this.send(client, {
|
|
940
|
+
c: "rtc-candidate",
|
|
941
|
+
type: "ice-candidate",
|
|
942
|
+
candidate: event.candidate
|
|
943
|
+
// .toJSON()
|
|
944
|
+
});
|
|
945
|
+
} else {
|
|
965
946
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
947
|
+
};
|
|
948
|
+
peerConnection.onconnectionstatechange = () => {
|
|
949
|
+
if (peerConnection.connectionState === "connected") {
|
|
950
|
+
client.webRTCConnected = true;
|
|
951
|
+
this.log(`RTC: Connection established with client ${client.ID}`);
|
|
952
|
+
} else if (peerConnection.connectionState === "failed" || peerConnection.connectionState === "disconnected" || peerConnection.connectionState === "closed") {
|
|
953
|
+
client.webRTCConnected = false;
|
|
954
|
+
this.log(`RTC: Connection failed or closed with client ${client.ID}`);
|
|
969
955
|
}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
peerConnection.iceConnectionState === 'completed'
|
|
976
|
-
) {
|
|
977
|
-
//this.log(`RTC: ICE connection established with client ${client.ID}`);
|
|
978
|
-
}
|
|
956
|
+
};
|
|
957
|
+
peerConnection.onicegatheringstatechange = () => {
|
|
958
|
+
};
|
|
959
|
+
peerConnection.oniceconnectionstatechange = () => {
|
|
960
|
+
if (peerConnection.iceConnectionState === "connected" || peerConnection.iceConnectionState === "completed") {
|
|
979
961
|
}
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
} catch (e) {
|
|
999
|
-
this.error(
|
|
1000
|
-
`RTC: Error sending test message to client ${client.ID}`,
|
|
1001
|
-
e
|
|
1002
|
-
)
|
|
1003
|
-
}
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
client.dataChannel.onclose = () => {
|
|
1007
|
-
this.log(`RTC: Data channel closed for client ${client.ID}`)
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
client.dataChannel.onerror = (error: Event) => {
|
|
1011
|
-
this.error(`RTC: Data channel error for client ${client.ID}:`, error)
|
|
962
|
+
};
|
|
963
|
+
try {
|
|
964
|
+
await peerConnection.setRemoteDescription(
|
|
965
|
+
new wrtc.RTCSessionDescription(data)
|
|
966
|
+
);
|
|
967
|
+
client.dataChannel = peerConnection.createDataChannel("serverchannel", {
|
|
968
|
+
ordered: true,
|
|
969
|
+
maxRetransmits: 1
|
|
970
|
+
});
|
|
971
|
+
client.dataChannel.onopen = () => {
|
|
972
|
+
try {
|
|
973
|
+
const testData = { c: "test", message: "Hello WebRTC" };
|
|
974
|
+
this.sendRTC(client, testData);
|
|
975
|
+
} catch (e) {
|
|
976
|
+
this.error(
|
|
977
|
+
`RTC: Error sending test message to client ${client.ID}`,
|
|
978
|
+
e
|
|
979
|
+
);
|
|
1012
980
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
981
|
+
};
|
|
982
|
+
client.dataChannel.onclose = () => {
|
|
983
|
+
this.log(`RTC: Data channel closed for client ${client.ID}`);
|
|
984
|
+
};
|
|
985
|
+
client.dataChannel.onerror = (error) => {
|
|
986
|
+
this.error(`RTC: Data channel error for client ${client.ID}:`, error);
|
|
987
|
+
};
|
|
988
|
+
client.dataChannel.onmessage = (event) => {
|
|
989
|
+
try {
|
|
990
|
+
const data2 = decode(event.data);
|
|
991
|
+
this.log(
|
|
992
|
+
`RTC: Data channel message from client ${client.ID}:`,
|
|
993
|
+
data2
|
|
994
|
+
);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
this.error(
|
|
997
|
+
`RTC: Error decoding message from client ${client.ID}:`,
|
|
998
|
+
error
|
|
999
|
+
);
|
|
1028
1000
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1001
|
+
};
|
|
1002
|
+
const answer = await peerConnection.createAnswer();
|
|
1003
|
+
await peerConnection.setLocalDescription(answer);
|
|
1004
|
+
this.send(client, {
|
|
1005
|
+
c: "rtc-answer",
|
|
1006
|
+
type: answer.type,
|
|
1007
|
+
sdp: answer.sdp
|
|
1008
|
+
});
|
|
1009
|
+
} catch (error) {
|
|
1010
|
+
this.error(
|
|
1011
|
+
`RTC: Error processing offer from client ${client.ID}:`,
|
|
1012
|
+
error
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
async _processICECandidate(client, data) {
|
|
1017
|
+
try {
|
|
1018
|
+
if (client.peerConnection && data.candidate) {
|
|
1019
|
+
await client.peerConnection.addIceCandidate(
|
|
1020
|
+
data.candidate
|
|
1021
|
+
//new wrtc.RTCIceCandidate(data.candidate)
|
|
1022
|
+
);
|
|
1023
|
+
} else {
|
|
1045
1024
|
}
|
|
1025
|
+
} catch (error) {
|
|
1026
|
+
this.error(`RTC: Error adding ICE candidate for client ${client.ID}`);
|
|
1046
1027
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1028
|
+
}
|
|
1029
|
+
_clientRTCOpen(client) {
|
|
1030
|
+
return client.dataChannel !== null && client.dataChannel !== void 0 && client.dataChannel.readyState === "open";
|
|
1031
|
+
}
|
|
1032
|
+
async sendRTC(client, message) {
|
|
1033
|
+
let data = encode(message);
|
|
1034
|
+
if (this.allowCompression) {
|
|
1035
|
+
data = await compress(data);
|
|
1036
|
+
}
|
|
1037
|
+
this.stats.sendRTC += data.byteLength;
|
|
1038
|
+
this.stats._sendRTCUpdate += data.byteLength;
|
|
1039
|
+
let packages = this._splitRTCMessage(data);
|
|
1040
|
+
if (this.simulateLatency) {
|
|
1041
|
+
setTimeout(() => {
|
|
1042
|
+
if (this._clientRTCOpen(client)) {
|
|
1043
|
+
packages.forEach((p) => {
|
|
1044
|
+
client.dataChannel.send(p);
|
|
1045
|
+
});
|
|
1059
1046
|
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1047
|
+
}, this.simulateLatency);
|
|
1048
|
+
} else {
|
|
1049
|
+
if (this._clientRTCOpen(client)) {
|
|
1050
|
+
packages.forEach((p) => {
|
|
1051
|
+
client.dataChannel.send(p);
|
|
1052
|
+
});
|
|
1062
1053
|
}
|
|
1063
1054
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1055
|
+
}
|
|
1056
|
+
async broadcastRTC(message, clients = []) {
|
|
1057
|
+
if (clients.length == 0) {
|
|
1058
|
+
clients = this.clients;
|
|
1067
1059
|
}
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1060
|
+
let t1 = Date.now();
|
|
1061
|
+
let data = encode(message);
|
|
1062
|
+
let dl = data.byteLength;
|
|
1063
|
+
let t2 = Date.now();
|
|
1064
|
+
if (this.allowCompression) {
|
|
1065
|
+
data = await compress(data);
|
|
1066
|
+
}
|
|
1067
|
+
let t3 = Date.now();
|
|
1068
|
+
if (data.length > 16384) {
|
|
1069
|
+
this.log(`BroadcastRTC message ${dl} -> ${data.length} (${(100 * data.length / dl).toFixed()}%) encoding:${t2 - t1}ms compression:${t3 - t1}ms`);
|
|
1070
|
+
}
|
|
1071
|
+
let packages = this._splitRTCMessage(data);
|
|
1072
|
+
for (let client of this.clients) {
|
|
1073
|
+
this.stats.sendRTC += data.byteLength;
|
|
1074
|
+
this.stats._sendRTCUpdate += data.byteLength;
|
|
1079
1075
|
if (this.simulateLatency) {
|
|
1080
1076
|
setTimeout(() => {
|
|
1081
|
-
if (
|
|
1077
|
+
if (client.dataChannel && client.dataChannel.readyState === "open") {
|
|
1082
1078
|
packages.forEach((p) => {
|
|
1083
|
-
client
|
|
1084
|
-
})
|
|
1079
|
+
client?.dataChannel?.send(p);
|
|
1080
|
+
});
|
|
1085
1081
|
}
|
|
1086
|
-
}, this.simulateLatency)
|
|
1082
|
+
}, this.simulateLatency);
|
|
1087
1083
|
} else {
|
|
1088
|
-
if (
|
|
1084
|
+
if (client.dataChannel && client.dataChannel.readyState === "open") {
|
|
1089
1085
|
packages.forEach((p) => {
|
|
1090
|
-
client
|
|
1091
|
-
})
|
|
1086
|
+
client?.dataChannel?.send(p);
|
|
1087
|
+
});
|
|
1092
1088
|
}
|
|
1093
1089
|
}
|
|
1094
1090
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
let
|
|
1103
|
-
let
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
client?.dataChannel?.send(p)
|
|
1124
|
-
})
|
|
1125
|
-
}
|
|
1126
|
-
}, this.simulateLatency)
|
|
1127
|
-
} else {
|
|
1128
|
-
if (client.dataChannel && client.dataChannel.readyState === 'open') {
|
|
1129
|
-
packages.forEach((p) => {
|
|
1130
|
-
client?.dataChannel?.send(p)
|
|
1131
|
-
})
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
_splitRTCMessage(data: Uint8Array): Uint8Array[] {
|
|
1138
|
-
let packages: Uint8Array[]
|
|
1139
|
-
if (data.byteLength > 65535) {
|
|
1140
|
-
const now = Date.now()
|
|
1141
|
-
this.warn(`RTC: Message too large: ${data.byteLength} bytes`)
|
|
1142
|
-
// Split the message into smaller packages
|
|
1143
|
-
packages = [];
|
|
1144
|
-
let offset = 0;
|
|
1145
|
-
let mid = this.update +'-'+ now
|
|
1146
|
-
let seq = 0
|
|
1147
|
-
|
|
1148
|
-
// Create subsequent packages if needed
|
|
1149
|
-
while (offset < data.byteLength) {
|
|
1150
|
-
const remaining = data.byteLength - offset;
|
|
1151
|
-
const chunkSize = Math.min(remaining, MAX_PACKAGE_SIZE);
|
|
1152
|
-
const chunk = new Uint8Array(data.buffer, offset, chunkSize);
|
|
1153
|
-
let cmessage = {
|
|
1154
|
-
c: 'chunk',
|
|
1155
|
-
t: now,
|
|
1156
|
-
mid: mid,
|
|
1157
|
-
seq: seq,
|
|
1158
|
-
ofs: offset,
|
|
1159
|
-
chs: chunkSize,
|
|
1160
|
-
ts: data.byteLength,
|
|
1161
|
-
data: chunk,
|
|
1162
|
-
last: remaining <= MAX_PACKAGE_SIZE,
|
|
1163
|
-
}
|
|
1164
|
-
packages.push(encode(cmessage))
|
|
1165
|
-
offset += chunkSize;
|
|
1166
|
-
seq++;
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
this.log(`RTC: Large message split into ${packages.length} packages`);
|
|
1170
|
-
} else {
|
|
1171
|
-
packages = [data]
|
|
1172
|
-
this.log(`RTC: Message - ${data.byteLength} bytes`)
|
|
1091
|
+
}
|
|
1092
|
+
_splitRTCMessage(data) {
|
|
1093
|
+
let packages;
|
|
1094
|
+
if (data.byteLength > 65535) {
|
|
1095
|
+
const now = Date.now();
|
|
1096
|
+
this.warn(`RTC: Message too large: ${data.byteLength} bytes`);
|
|
1097
|
+
packages = [];
|
|
1098
|
+
let offset = 0;
|
|
1099
|
+
let mid = this.update + "-" + now;
|
|
1100
|
+
let seq = 0;
|
|
1101
|
+
while (offset < data.byteLength) {
|
|
1102
|
+
const remaining = data.byteLength - offset;
|
|
1103
|
+
const chunkSize = Math.min(remaining, MAX_PACKAGE_SIZE);
|
|
1104
|
+
const chunk = new Uint8Array(data.buffer, offset, chunkSize);
|
|
1105
|
+
let cmessage = {
|
|
1106
|
+
c: "chunk",
|
|
1107
|
+
t: now,
|
|
1108
|
+
mid,
|
|
1109
|
+
seq,
|
|
1110
|
+
ofs: offset,
|
|
1111
|
+
chs: chunkSize,
|
|
1112
|
+
ts: data.byteLength,
|
|
1113
|
+
data: chunk,
|
|
1114
|
+
last: remaining <= MAX_PACKAGE_SIZE
|
|
1115
|
+
};
|
|
1116
|
+
packages.push(encode(cmessage));
|
|
1117
|
+
offset += chunkSize;
|
|
1118
|
+
seq++;
|
|
1173
1119
|
}
|
|
1174
|
-
|
|
1120
|
+
this.log(`RTC: Large message split into ${packages.length} packages`);
|
|
1121
|
+
} else {
|
|
1122
|
+
packages = [data];
|
|
1123
|
+
this.log(`RTC: Message - ${data.byteLength} bytes`);
|
|
1175
1124
|
}
|
|
1176
|
-
|
|
1125
|
+
return packages;
|
|
1126
|
+
}
|
|
1177
1127
|
/*= DATABASE =================================================================*/
|
|
1178
1128
|
// properties (of the documents) that starts with __ are not saved to the database.
|
|
1179
1129
|
// __properties are restored on hydration. (for example __physicsBody or __bigObject)
|