topazcube 0.0.3 → 0.1.0

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.
@@ -0,0 +1,740 @@
1
+ import * as G from "node:https";
2
+ import * as b from "node:fs";
3
+ import { FLOAT32_OPTIONS as N, Packr as W } from "msgpackr";
4
+ import { promisify as x } from "util";
5
+ import { constants as H, gzip as Z, gunzip as X } from "zlib";
6
+ import q from "fast-json-patch";
7
+ import { WebSocketServer as B } from "ws";
8
+ import { MongoClient as K } from "mongodb";
9
+ import * as L from "@roamhq/wrtc";
10
+ import { glMatrix as Q } from "gl-matrix";
11
+ const { ALWAYS: J, DECIMAL_ROUND: ft, DECIMAL_FIT: pt } = N;
12
+ let O = new W({
13
+ useFloat32: J
14
+ });
15
+ function w(o) {
16
+ return O.pack(o);
17
+ }
18
+ function P(o) {
19
+ return O.unpack(o);
20
+ }
21
+ function U(o, t, e, s = "", n = !1) {
22
+ if (t === null || typeof t != "object")
23
+ return t;
24
+ function i(a) {
25
+ let l = !0;
26
+ return a.startsWith("_") && (l = !1), n && n[a] && (l = !1), s == "/entities" && (l = !1), l;
27
+ }
28
+ for (const a in t)
29
+ i(a) && (t[a] = U(
30
+ o,
31
+ t[a],
32
+ e,
33
+ s + "/" + a,
34
+ n
35
+ ));
36
+ return new Proxy(t, {
37
+ get(a, l, u) {
38
+ return Reflect.get(a, l, u);
39
+ },
40
+ set(a, l, u) {
41
+ let h, f = s + "/" + String(l);
42
+ return i(l) ? (h = U(o, u, e, f, n), e(o, "replace", a, f, h)) : h = u, Reflect.set(a, l, h);
43
+ },
44
+ deleteProperty(a, l) {
45
+ let u = s + "/" + String(l);
46
+ return delete a[l], i(l) && e(o, "delete", a, u, null), !0;
47
+ }
48
+ });
49
+ }
50
+ function C(o, t = "_") {
51
+ if (o === null || typeof o != "object")
52
+ return o;
53
+ function e(n) {
54
+ let i = !1;
55
+ return (typeof t == "string" && n.startsWith(t) || typeof t == "object" && (t[n] || n.startsWith("_"))) && (i = !0), i;
56
+ }
57
+ if (o instanceof Map) {
58
+ const n = /* @__PURE__ */ new Map();
59
+ for (let [i, a] of o)
60
+ n.set(C(i, t), C(a, t));
61
+ return n;
62
+ }
63
+ let s;
64
+ if (Array.isArray(o)) {
65
+ s = [];
66
+ for (let n = 0; n < o.length; n++)
67
+ s[n] = C(o[n], t);
68
+ } else {
69
+ s = {};
70
+ for (let n in o)
71
+ o.hasOwnProperty(n) && !e(n) && (typeof o[n] == "object" ? s[n] = C(o[n], t) : s[n] = o[n]);
72
+ }
73
+ return s;
74
+ }
75
+ function $(o) {
76
+ if (Array.isArray(o))
77
+ return o.map($);
78
+ if (o !== null && typeof o == "object") {
79
+ const t = {};
80
+ for (const e in o)
81
+ t[e] = $(o[e]);
82
+ return t;
83
+ } else return typeof o == "number" ? Number.isInteger(o) ? o : parseFloat(o.toFixed(3)) : o;
84
+ }
85
+ function V(o) {
86
+ let t = {};
87
+ return o.o ? t.op = {
88
+ a: "add",
89
+ r: "remove",
90
+ d: "delete",
91
+ t: "test"
92
+ }[o.o] : t.op = "replace", t.path = o.p, t.value = o.v, t;
93
+ }
94
+ function Y(o, t, e, s) {
95
+ let n = { p: e, v: s };
96
+ return o != "replace" && (n.o = {
97
+ add: "a",
98
+ remove: "r",
99
+ delete: "d",
100
+ test: "t"
101
+ }[o]), n;
102
+ }
103
+ function I(o, t, e = 0) {
104
+ t || (t = new Uint8Array(4));
105
+ let s = e + 3;
106
+ return t[s--] = o & 255, o >>= 8, t[s--] = o & 255, o >>= 8, t[s--] = o & 255, o >>= 8, t[s] = o, t;
107
+ }
108
+ function j(o, t, e = 0) {
109
+ t || (t = new Uint8Array(3));
110
+ let s = e + 2;
111
+ return t[s--] = o & 255, o >>= 8, t[s--] = o & 255, o >>= 8, t[s] = o, t;
112
+ }
113
+ function tt(o, t, e = 0) {
114
+ t || (t = new Uint8Array(2));
115
+ let s = e + 1;
116
+ return t[s--] = o & 255, o >>= 8, t[s] = o, t;
117
+ }
118
+ function E(o, t, e = 0) {
119
+ const s = Math.round(Math.abs(o) * 256);
120
+ j(s, t, e), o < 0 && (t[e] |= 128);
121
+ }
122
+ function M(o, t, e = 0) {
123
+ const s = Math.round(Math.abs(o) * 65536);
124
+ I(s, t, e), o < 0 && (t[e] |= 128);
125
+ }
126
+ function S(o, t, e = 0) {
127
+ const s = Math.round(Math.abs(o) * 4096);
128
+ tt(s, t, e), o < 0 && (t[e] |= 128);
129
+ }
130
+ const et = 256, st = 999999, nt = x(Z);
131
+ x(X);
132
+ async function T(o) {
133
+ if (o.byteLength <= et || o.byteLength >= st) return o;
134
+ try {
135
+ let t = Date.now(), e = await nt(o, {
136
+ level: H.Z_BEST_SPEED
137
+ }), s = Date.now(), n = Buffer.from(e), i = Date.now();
138
+ return n;
139
+ } catch (t) {
140
+ return console.error("Error compressing buffer:", t), o;
141
+ }
142
+ }
143
+ Q.setMatrixArrayType(Array);
144
+ const D = {
145
+ type: !0,
146
+ // string 'enemy'
147
+ status: !0,
148
+ // string 'idle'
149
+ level: !0,
150
+ // number 2
151
+ race: !0,
152
+ // string 'goblin'
153
+ class: !0,
154
+ // string 'warrior'
155
+ model: !0,
156
+ // string 'models/models.glb|goblin'
157
+ animation: !0,
158
+ // string 'idle2'
159
+ sound: !0,
160
+ // string 'sound/goblin.snd|snarl'
161
+ effect: !0,
162
+ // 'selected'
163
+ position: !0,
164
+ // [0, 0, 0] Vector (Number)
165
+ rotation: !0,
166
+ // [0, 0, 0, 1] Quaternion (Number)
167
+ scale: !0
168
+ // [1, 1, 1] Vector (Number)
169
+ }, F = {
170
+ type: !0,
171
+ status: !0,
172
+ level: !0,
173
+ race: !0,
174
+ class: !0,
175
+ model: !0,
176
+ animation: !0,
177
+ sound: !0,
178
+ effect: !0
179
+ }, { applyOperation: ot } = q, it = (() => {
180
+ const o = new ArrayBuffer(2);
181
+ return new DataView(o).setInt16(0, 256, !0), new Int16Array(o)[0] === 256;
182
+ })(), k = 65400;
183
+ class gt {
184
+ constructor({
185
+ name: t = "TopazCubeServer",
186
+ cycle: e = 100,
187
+ port: s = 8799,
188
+ useHttps: n = !1,
189
+ key: i = "./cert/key.pem",
190
+ cert: a = "./cert/fullchain.pem",
191
+ MongoUrl: l = "mongodb://localhost:27017",
192
+ database: u = "topazcube",
193
+ collection: h = "documents",
194
+ allowSave: f = !0,
195
+ allowSync: y = !0,
196
+ allowWebRTC: R = !1,
197
+ allowFastPatch: r = !1,
198
+ allowCompression: p = !1,
199
+ simulateLatency: g = 0
200
+ }) {
201
+ if (this.name = "TopazCubeServer", this.cycle = 100, this.patchCycleDivider = 1, this.port = 8799, this.useHttps = !1, this.key = "./cert/key.pem", this.cert = "./cert/fullchain.pem", this.MongoUrl = "mongodb://localhost:27017", this.mongoClient = null, this.DB = null, this.database = "topazcube", this.collection = "documents", this.allowSave = !0, this.allowSync = !0, this.allowWebRTC = !1, this.allowFastPatch = !1, this.allowCompression = !1, this.simulateLatency = 0, this._lastUID = 100, this.clients = [], this.documents = {}, this.isLoading = {}, this._documentChanges = {}, this._documentState = {}, this.update = 0, this.lastUpdate = 0, this._loopiv = null, this._statsiv = null, this._stillUpdating = !1, this.stats = {
202
+ tUpdate: [],
203
+ tPatch: [],
204
+ send: 0,
205
+ sendRTC: 0,
206
+ _sendRTCUpdate: 0
207
+ }, this._wss = null, this._exited = !1, this.name = t, this.cycle = e, this.port = s, this.useHttps = n, this.key = i, this.cert = a, this.MongoUrl = l, this.database = u, this.collection = h, this.allowSave = f, this.allowSync = y, this.allowWebRTC = R, this.allowFastPatch = r, this.allowCompression = p, this.simulateLatency = g, this._initDB(), n) {
208
+ let c = G.createServer({
209
+ key: b.readFileSync(this.key),
210
+ cert: b.readFileSync(this.cert)
211
+ }, (d, _) => {
212
+ _.writeHead(200), _.end("<b>Hello World!</b>");
213
+ }).listen(this.port);
214
+ this._wss = new B({ server: c }), c = null, console.log(this.name + " running on HTTPS port " + this.port);
215
+ } else
216
+ this._wss = new B({ port: this.port }), console.log(this.name + " running on port " + this.port);
217
+ this._wss.on("connection", (c) => {
218
+ this._onConnected(c);
219
+ }), process.stdin.resume(), process.on("SIGINT", () => {
220
+ this._exitSignal("SIGINT");
221
+ }), process.on("SIGQUIT", () => {
222
+ this._exitSignal("SIGQUIT");
223
+ }), process.on("SIGTERM", () => {
224
+ this._exitSignal("SIGTERM");
225
+ }), process.on("SIGUSR2", () => {
226
+ this._exitSignal("SIGUSR2");
227
+ }), process.stdin.resume(), process.stdin.setEncoding("utf8"), process.stdin.on("data", (c) => {
228
+ if (c = ("" + c).trim(), c == "") {
229
+ this._exitSignal("SIGINT");
230
+ return;
231
+ }
232
+ console.log(`Key pressed: ${c}`), c == "s" && (console.log("Saving all documents..."), this._saveAllDocuments()), c == "i" && console.log(
233
+ `Server: ${this.name}, Clients: ${this.clients.length}, Documents: ${Object.keys(this.documents).length}`
234
+ );
235
+ }), this._startLoop();
236
+ }
237
+ /*= DOCUMENTS ==============================================================*/
238
+ // to be redefined. Called before a new document is created. Returns true if
239
+ // the client has the right to create an empty document
240
+ canCreate(t, e) {
241
+ return !0;
242
+ }
243
+ // to be redefined. Called when a new document is created
244
+ // (returns an empty document)
245
+ onCreate(t) {
246
+ return {
247
+ data: {}
248
+ };
249
+ }
250
+ // to be redefined. Called when a client wants to sync (modify) a document.
251
+ // Returns true if the client has the right to sync that operation.
252
+ canSync(t, e, s) {
253
+ return !0;
254
+ }
255
+ // to be redefined. Called when a new document is hydrated
256
+ // (created, or loaded from db)
257
+ async onHydrate(t, e) {
258
+ e.__hydrated = !0;
259
+ }
260
+ _makeReactive(t) {
261
+ let e = !1;
262
+ this.allowFastPatch && (e = D), this.documents[t] = U(
263
+ t,
264
+ this.documents[t],
265
+ this._onDocumentChange.bind(this),
266
+ "",
267
+ e
268
+ ), this._documentChanges[t] || (this._documentChanges[t] = []);
269
+ }
270
+ _createEmptyDocument(t) {
271
+ let e = this.onCreate(t);
272
+ e && (this.documents[t] = e);
273
+ }
274
+ async _waitLoad(t) {
275
+ if (this.isLoading[t])
276
+ for (; this.isLoading[t]; )
277
+ await new Promise((e) => setTimeout(e, 50));
278
+ }
279
+ async _checkDocument(t, e) {
280
+ await this._waitLoad(t), this.documents[t] || (this.isLoading[t] = !0, await this._loadDocument(t), !this.documents[t] && this.canCreate(e, t) && this._createEmptyDocument(t), this.documents[t] && (this._makeReactive(t), this.onHydrate(t, this.documents[t])), this.isLoading[t] = !1, this._documentState[t] = {
281
+ subscibers: 0,
282
+ lastModified: Date.now()
283
+ });
284
+ }
285
+ _updateAllDocumentsState() {
286
+ for (let t in this.documents)
287
+ if (t != "_server") {
288
+ this.documents[t], this._documentState[t].subscibers = 0;
289
+ for (let e of this.clients)
290
+ e.subscribed[t] && this._documentState[t].subscibers++;
291
+ }
292
+ }
293
+ /*= UPDATE LOOP ============================================================*/
294
+ // to be redefined. called every this.cycle ms
295
+ onUpdate(t, e, s) {
296
+ }
297
+ _startLoop() {
298
+ this.lastUpdate = Date.now(), this._loop(), this._statsiv = setInterval(() => {
299
+ this._doStats();
300
+ }, 1e3);
301
+ }
302
+ _loop() {
303
+ let t = Date.now(), e = t - this.lastUpdate, s = e / 1e3;
304
+ this.lastUpdate = t, this._stillUpdating = !0;
305
+ for (let h in this.documents)
306
+ this.onUpdate(h, this.documents[h], s);
307
+ let n = Date.now();
308
+ this._stillUpdating = !1;
309
+ let i = n - t;
310
+ this.stats.tUpdate.push(i);
311
+ let a = 0;
312
+ this.update % this.patchCycleDivider == 0 && (this._sendPatches(), a = Date.now() - n, this.stats.tPatch.push(a), this.allowFastPatch && console.log(`update ${this.update} dt:${e}ms RTC:${this.stats._sendRTCUpdate}bytes, tUpdate: ${i}ms, tPatch: ${a}ms`), this.stats._sendRTCUpdate = 0), this.update++;
313
+ let u = Date.now() - t;
314
+ setTimeout(() => {
315
+ this._loop();
316
+ }, Math.max(this.cycle - u, 10));
317
+ }
318
+ _doStats() {
319
+ for (let t in this.stats) {
320
+ let e = this.stats[t];
321
+ if (e.length > 0) {
322
+ for (; e.length > 60; )
323
+ e.shift();
324
+ this.stats["_avg_" + t] = e.reduce((s, n) => s + n, 0) / e.length;
325
+ } else t.startsWith("_") || (this.stats["_persec_" + t] = e / 3, this.stats[t] = 0);
326
+ }
327
+ }
328
+ /*= MESSAGES ===============================================================*/
329
+ // to be redefined. Called on message (operation) from client
330
+ onMessage(t, e) {
331
+ }
332
+ // to be redefined. Called when a client connects
333
+ onConnect(t) {
334
+ }
335
+ // to be redefined. Called when a client disconnects
336
+ onDisconnect(t) {
337
+ }
338
+ _onConnected(t) {
339
+ t.ID = this.getUID(), t.ping = 0, t.ctdiff = 0, t.subscribed = {}, t.dataChannel = null, t.peerConnection = null, console.log("client connected", t.ID), this.clients.push(t), t.on("error", () => {
340
+ this._onError(t, arguments);
341
+ }), t.on("message", (e) => {
342
+ let s = P(e);
343
+ this.simulateLatency ? setTimeout(() => {
344
+ this._onMessage(t, s);
345
+ }, this.simulateLatency) : this._onMessage(t, s);
346
+ }), t.on("close", (e) => {
347
+ this._onDisconnected(t), this.onDisconnect(t);
348
+ }), this.onConnect(t);
349
+ }
350
+ async _onMessage(t, e) {
351
+ if (e.c == "sync" && this.allowSync && t.subscribed[e.n] && this.documents[e.n]) {
352
+ let s = e.n;
353
+ this._documentChanges[s] || (this._documentChanges[s] = []);
354
+ for (let n of e.p) {
355
+ if (!this.canSync(t, s, n))
356
+ continue;
357
+ this._documentChanges[s].push(n);
358
+ let i = V(n);
359
+ ot(this.documents[s], i);
360
+ }
361
+ } else if (e.c == "ping")
362
+ this.send(t, {
363
+ c: "pong",
364
+ t: Date.now(),
365
+ ct: e.ct,
366
+ ID: t.ID
367
+ });
368
+ else if (e.c == "peng") {
369
+ let s = Date.now(), n = s - e.st;
370
+ t.ctdiff = e.ct + n / 2 - s, t.ping = n;
371
+ } else if (e.c == "rtc-offer")
372
+ this._processOffer(t, e);
373
+ else if (e.c == "rtc-candidate")
374
+ this._processICECandidate(t, e);
375
+ else if (e.c == "sub") {
376
+ if (await this._checkDocument(e.n, t), !this.documents[e.n]) {
377
+ this.send(t, {
378
+ c: "error",
379
+ t: Date.now(),
380
+ message: "Document not found"
381
+ });
382
+ return;
383
+ }
384
+ t.subscribed[e.n] = !0, this._sendFullState(e.n, t);
385
+ } else e.c == "unsub" ? t.subscribed[e.n] = !1 : this.onMessage(t, e);
386
+ }
387
+ _onError(t, e) {
388
+ console.error("onError:", e);
389
+ }
390
+ _onDisconnected(t) {
391
+ t.dataChannel && t.dataChannel.close(), t.peerConnection && t.peerConnection.close(), console.log("client disconnected");
392
+ let e = this.clients.indexOf(t);
393
+ e !== -1 && this.clients.splice(e, 1);
394
+ }
395
+ async send(t, e) {
396
+ try {
397
+ let s = Date.now(), n = w(e), i = Date.now(), a = n.byteLength;
398
+ this.allowCompression && (n = await T(n));
399
+ let l = Date.now();
400
+ n.length > 4096 && console.log(`Big message ${a} -> ${n.length} (${(100 * n.length / a).toFixed()}%) encoding:${i - s}ms compression:${l - s}ms`), this.stats.send += n.byteLength, this.simulateLatency ? setTimeout(() => {
401
+ t.send(n);
402
+ }, this.simulateLatency) : t.send(n);
403
+ } catch (s) {
404
+ console.error("Error sending message:", s, e);
405
+ }
406
+ }
407
+ async broadcast(t, e = !1) {
408
+ e || (e = this.clients);
409
+ let s = w(t);
410
+ this.allowCompression && (s = await T(s));
411
+ for (let n of this.clients)
412
+ this.stats.send += s.byteLength, this.simulateLatency ? setTimeout(() => {
413
+ n.send(s);
414
+ }, this.simulateLatency) : n.send(s);
415
+ }
416
+ async _sendFullState(t, e) {
417
+ await this._waitLoad(t);
418
+ let s = "_";
419
+ this.allowFastPatch && (s = D);
420
+ let n = C(this.documents[t], s);
421
+ $(n);
422
+ let i = !1;
423
+ this.allowFastPatch && (i = this._encodeFastChanges(t, !1));
424
+ let a = {
425
+ c: "full",
426
+ le: it,
427
+ t: Date.now(),
428
+ n: t,
429
+ doc: n,
430
+ fdata: i
431
+ };
432
+ this.send(e, a);
433
+ }
434
+ _encodeFastChanges(t, e = !0) {
435
+ let s = this.documents[t];
436
+ if (!s)
437
+ return !1;
438
+ let n = this.documents[t].origin;
439
+ n || (n = [0, 0, 0], this.documents[t].origin = n);
440
+ let i = s.entities, a = Object.keys(i);
441
+ if (!i)
442
+ return !1;
443
+ let l = {}, u = {}, h = {}, f = {}, y = {};
444
+ for (let r in D)
445
+ e ? (l[r] = 0, u[r] = {}, h[r] = !1) : (l[r] = a.length, u[r] = {}, h[r] = !0), f[r] = {};
446
+ if (e)
447
+ for (let r in i) {
448
+ let p = i[r];
449
+ for (let g in D)
450
+ p["__changed_" + g] && (u[g][r] = !0, l[g]++, h[g] = !0, p["__changed_" + g] = !1);
451
+ }
452
+ else
453
+ for (let r in i)
454
+ for (let p in D)
455
+ u[p][r] = !0;
456
+ let R = 1;
457
+ for (let r in h)
458
+ if (h[r] && F[r])
459
+ for (let p in u[r]) {
460
+ let c = i[p][r];
461
+ f[r][c] || (f[r][c] = R++);
462
+ }
463
+ console.log("--------------------------------------------------");
464
+ for (let r in h)
465
+ if (h[r]) {
466
+ let p = l[r], g = {};
467
+ if (F[r]) {
468
+ g.dict = f[r];
469
+ let c = new Uint8Array(p * 8), d = 0;
470
+ for (let _ in u[r]) {
471
+ let m = i[_], v = parseInt(_);
472
+ I(v, c, d), d += 4;
473
+ let A = m[r], z = parseInt(f[r][A]);
474
+ I(z, c, d), d += 4;
475
+ }
476
+ g.pdata = c;
477
+ } else {
478
+ let c;
479
+ r == "position" ? c = new Uint8Array(p * 13) : r == "rotation" ? c = new Uint8Array(p * 16) : r == "scale" && (c = new Uint8Array(p * 16));
480
+ let d = 0;
481
+ for (let _ in u[r]) {
482
+ let m = i[_], v = parseInt(_);
483
+ I(v, c, d), d += 4, r == "position" ? (E(m.position[0] - n[0], c, d), d += 3, E(m.position[1] - n[1], c, d), d += 3, E(m.position[2] - n[2], c, d), d += 3) : r == "rotation" ? (S(m.rotation[0], c, d), d += 2, S(m.rotation[1], c, d), d += 2, S(m.rotation[2], c, d), d += 2, S(m.rotation[3], c, d), d += 2) : r == "scale" && (M(m.scale[0], c, d), d += 4, M(m.scale[1], c, d), d += 4, M(m.scale[2], c, d), d += 4);
484
+ }
485
+ g.pdata = c;
486
+ }
487
+ y[r] = g;
488
+ }
489
+ return y;
490
+ }
491
+ _sendPatches() {
492
+ let t = Date.now();
493
+ for (let e in this._documentChanges) {
494
+ let s = this._documentChanges[e];
495
+ this._documentChanges[e] = [];
496
+ let n = this.clients.filter((i) => i.subscribed[e]);
497
+ if (n.length > 0 && s.length > 0) {
498
+ let i = {
499
+ c: "patch",
500
+ t,
501
+ // server time
502
+ u: this.update,
503
+ n: e,
504
+ doc: s
505
+ };
506
+ this.broadcast(i, n);
507
+ }
508
+ if (this.allowFastPatch && n.length > 0) {
509
+ let i = Date.now(), a = this._encodeFastChanges(e), l = Date.now(), u = {
510
+ c: "fpatch",
511
+ t,
512
+ // server time
513
+ u: this.update,
514
+ n: e,
515
+ fdata: a
516
+ };
517
+ this.broadcastRTC(u, n);
518
+ let h = Date.now();
519
+ console.log(`_sendPatches: ${e} encode_changes: ${l - i}ms broadcast:${h - l}ms`);
520
+ }
521
+ }
522
+ }
523
+ _onDocumentChange(t, e, s, n, i) {
524
+ this._documentChanges[t].push(Y(e, s, n, i));
525
+ }
526
+ propertyChange(t, e, s) {
527
+ let n = this.documents[t];
528
+ if (!n)
529
+ return;
530
+ let i = n.entities;
531
+ if (!i)
532
+ return;
533
+ let a = i[e];
534
+ a && (a["__changed_" + s] = !0);
535
+ }
536
+ /*= WEBRTC ===================================================================*/
537
+ async _processOffer(t, e) {
538
+ const s = new L.RTCPeerConnection({
539
+ iceServers: [
540
+ { urls: "stun:stun.l.google.com:19302" },
541
+ { urls: "stun:stun.cloudflare.com:3478" },
542
+ { urls: "stun:freestun.net:3478" }
543
+ ],
544
+ iceCandidatePoolSize: 10
545
+ });
546
+ t.peerConnection = s, s.onicecandidate = (n) => {
547
+ n.candidate && this.send(t, {
548
+ c: "rtc-candidate",
549
+ type: "ice-candidate",
550
+ candidate: n.candidate.toJSON()
551
+ });
552
+ }, s.onconnectionstatechange = () => {
553
+ s.connectionState === "connected" ? (t.webRTCConnected = !0, console.log(`RTC: Connection established with client ${t.ID}`)) : (s.connectionState === "failed" || s.connectionState === "disconnected" || s.connectionState === "closed") && (t.webRTCConnected = !1, console.log(`RTC: Connection failed or closed with client ${t.ID}`));
554
+ }, s.onicegatheringstatechange = () => {
555
+ }, s.oniceconnectionstatechange = () => {
556
+ s.iceConnectionState === "connected" || s.iceConnectionState;
557
+ };
558
+ try {
559
+ await s.setRemoteDescription(
560
+ new L.RTCSessionDescription(e)
561
+ ), t.dataChannel = s.createDataChannel("serverchannel", {
562
+ ordered: !0,
563
+ maxRetransmits: 1
564
+ }), t.dataChannel.onopen = () => {
565
+ try {
566
+ const i = { c: "test", message: "Hello WebRTC" };
567
+ this.sendRTC(t, i);
568
+ } catch (i) {
569
+ console.error(
570
+ `RTC: Error sending test message to client ${t.ID}`,
571
+ i
572
+ );
573
+ }
574
+ }, t.dataChannel.onclose = () => {
575
+ console.log(`RTC: Data channel closed for client ${t.ID}`);
576
+ }, t.dataChannel.onerror = (i) => {
577
+ console.error(`RTC: Data channel error for client ${t.ID}:`, i);
578
+ }, t.dataChannel.onmessage = (i) => {
579
+ try {
580
+ const a = P(i.data);
581
+ console.log(
582
+ `RTC: Data channel message from client ${t.ID}:`,
583
+ a
584
+ );
585
+ } catch (a) {
586
+ console.error(
587
+ `RTC: Error decoding message from client ${t.ID}:`,
588
+ a
589
+ );
590
+ }
591
+ };
592
+ const n = await s.createAnswer();
593
+ await s.setLocalDescription(n), this.send(t, {
594
+ c: "rtc-answer",
595
+ type: n.type,
596
+ sdp: n.sdp
597
+ });
598
+ } catch (n) {
599
+ console.error(
600
+ `RTC: Error processing offer from client ${t.ID}:`,
601
+ n
602
+ );
603
+ }
604
+ }
605
+ async _processICECandidate(t, e) {
606
+ try {
607
+ t.peerConnection && e.candidate && await t.peerConnection.addIceCandidate(
608
+ new L.RTCIceCandidate(e.candidate)
609
+ );
610
+ } catch {
611
+ console.error(`RTC: Error adding ICE candidate for client ${t.ID}`);
612
+ }
613
+ }
614
+ _clientRTCOpen(t) {
615
+ return t.dataChannel && t.dataChannel.readyState === "open";
616
+ }
617
+ async sendRTC(t, e) {
618
+ let s = w(e);
619
+ this.allowCompression && (s = await T(s)), this.stats.sendRTC += s.byteLength, this.stats._sendRTCUpdate += s.byteLength;
620
+ let n = this._splitRTCMessage(s);
621
+ this.simulateLatency ? setTimeout(() => {
622
+ this._clientRTCOpen(t) && n.forEach((i) => {
623
+ t.dataChannel.send(i);
624
+ });
625
+ }, this.simulateLatency) : this._clientRTCOpen(t) && n.forEach((i) => {
626
+ t.dataChannel.send(i);
627
+ });
628
+ }
629
+ async broadcastRTC(t, e = []) {
630
+ e.length == 0 && (e = this.clients);
631
+ let s = Date.now(), n = w(t), i = n.byteLength, a = Date.now();
632
+ this.allowCompression && (n = await T(n));
633
+ let l = Date.now();
634
+ n.length > 16384 && console.log(`BroadcastRTC message ${i} -> ${n.length} (${(100 * n.length / i).toFixed()}%) encoding:${a - s}ms compression:${l - s}ms`);
635
+ let u = this._splitRTCMessage(n);
636
+ for (let h of this.clients)
637
+ this.stats.sendRTC += n.byteLength, this.stats._sendRTCUpdate += n.byteLength, this.simulateLatency ? setTimeout(() => {
638
+ h.dataChannel && h.dataChannel.readyState === "open" && u.forEach((f) => {
639
+ h.dataChannel.send(f);
640
+ });
641
+ }, this.simulateLatency) : h.dataChannel && h.dataChannel.readyState === "open" && u.forEach((f) => {
642
+ h.dataChannel.send(f);
643
+ });
644
+ }
645
+ _splitRTCMessage(t) {
646
+ let e;
647
+ if (t.byteLength > 65535) {
648
+ const s = Date.now();
649
+ console.warn(`RTC: Message too large: ${t.byteLength} bytes`), e = [];
650
+ let n = 0, i = this.update + "-" + s, a = 0;
651
+ for (; n < t.byteLength; ) {
652
+ const l = t.byteLength - n, u = Math.min(l, k), h = new Uint8Array(t.buffer, n, u);
653
+ let f = {
654
+ c: "chunk",
655
+ t: s,
656
+ mid: i,
657
+ seq: a,
658
+ ofs: n,
659
+ chs: u,
660
+ ts: t.byteLength,
661
+ data: h,
662
+ last: l <= k
663
+ };
664
+ e.push(w(f)), n += u, a++;
665
+ }
666
+ console.log(`RTC: Large message split into ${e.length} packages`);
667
+ } else
668
+ e = [t], console.log(`RTC: Message - ${t.byteLength} bytes`);
669
+ return e;
670
+ }
671
+ /*= DATABASE =================================================================*/
672
+ // properties (of the documents) that starts with __ are not saved to the database.
673
+ // __properties are restored on hydration. (for example __physicsBody or __bigObject)
674
+ getUID() {
675
+ return this.documents._server.nextUID++, this.documents._server.nextUID;
676
+ }
677
+ async _initDB() {
678
+ await this._connectDB(), await this._loadDocument("_server"), this.documents._server || this._initServerDocument();
679
+ }
680
+ async _connectDB() {
681
+ this.mongoClient = new K(this.MongoUrl);
682
+ try {
683
+ await this.mongoClient.connect(), console.log("Connected to MongoDB");
684
+ const t = this.mongoClient.db(this.database);
685
+ this.DB = t;
686
+ } catch (t) {
687
+ console.error("Error connecting to MongoDB:", t), this.mongoClient = null;
688
+ }
689
+ }
690
+ async _loadDocument(t) {
691
+ if (console.log(`Loading document '${t}' from MongoDB`), this.DB)
692
+ try {
693
+ const e = await this.DB.collection(this.collection).findOne({
694
+ name: t
695
+ });
696
+ e && (delete e._id, this.documents[t] = e);
697
+ } catch (e) {
698
+ console.error("Error loading document from MongoDB:", e);
699
+ }
700
+ else
701
+ console.warn("MongoDB client not initialized. Document not loaded.");
702
+ }
703
+ async _saveDocument(t) {
704
+ if (this.DB)
705
+ try {
706
+ const e = this.documents[t];
707
+ let s = C(e, "__");
708
+ console.log(`Saving document '${t}' to MongoDB`), await this.DB.collection(this.collection).updateOne(
709
+ { name: t },
710
+ { $set: s },
711
+ { upsert: !0 }
712
+ ), console.log("Document saved to MongoDB");
713
+ } catch (e) {
714
+ console.error("Error saving document to MongoDB:", e);
715
+ }
716
+ else
717
+ console.warn("MongoDB client not initialized. Document not saved.");
718
+ }
719
+ async _saveAllDocuments() {
720
+ if (this.allowSave)
721
+ for (let t in this.documents)
722
+ await this._saveDocument(t);
723
+ }
724
+ _initServerDocument() {
725
+ this.documents._server = {
726
+ nextUID: 100
727
+ };
728
+ }
729
+ /*= EXIT ===================================================================*/
730
+ _exitSignal(t) {
731
+ this._exited || (console.log(`
732
+ EXIT: Caught interrupt signal ` + t), this._exited = !0, clearInterval(this._loopiv), this.onBeforeExit(), this.broadcast({ server: "Going down" }), this._saveAllDocuments(), this._wss.close(), setTimeout(() => process.exit(0), 1e3));
733
+ }
734
+ // To be redefined. Called BEFORE program exit, and saving all documents
735
+ onBeforeExit() {
736
+ }
737
+ }
738
+ export {
739
+ gt as default
740
+ };