@principal-ai/control-tower-core 0.1.24 → 0.2.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.
- package/dist/abstractions/AuthAdapter.d.ts +1 -1
- package/dist/abstractions/AuthAdapter.d.ts.map +1 -1
- package/dist/abstractions/DefaultLockManager.d.ts +2 -2
- package/dist/abstractions/DefaultLockManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultLockManager.js +7 -8
- package/dist/abstractions/DefaultPresenceManager.d.ts +4 -4
- package/dist/abstractions/DefaultPresenceManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultPresenceManager.js +25 -25
- package/dist/abstractions/DefaultRoomManager.d.ts +3 -3
- package/dist/abstractions/DefaultRoomManager.d.ts.map +1 -1
- package/dist/abstractions/DefaultRoomManager.js +6 -4
- package/dist/abstractions/EventEmitter.d.ts.map +1 -1
- package/dist/abstractions/LockManager.d.ts +1 -1
- package/dist/abstractions/LockManager.d.ts.map +1 -1
- package/dist/abstractions/LockManager.js +1 -1
- package/dist/abstractions/PresenceExtension.d.ts +2 -2
- package/dist/abstractions/PresenceExtension.d.ts.map +1 -1
- package/dist/abstractions/PresenceManager.d.ts +1 -1
- package/dist/abstractions/PresenceManager.d.ts.map +1 -1
- package/dist/abstractions/PresenceManager.js +5 -5
- package/dist/abstractions/RoomManager.d.ts +2 -2
- package/dist/abstractions/RoomManager.d.ts.map +1 -1
- package/dist/abstractions/StorageAdapter.d.ts +4 -4
- package/dist/abstractions/StorageAdapter.d.ts.map +1 -1
- package/dist/abstractions/TransportAdapter.d.ts +4 -4
- package/dist/abstractions/TransportAdapter.d.ts.map +1 -1
- package/dist/abstractions/index.d.ts +11 -11
- package/dist/abstractions/index.d.ts.map +1 -1
- package/dist/abstractions/index.js +9 -9
- package/dist/adapters/mock/MockAuthAdapter.d.ts +2 -2
- package/dist/adapters/mock/MockAuthAdapter.d.ts.map +1 -1
- package/dist/adapters/mock/MockAuthAdapter.js +13 -11
- package/dist/adapters/mock/MockRTCDataChannel.d.ts +83 -0
- package/dist/adapters/mock/MockRTCDataChannel.d.ts.map +1 -0
- package/dist/adapters/mock/MockRTCDataChannel.js +146 -0
- package/dist/adapters/mock/MockRTCPeerConnection.d.ts +168 -0
- package/dist/adapters/mock/MockRTCPeerConnection.d.ts.map +1 -0
- package/dist/adapters/mock/MockRTCPeerConnection.js +449 -0
- package/dist/adapters/mock/MockStorageAdapter.d.ts +1 -1
- package/dist/adapters/mock/MockStorageAdapter.d.ts.map +1 -1
- package/dist/adapters/mock/MockStorageAdapter.js +18 -18
- package/dist/adapters/mock/MockTransportAdapter.d.ts +2 -2
- package/dist/adapters/mock/MockTransportAdapter.d.ts.map +1 -1
- package/dist/adapters/mock/MockTransportAdapter.js +38 -38
- package/dist/adapters/mock/index.d.ts +5 -3
- package/dist/adapters/mock/index.d.ts.map +1 -1
- package/dist/adapters/mock/index.js +10 -5
- package/dist/adapters/webrtc/WebRTCSignalingAdapter.d.ts +135 -0
- package/dist/adapters/webrtc/WebRTCSignalingAdapter.d.ts.map +1 -0
- package/dist/adapters/webrtc/WebRTCSignalingAdapter.js +368 -0
- package/dist/adapters/webrtc/index.d.ts +2 -0
- package/dist/adapters/webrtc/index.d.ts.map +1 -0
- package/dist/adapters/webrtc/index.js +5 -0
- package/dist/adapters/websocket/BrowserWebSocketTransportAdapter.d.ts +75 -0
- package/dist/adapters/websocket/BrowserWebSocketTransportAdapter.d.ts.map +1 -0
- package/dist/adapters/websocket/BrowserWebSocketTransportAdapter.js +231 -0
- package/dist/adapters/websocket/WebSocketClientTransportAdapter.d.ts +3 -3
- package/dist/adapters/websocket/WebSocketClientTransportAdapter.d.ts.map +1 -1
- package/dist/adapters/websocket/WebSocketClientTransportAdapter.js +38 -38
- package/dist/adapters/websocket/WebSocketServerTransportAdapter.d.ts +7 -7
- package/dist/adapters/websocket/WebSocketServerTransportAdapter.d.ts.map +1 -1
- package/dist/adapters/websocket/WebSocketServerTransportAdapter.js +94 -91
- package/dist/adapters/websocket/browser.d.ts +2 -0
- package/dist/adapters/websocket/browser.d.ts.map +1 -0
- package/dist/adapters/websocket/browser.js +6 -0
- package/dist/adapters/websocket/index.d.ts +3 -2
- package/dist/adapters/websocket/index.d.ts.map +1 -1
- package/dist/adapters/websocket/index.js +7 -3
- package/dist/adapters/websocket/node.d.ts +3 -0
- package/dist/adapters/websocket/node.d.ts.map +1 -0
- package/dist/adapters/websocket/node.js +8 -0
- package/dist/client/BaseClient.d.ts +6 -6
- package/dist/client/BaseClient.d.ts.map +1 -1
- package/dist/client/BaseClient.js +86 -72
- package/dist/client/ClientBuilder.d.ts +5 -5
- package/dist/client/ClientBuilder.d.ts.map +1 -1
- package/dist/client/ClientBuilder.js +3 -3
- package/dist/client/PresenceClient.d.ts +4 -4
- package/dist/client/PresenceClient.d.ts.map +1 -1
- package/dist/client/PresenceClient.js +30 -30
- package/dist/client/index.d.ts +3 -3
- package/dist/client/index.d.ts.map +1 -1
- package/dist/index.d.ts +7 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -19
- package/dist/index.js.map +27 -23
- package/dist/index.mjs +1590 -558
- package/dist/index.mjs.map +27 -23
- package/dist/server/BaseServer.d.ts +13 -13
- package/dist/server/BaseServer.d.ts.map +1 -1
- package/dist/server/BaseServer.js +224 -143
- package/dist/server/ExperimentalAPI.d.ts +7 -7
- package/dist/server/ExperimentalAPI.d.ts.map +1 -1
- package/dist/server/ExperimentalAPI.js +22 -22
- package/dist/server/ServerBuilder.d.ts +11 -11
- package/dist/server/ServerBuilder.d.ts.map +1 -1
- package/dist/server/ServerBuilder.js +10 -10
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +3 -3
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/events.d.ts +10 -10
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/experimental.d.ts +2 -2
- package/dist/types/experimental.d.ts.map +1 -1
- package/dist/types/experimental.js +1 -1
- package/dist/types/index.d.ts +7 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -2
- package/dist/types/lock.d.ts +2 -2
- package/dist/types/lock.d.ts.map +1 -1
- package/dist/types/presence.d.ts +3 -3
- package/dist/types/presence.d.ts.map +1 -1
- package/dist/types/room.d.ts +4 -4
- package/dist/types/room.d.ts.map +1 -1
- package/dist/types/room.js +2 -2
- package/package.json +15 -7
package/dist/index.mjs
CHANGED
|
@@ -1345,7 +1345,7 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1345
1345
|
var kType = Symbol("kType");
|
|
1346
1346
|
var kWasClean = Symbol("kWasClean");
|
|
1347
1347
|
|
|
1348
|
-
class
|
|
1348
|
+
class Event {
|
|
1349
1349
|
constructor(type) {
|
|
1350
1350
|
this[kTarget] = null;
|
|
1351
1351
|
this[kType] = type;
|
|
@@ -1357,10 +1357,10 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1357
1357
|
return this[kType];
|
|
1358
1358
|
}
|
|
1359
1359
|
}
|
|
1360
|
-
Object.defineProperty(
|
|
1361
|
-
Object.defineProperty(
|
|
1360
|
+
Object.defineProperty(Event.prototype, "target", { enumerable: true });
|
|
1361
|
+
Object.defineProperty(Event.prototype, "type", { enumerable: true });
|
|
1362
1362
|
|
|
1363
|
-
class CloseEvent extends
|
|
1363
|
+
class CloseEvent extends Event {
|
|
1364
1364
|
constructor(type, options = {}) {
|
|
1365
1365
|
super(type);
|
|
1366
1366
|
this[kCode] = options.code === undefined ? 0 : options.code;
|
|
@@ -1381,7 +1381,7 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1381
1381
|
Object.defineProperty(CloseEvent.prototype, "reason", { enumerable: true });
|
|
1382
1382
|
Object.defineProperty(CloseEvent.prototype, "wasClean", { enumerable: true });
|
|
1383
1383
|
|
|
1384
|
-
class ErrorEvent extends
|
|
1384
|
+
class ErrorEvent extends Event {
|
|
1385
1385
|
constructor(type, options = {}) {
|
|
1386
1386
|
super(type);
|
|
1387
1387
|
this[kError] = options.error === undefined ? null : options.error;
|
|
@@ -1397,7 +1397,7 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1397
1397
|
Object.defineProperty(ErrorEvent.prototype, "error", { enumerable: true });
|
|
1398
1398
|
Object.defineProperty(ErrorEvent.prototype, "message", { enumerable: true });
|
|
1399
1399
|
|
|
1400
|
-
class MessageEvent extends
|
|
1400
|
+
class MessageEvent extends Event {
|
|
1401
1401
|
constructor(type, options = {}) {
|
|
1402
1402
|
super(type);
|
|
1403
1403
|
this[kData] = options.data === undefined ? null : options.data;
|
|
@@ -1444,7 +1444,7 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1444
1444
|
};
|
|
1445
1445
|
} else if (type === "open") {
|
|
1446
1446
|
wrapper = function onOpen() {
|
|
1447
|
-
const event = new
|
|
1447
|
+
const event = new Event("open");
|
|
1448
1448
|
event[kTarget] = this;
|
|
1449
1449
|
callListener(handler, this, event);
|
|
1450
1450
|
};
|
|
@@ -1471,7 +1471,7 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1471
1471
|
module.exports = {
|
|
1472
1472
|
CloseEvent,
|
|
1473
1473
|
ErrorEvent,
|
|
1474
|
-
Event
|
|
1474
|
+
Event,
|
|
1475
1475
|
EventTarget,
|
|
1476
1476
|
MessageEvent
|
|
1477
1477
|
};
|
|
@@ -1684,7 +1684,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1684
1684
|
var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
|
|
1685
1685
|
var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
|
1686
1686
|
|
|
1687
|
-
class
|
|
1687
|
+
class WebSocket2 extends EventEmitter {
|
|
1688
1688
|
constructor(address, protocols, options) {
|
|
1689
1689
|
super();
|
|
1690
1690
|
this._binaryType = BINARY_TYPES[0];
|
|
@@ -1697,7 +1697,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1697
1697
|
this._extensions = {};
|
|
1698
1698
|
this._paused = false;
|
|
1699
1699
|
this._protocol = "";
|
|
1700
|
-
this._readyState =
|
|
1700
|
+
this._readyState = WebSocket2.CONNECTING;
|
|
1701
1701
|
this._receiver = null;
|
|
1702
1702
|
this._sender = null;
|
|
1703
1703
|
this._socket = null;
|
|
@@ -1796,12 +1796,12 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1796
1796
|
socket.on("data", socketOnData);
|
|
1797
1797
|
socket.on("end", socketOnEnd);
|
|
1798
1798
|
socket.on("error", socketOnError);
|
|
1799
|
-
this._readyState =
|
|
1799
|
+
this._readyState = WebSocket2.OPEN;
|
|
1800
1800
|
this.emit("open");
|
|
1801
1801
|
}
|
|
1802
1802
|
emitClose() {
|
|
1803
1803
|
if (!this._socket) {
|
|
1804
|
-
this._readyState =
|
|
1804
|
+
this._readyState = WebSocket2.CLOSED;
|
|
1805
1805
|
this.emit("close", this._closeCode, this._closeMessage);
|
|
1806
1806
|
return;
|
|
1807
1807
|
}
|
|
@@ -1809,24 +1809,24 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1809
1809
|
this._extensions[PerMessageDeflate.extensionName].cleanup();
|
|
1810
1810
|
}
|
|
1811
1811
|
this._receiver.removeAllListeners();
|
|
1812
|
-
this._readyState =
|
|
1812
|
+
this._readyState = WebSocket2.CLOSED;
|
|
1813
1813
|
this.emit("close", this._closeCode, this._closeMessage);
|
|
1814
1814
|
}
|
|
1815
1815
|
close(code, data) {
|
|
1816
|
-
if (this.readyState ===
|
|
1816
|
+
if (this.readyState === WebSocket2.CLOSED)
|
|
1817
1817
|
return;
|
|
1818
|
-
if (this.readyState ===
|
|
1818
|
+
if (this.readyState === WebSocket2.CONNECTING) {
|
|
1819
1819
|
const msg = "WebSocket was closed before the connection was established";
|
|
1820
1820
|
abortHandshake(this, this._req, msg);
|
|
1821
1821
|
return;
|
|
1822
1822
|
}
|
|
1823
|
-
if (this.readyState ===
|
|
1823
|
+
if (this.readyState === WebSocket2.CLOSING) {
|
|
1824
1824
|
if (this._closeFrameSent && (this._closeFrameReceived || this._receiver._writableState.errorEmitted)) {
|
|
1825
1825
|
this._socket.end();
|
|
1826
1826
|
}
|
|
1827
1827
|
return;
|
|
1828
1828
|
}
|
|
1829
|
-
this._readyState =
|
|
1829
|
+
this._readyState = WebSocket2.CLOSING;
|
|
1830
1830
|
this._sender.close(code, data, !this._isServer, (err) => {
|
|
1831
1831
|
if (err)
|
|
1832
1832
|
return;
|
|
@@ -1838,14 +1838,14 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1838
1838
|
setCloseTimer(this);
|
|
1839
1839
|
}
|
|
1840
1840
|
pause() {
|
|
1841
|
-
if (this.readyState ===
|
|
1841
|
+
if (this.readyState === WebSocket2.CONNECTING || this.readyState === WebSocket2.CLOSED) {
|
|
1842
1842
|
return;
|
|
1843
1843
|
}
|
|
1844
1844
|
this._paused = true;
|
|
1845
1845
|
this._socket.pause();
|
|
1846
1846
|
}
|
|
1847
1847
|
ping(data, mask, cb) {
|
|
1848
|
-
if (this.readyState ===
|
|
1848
|
+
if (this.readyState === WebSocket2.CONNECTING) {
|
|
1849
1849
|
throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
|
|
1850
1850
|
}
|
|
1851
1851
|
if (typeof data === "function") {
|
|
@@ -1857,7 +1857,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1857
1857
|
}
|
|
1858
1858
|
if (typeof data === "number")
|
|
1859
1859
|
data = data.toString();
|
|
1860
|
-
if (this.readyState !==
|
|
1860
|
+
if (this.readyState !== WebSocket2.OPEN) {
|
|
1861
1861
|
sendAfterClose(this, data, cb);
|
|
1862
1862
|
return;
|
|
1863
1863
|
}
|
|
@@ -1866,7 +1866,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1866
1866
|
this._sender.ping(data || EMPTY_BUFFER, mask, cb);
|
|
1867
1867
|
}
|
|
1868
1868
|
pong(data, mask, cb) {
|
|
1869
|
-
if (this.readyState ===
|
|
1869
|
+
if (this.readyState === WebSocket2.CONNECTING) {
|
|
1870
1870
|
throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
|
|
1871
1871
|
}
|
|
1872
1872
|
if (typeof data === "function") {
|
|
@@ -1878,7 +1878,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1878
1878
|
}
|
|
1879
1879
|
if (typeof data === "number")
|
|
1880
1880
|
data = data.toString();
|
|
1881
|
-
if (this.readyState !==
|
|
1881
|
+
if (this.readyState !== WebSocket2.OPEN) {
|
|
1882
1882
|
sendAfterClose(this, data, cb);
|
|
1883
1883
|
return;
|
|
1884
1884
|
}
|
|
@@ -1887,7 +1887,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1887
1887
|
this._sender.pong(data || EMPTY_BUFFER, mask, cb);
|
|
1888
1888
|
}
|
|
1889
1889
|
resume() {
|
|
1890
|
-
if (this.readyState ===
|
|
1890
|
+
if (this.readyState === WebSocket2.CONNECTING || this.readyState === WebSocket2.CLOSED) {
|
|
1891
1891
|
return;
|
|
1892
1892
|
}
|
|
1893
1893
|
this._paused = false;
|
|
@@ -1895,7 +1895,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1895
1895
|
this._socket.resume();
|
|
1896
1896
|
}
|
|
1897
1897
|
send(data, options, cb) {
|
|
1898
|
-
if (this.readyState ===
|
|
1898
|
+
if (this.readyState === WebSocket2.CONNECTING) {
|
|
1899
1899
|
throw new Error("WebSocket is not open: readyState 0 (CONNECTING)");
|
|
1900
1900
|
}
|
|
1901
1901
|
if (typeof options === "function") {
|
|
@@ -1904,7 +1904,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1904
1904
|
}
|
|
1905
1905
|
if (typeof data === "number")
|
|
1906
1906
|
data = data.toString();
|
|
1907
|
-
if (this.readyState !==
|
|
1907
|
+
if (this.readyState !== WebSocket2.OPEN) {
|
|
1908
1908
|
sendAfterClose(this, data, cb);
|
|
1909
1909
|
return;
|
|
1910
1910
|
}
|
|
@@ -1921,48 +1921,48 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1921
1921
|
this._sender.send(data || EMPTY_BUFFER, opts, cb);
|
|
1922
1922
|
}
|
|
1923
1923
|
terminate() {
|
|
1924
|
-
if (this.readyState ===
|
|
1924
|
+
if (this.readyState === WebSocket2.CLOSED)
|
|
1925
1925
|
return;
|
|
1926
|
-
if (this.readyState ===
|
|
1926
|
+
if (this.readyState === WebSocket2.CONNECTING) {
|
|
1927
1927
|
const msg = "WebSocket was closed before the connection was established";
|
|
1928
1928
|
abortHandshake(this, this._req, msg);
|
|
1929
1929
|
return;
|
|
1930
1930
|
}
|
|
1931
1931
|
if (this._socket) {
|
|
1932
|
-
this._readyState =
|
|
1932
|
+
this._readyState = WebSocket2.CLOSING;
|
|
1933
1933
|
this._socket.destroy();
|
|
1934
1934
|
}
|
|
1935
1935
|
}
|
|
1936
1936
|
}
|
|
1937
|
-
Object.defineProperty(
|
|
1937
|
+
Object.defineProperty(WebSocket2, "CONNECTING", {
|
|
1938
1938
|
enumerable: true,
|
|
1939
1939
|
value: readyStates.indexOf("CONNECTING")
|
|
1940
1940
|
});
|
|
1941
|
-
Object.defineProperty(
|
|
1941
|
+
Object.defineProperty(WebSocket2.prototype, "CONNECTING", {
|
|
1942
1942
|
enumerable: true,
|
|
1943
1943
|
value: readyStates.indexOf("CONNECTING")
|
|
1944
1944
|
});
|
|
1945
|
-
Object.defineProperty(
|
|
1945
|
+
Object.defineProperty(WebSocket2, "OPEN", {
|
|
1946
1946
|
enumerable: true,
|
|
1947
1947
|
value: readyStates.indexOf("OPEN")
|
|
1948
1948
|
});
|
|
1949
|
-
Object.defineProperty(
|
|
1949
|
+
Object.defineProperty(WebSocket2.prototype, "OPEN", {
|
|
1950
1950
|
enumerable: true,
|
|
1951
1951
|
value: readyStates.indexOf("OPEN")
|
|
1952
1952
|
});
|
|
1953
|
-
Object.defineProperty(
|
|
1953
|
+
Object.defineProperty(WebSocket2, "CLOSING", {
|
|
1954
1954
|
enumerable: true,
|
|
1955
1955
|
value: readyStates.indexOf("CLOSING")
|
|
1956
1956
|
});
|
|
1957
|
-
Object.defineProperty(
|
|
1957
|
+
Object.defineProperty(WebSocket2.prototype, "CLOSING", {
|
|
1958
1958
|
enumerable: true,
|
|
1959
1959
|
value: readyStates.indexOf("CLOSING")
|
|
1960
1960
|
});
|
|
1961
|
-
Object.defineProperty(
|
|
1961
|
+
Object.defineProperty(WebSocket2, "CLOSED", {
|
|
1962
1962
|
enumerable: true,
|
|
1963
1963
|
value: readyStates.indexOf("CLOSED")
|
|
1964
1964
|
});
|
|
1965
|
-
Object.defineProperty(
|
|
1965
|
+
Object.defineProperty(WebSocket2.prototype, "CLOSED", {
|
|
1966
1966
|
enumerable: true,
|
|
1967
1967
|
value: readyStates.indexOf("CLOSED")
|
|
1968
1968
|
});
|
|
@@ -1975,10 +1975,10 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
1975
1975
|
"readyState",
|
|
1976
1976
|
"url"
|
|
1977
1977
|
].forEach((property) => {
|
|
1978
|
-
Object.defineProperty(
|
|
1978
|
+
Object.defineProperty(WebSocket2.prototype, property, { enumerable: true });
|
|
1979
1979
|
});
|
|
1980
1980
|
["open", "error", "close", "message"].forEach((method) => {
|
|
1981
|
-
Object.defineProperty(
|
|
1981
|
+
Object.defineProperty(WebSocket2.prototype, `on${method}`, {
|
|
1982
1982
|
enumerable: true,
|
|
1983
1983
|
get() {
|
|
1984
1984
|
for (const listener of this.listeners(method)) {
|
|
@@ -2002,9 +2002,9 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2002
2002
|
}
|
|
2003
2003
|
});
|
|
2004
2004
|
});
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
module.exports =
|
|
2005
|
+
WebSocket2.prototype.addEventListener = addEventListener;
|
|
2006
|
+
WebSocket2.prototype.removeEventListener = removeEventListener;
|
|
2007
|
+
module.exports = WebSocket2;
|
|
2008
2008
|
function initAsClient(websocket, address, protocols, options) {
|
|
2009
2009
|
const opts = {
|
|
2010
2010
|
allowSynchronousEvents: true,
|
|
@@ -2180,7 +2180,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2180
2180
|
});
|
|
2181
2181
|
req.on("upgrade", (res, socket, head) => {
|
|
2182
2182
|
websocket.emit("upgrade", res);
|
|
2183
|
-
if (websocket.readyState !==
|
|
2183
|
+
if (websocket.readyState !== WebSocket2.CONNECTING)
|
|
2184
2184
|
return;
|
|
2185
2185
|
req = websocket._req = null;
|
|
2186
2186
|
const upgrade = res.headers.upgrade;
|
|
@@ -2254,7 +2254,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2254
2254
|
}
|
|
2255
2255
|
}
|
|
2256
2256
|
function emitErrorAndClose(websocket, err) {
|
|
2257
|
-
websocket._readyState =
|
|
2257
|
+
websocket._readyState = WebSocket2.CLOSING;
|
|
2258
2258
|
websocket._errorEmitted = true;
|
|
2259
2259
|
websocket.emit("error", err);
|
|
2260
2260
|
websocket.emitClose();
|
|
@@ -2271,7 +2271,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2271
2271
|
return tls.connect(options);
|
|
2272
2272
|
}
|
|
2273
2273
|
function abortHandshake(websocket, stream, message) {
|
|
2274
|
-
websocket._readyState =
|
|
2274
|
+
websocket._readyState = WebSocket2.CLOSING;
|
|
2275
2275
|
const err = new Error(message);
|
|
2276
2276
|
Error.captureStackTrace(err, abortHandshake);
|
|
2277
2277
|
if (stream.setHeader) {
|
|
@@ -2351,10 +2351,10 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2351
2351
|
}
|
|
2352
2352
|
function senderOnError(err) {
|
|
2353
2353
|
const websocket = this[kWebSocket];
|
|
2354
|
-
if (websocket.readyState ===
|
|
2354
|
+
if (websocket.readyState === WebSocket2.CLOSED)
|
|
2355
2355
|
return;
|
|
2356
|
-
if (websocket.readyState ===
|
|
2357
|
-
websocket._readyState =
|
|
2356
|
+
if (websocket.readyState === WebSocket2.OPEN) {
|
|
2357
|
+
websocket._readyState = WebSocket2.CLOSING;
|
|
2358
2358
|
setCloseTimer(websocket);
|
|
2359
2359
|
}
|
|
2360
2360
|
this._socket.end();
|
|
@@ -2371,7 +2371,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2371
2371
|
this.removeListener("close", socketOnClose);
|
|
2372
2372
|
this.removeListener("data", socketOnData);
|
|
2373
2373
|
this.removeListener("end", socketOnEnd);
|
|
2374
|
-
websocket._readyState =
|
|
2374
|
+
websocket._readyState = WebSocket2.CLOSING;
|
|
2375
2375
|
let chunk;
|
|
2376
2376
|
if (!this._readableState.endEmitted && !websocket._closeFrameReceived && !websocket._receiver._writableState.errorEmitted && (chunk = websocket._socket.read()) !== null) {
|
|
2377
2377
|
websocket._receiver.write(chunk);
|
|
@@ -2393,7 +2393,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2393
2393
|
}
|
|
2394
2394
|
function socketOnEnd() {
|
|
2395
2395
|
const websocket = this[kWebSocket];
|
|
2396
|
-
websocket._readyState =
|
|
2396
|
+
websocket._readyState = WebSocket2.CLOSING;
|
|
2397
2397
|
websocket._receiver.end();
|
|
2398
2398
|
this.end();
|
|
2399
2399
|
}
|
|
@@ -2402,7 +2402,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2402
2402
|
this.removeListener("error", socketOnError);
|
|
2403
2403
|
this.on("error", NOOP);
|
|
2404
2404
|
if (websocket) {
|
|
2405
|
-
websocket._readyState =
|
|
2405
|
+
websocket._readyState = WebSocket2.CLOSING;
|
|
2406
2406
|
this.destroy();
|
|
2407
2407
|
}
|
|
2408
2408
|
}
|
|
@@ -2410,7 +2410,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2410
2410
|
|
|
2411
2411
|
// node_modules/ws/lib/stream.js
|
|
2412
2412
|
var require_stream = __commonJS((exports, module) => {
|
|
2413
|
-
var
|
|
2413
|
+
var WebSocket2 = require_websocket();
|
|
2414
2414
|
var { Duplex } = __require("stream");
|
|
2415
2415
|
function emitClose(stream) {
|
|
2416
2416
|
stream.emit("close");
|
|
@@ -2565,7 +2565,7 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
2565
2565
|
var extension = require_extension();
|
|
2566
2566
|
var PerMessageDeflate = require_permessage_deflate();
|
|
2567
2567
|
var subprotocol = require_subprotocol();
|
|
2568
|
-
var
|
|
2568
|
+
var WebSocket2 = require_websocket();
|
|
2569
2569
|
var { GUID, kWebSocket } = require_constants();
|
|
2570
2570
|
var keyRegex = /^[+/0-9A-Za-z]{22}==$/;
|
|
2571
2571
|
var RUNNING = 0;
|
|
@@ -2590,7 +2590,7 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
2590
2590
|
host: null,
|
|
2591
2591
|
path: null,
|
|
2592
2592
|
port: null,
|
|
2593
|
-
WebSocket,
|
|
2593
|
+
WebSocket: WebSocket2,
|
|
2594
2594
|
...options
|
|
2595
2595
|
};
|
|
2596
2596
|
if (options.port == null && !options.server && !options.noServer || options.port != null && (options.server || options.noServer) || options.server && options.noServer) {
|
|
@@ -2856,103 +2856,6 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
2856
2856
|
}
|
|
2857
2857
|
}
|
|
2858
2858
|
});
|
|
2859
|
-
// src/types/room.ts
|
|
2860
|
-
function serializeRoomState(state) {
|
|
2861
|
-
return {
|
|
2862
|
-
room: state.room,
|
|
2863
|
-
users: Object.fromEntries(state.users),
|
|
2864
|
-
eventHistory: state.eventHistory,
|
|
2865
|
-
locks: Object.fromEntries(state.locks)
|
|
2866
|
-
};
|
|
2867
|
-
}
|
|
2868
|
-
function deserializeRoomState(serialized) {
|
|
2869
|
-
return {
|
|
2870
|
-
room: serialized.room,
|
|
2871
|
-
users: new Map(Object.entries(serialized.users)),
|
|
2872
|
-
eventHistory: serialized.eventHistory,
|
|
2873
|
-
locks: new Map(Object.entries(serialized.locks))
|
|
2874
|
-
};
|
|
2875
|
-
}
|
|
2876
|
-
// src/types/experimental.ts
|
|
2877
|
-
class ExperimentalFeatureError extends Error {
|
|
2878
|
-
feature;
|
|
2879
|
-
configKey;
|
|
2880
|
-
constructor(feature, configKey) {
|
|
2881
|
-
super(`Experimental feature '${feature}' is not enabled.
|
|
2882
|
-
|
|
2883
|
-
` + `To enable, set '${String(configKey)}: true' in your server configuration:
|
|
2884
|
-
|
|
2885
|
-
` + ` const server = new ServerBuilder()
|
|
2886
|
-
` + ` .withExperimentalFeatures({ ${String(configKey)}: true })
|
|
2887
|
-
` + ` .build();
|
|
2888
|
-
|
|
2889
|
-
` + `⚠️ WARNING: Experimental APIs are for development only.
|
|
2890
|
-
` + `For production use, submit a feature request:
|
|
2891
|
-
` + `https://github.com/your-org/control-tower-core/issues/new
|
|
2892
|
-
|
|
2893
|
-
` + `See docs/EXPERIMENTAL_BROADCAST.md for details.`);
|
|
2894
|
-
this.feature = feature;
|
|
2895
|
-
this.configKey = configKey;
|
|
2896
|
-
this.name = "ExperimentalFeatureError";
|
|
2897
|
-
}
|
|
2898
|
-
}
|
|
2899
|
-
// src/abstractions/EventEmitter.ts
|
|
2900
|
-
class TypedEventEmitter {
|
|
2901
|
-
listeners = new Map;
|
|
2902
|
-
onceListeners = new Map;
|
|
2903
|
-
on(event, listener) {
|
|
2904
|
-
if (!this.listeners.has(event)) {
|
|
2905
|
-
this.listeners.set(event, new Set);
|
|
2906
|
-
}
|
|
2907
|
-
this.listeners.get(event).add(listener);
|
|
2908
|
-
return () => this.off(event, listener);
|
|
2909
|
-
}
|
|
2910
|
-
once(event, listener) {
|
|
2911
|
-
if (!this.onceListeners.has(event)) {
|
|
2912
|
-
this.onceListeners.set(event, new Set);
|
|
2913
|
-
}
|
|
2914
|
-
this.onceListeners.get(event).add(listener);
|
|
2915
|
-
return () => this.off(event, listener);
|
|
2916
|
-
}
|
|
2917
|
-
off(event, listener) {
|
|
2918
|
-
this.listeners.get(event)?.delete(listener);
|
|
2919
|
-
this.onceListeners.get(event)?.delete(listener);
|
|
2920
|
-
}
|
|
2921
|
-
async emit(event, data) {
|
|
2922
|
-
const listeners = this.listeners.get(event);
|
|
2923
|
-
const onceListeners = this.onceListeners.get(event);
|
|
2924
|
-
if (onceListeners) {
|
|
2925
|
-
const listenersToCall = Array.from(onceListeners);
|
|
2926
|
-
this.onceListeners.delete(event);
|
|
2927
|
-
for (const listener of listenersToCall) {
|
|
2928
|
-
await listener(data);
|
|
2929
|
-
}
|
|
2930
|
-
}
|
|
2931
|
-
if (listeners) {
|
|
2932
|
-
for (const listener of Array.from(listeners)) {
|
|
2933
|
-
await listener(data);
|
|
2934
|
-
}
|
|
2935
|
-
}
|
|
2936
|
-
}
|
|
2937
|
-
removeAllListeners(event) {
|
|
2938
|
-
if (event) {
|
|
2939
|
-
this.listeners.delete(event);
|
|
2940
|
-
this.onceListeners.delete(event);
|
|
2941
|
-
} else {
|
|
2942
|
-
this.listeners.clear();
|
|
2943
|
-
this.onceListeners.clear();
|
|
2944
|
-
}
|
|
2945
|
-
}
|
|
2946
|
-
listenerCount(event) {
|
|
2947
|
-
const regular = this.listeners.get(event)?.size ?? 0;
|
|
2948
|
-
const once = this.onceListeners.get(event)?.size ?? 0;
|
|
2949
|
-
return regular + once;
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
// src/abstractions/RoomManager.ts
|
|
2953
|
-
class RoomManager {
|
|
2954
|
-
rooms = new Map;
|
|
2955
|
-
}
|
|
2956
2859
|
// src/abstractions/LockManager.ts
|
|
2957
2860
|
class LockManager {
|
|
2958
2861
|
lockState = {
|
|
@@ -2961,98 +2864,18 @@ class LockManager {
|
|
|
2961
2864
|
userLocks: new Map
|
|
2962
2865
|
};
|
|
2963
2866
|
}
|
|
2964
|
-
|
|
2965
|
-
class DefaultRoomManager extends RoomManager {
|
|
2966
|
-
async createRoom(id, config) {
|
|
2967
|
-
const room = {
|
|
2968
|
-
id,
|
|
2969
|
-
name: config.name || id,
|
|
2970
|
-
createdAt: Date.now(),
|
|
2971
|
-
maxUsers: config.maxUsers,
|
|
2972
|
-
maxHistory: config.maxHistory,
|
|
2973
|
-
permissions: config.permissions,
|
|
2974
|
-
metadata: config.metadata || {}
|
|
2975
|
-
};
|
|
2976
|
-
const roomState = {
|
|
2977
|
-
room,
|
|
2978
|
-
users: new Map,
|
|
2979
|
-
eventHistory: [],
|
|
2980
|
-
locks: new Map
|
|
2981
|
-
};
|
|
2982
|
-
this.rooms.set(id, roomState);
|
|
2983
|
-
return room;
|
|
2984
|
-
}
|
|
2985
|
-
async deleteRoom(id) {
|
|
2986
|
-
this.rooms.delete(id);
|
|
2987
|
-
}
|
|
2988
|
-
async joinRoom(roomId, user) {
|
|
2989
|
-
const roomState = this.rooms.get(roomId);
|
|
2990
|
-
if (!roomState) {
|
|
2991
|
-
throw new Error(`Room ${roomId} not found`);
|
|
2992
|
-
}
|
|
2993
|
-
if (roomState.room.maxUsers && roomState.users.size >= roomState.room.maxUsers) {
|
|
2994
|
-
throw new Error(`Room ${roomId} is full`);
|
|
2995
|
-
}
|
|
2996
|
-
roomState.users.set(user.id, user);
|
|
2997
|
-
}
|
|
2998
|
-
async leaveRoom(roomId, userId) {
|
|
2999
|
-
const roomState = this.rooms.get(roomId);
|
|
3000
|
-
if (!roomState) {
|
|
3001
|
-
return;
|
|
3002
|
-
}
|
|
3003
|
-
roomState.users.delete(userId);
|
|
3004
|
-
}
|
|
3005
|
-
async broadcastToRoom(roomId, event, _excludeUserId) {
|
|
3006
|
-
const roomState = this.rooms.get(roomId);
|
|
3007
|
-
if (!roomState) {
|
|
3008
|
-
throw new Error(`Room ${roomId} not found`);
|
|
3009
|
-
}
|
|
3010
|
-
await this.addEventToHistory(roomId, event);
|
|
3011
|
-
}
|
|
3012
|
-
async getRoomState(roomId) {
|
|
3013
|
-
return this.rooms.get(roomId) || null;
|
|
3014
|
-
}
|
|
3015
|
-
async getUsersInRoom(roomId) {
|
|
3016
|
-
const roomState = this.rooms.get(roomId);
|
|
3017
|
-
if (!roomState) {
|
|
3018
|
-
return [];
|
|
3019
|
-
}
|
|
3020
|
-
return Array.from(roomState.users.values());
|
|
3021
|
-
}
|
|
3022
|
-
async addEventToHistory(roomId, event) {
|
|
3023
|
-
const roomState = this.rooms.get(roomId);
|
|
3024
|
-
if (!roomState) {
|
|
3025
|
-
throw new Error(`Room ${roomId} not found`);
|
|
3026
|
-
}
|
|
3027
|
-
roomState.eventHistory.push(event);
|
|
3028
|
-
if (roomState.room.maxHistory && roomState.eventHistory.length > roomState.room.maxHistory) {
|
|
3029
|
-
roomState.eventHistory = roomState.eventHistory.slice(-roomState.room.maxHistory);
|
|
3030
|
-
}
|
|
3031
|
-
}
|
|
3032
|
-
async getEventHistory(roomId, limit) {
|
|
3033
|
-
const roomState = this.rooms.get(roomId);
|
|
3034
|
-
if (!roomState) {
|
|
3035
|
-
return [];
|
|
3036
|
-
}
|
|
3037
|
-
const history = roomState.eventHistory;
|
|
3038
|
-
return limit ? history.slice(-limit) : history;
|
|
3039
|
-
}
|
|
3040
|
-
async updateUserStatus(roomId, userId, status) {
|
|
3041
|
-
const roomState = this.rooms.get(roomId);
|
|
3042
|
-
if (!roomState) {
|
|
3043
|
-
throw new Error(`Room ${roomId} not found`);
|
|
3044
|
-
}
|
|
3045
|
-
const user = roomState.users.get(userId);
|
|
3046
|
-
if (user) {
|
|
3047
|
-
user.status = status;
|
|
3048
|
-
user.lastActivity = Date.now();
|
|
3049
|
-
}
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
2867
|
+
|
|
3052
2868
|
// src/abstractions/DefaultLockManager.ts
|
|
3053
2869
|
class DefaultLockManager extends LockManager {
|
|
3054
2870
|
async acquireLock(userId, username, request) {
|
|
3055
|
-
const {
|
|
2871
|
+
const {
|
|
2872
|
+
path,
|
|
2873
|
+
type = "file",
|
|
2874
|
+
priority = "normal",
|
|
2875
|
+
ttl,
|
|
2876
|
+
branch,
|
|
2877
|
+
metadata
|
|
2878
|
+
} = request;
|
|
3056
2879
|
const userLocks = this.lockState.userLocks.get(userId) || new Set;
|
|
3057
2880
|
if (userLocks.has(path)) {
|
|
3058
2881
|
throw new Error(`User ${username} already has a lock on ${path}`);
|
|
@@ -3225,6 +3048,7 @@ class PresenceManager {
|
|
|
3225
3048
|
return now - device.lastActivity > timeout;
|
|
3226
3049
|
}
|
|
3227
3050
|
}
|
|
3051
|
+
|
|
3228
3052
|
// src/abstractions/DefaultPresenceManager.ts
|
|
3229
3053
|
class DefaultPresenceManager extends PresenceManager {
|
|
3230
3054
|
userPresences = new Map;
|
|
@@ -3550,211 +3374,765 @@ class DefaultPresenceManager extends PresenceManager {
|
|
|
3550
3374
|
return this.userPresences.size + this.gracePeriodEntries.size;
|
|
3551
3375
|
}
|
|
3552
3376
|
}
|
|
3553
|
-
// src/
|
|
3554
|
-
class
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3377
|
+
// src/abstractions/RoomManager.ts
|
|
3378
|
+
class RoomManager {
|
|
3379
|
+
rooms = new Map;
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
// src/abstractions/DefaultRoomManager.ts
|
|
3383
|
+
class DefaultRoomManager extends RoomManager {
|
|
3384
|
+
async createRoom(id, config) {
|
|
3385
|
+
const room = {
|
|
3386
|
+
id,
|
|
3387
|
+
name: config.name || id,
|
|
3388
|
+
createdAt: Date.now(),
|
|
3389
|
+
maxUsers: config.maxUsers,
|
|
3390
|
+
maxHistory: config.maxHistory,
|
|
3391
|
+
permissions: config.permissions,
|
|
3392
|
+
metadata: config.metadata || {}
|
|
3393
|
+
};
|
|
3394
|
+
const roomState = {
|
|
3395
|
+
room,
|
|
3396
|
+
users: new Map,
|
|
3397
|
+
eventHistory: [],
|
|
3398
|
+
locks: new Map
|
|
3399
|
+
};
|
|
3400
|
+
this.rooms.set(id, roomState);
|
|
3401
|
+
return room;
|
|
3567
3402
|
}
|
|
3568
|
-
async
|
|
3569
|
-
this.
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
throw
|
|
3403
|
+
async deleteRoom(id) {
|
|
3404
|
+
this.rooms.delete(id);
|
|
3405
|
+
}
|
|
3406
|
+
async joinRoom(roomId, user) {
|
|
3407
|
+
const roomState = this.rooms.get(roomId);
|
|
3408
|
+
if (!roomState) {
|
|
3409
|
+
throw new Error(`Room ${roomId} not found`);
|
|
3575
3410
|
}
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
if (this.simulateLatency > 0) {
|
|
3579
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
3411
|
+
if (roomState.room.maxUsers && roomState.users.size >= roomState.room.maxUsers) {
|
|
3412
|
+
throw new Error(`Room ${roomId} is full`);
|
|
3580
3413
|
}
|
|
3581
|
-
|
|
3414
|
+
roomState.users.set(user.id, user);
|
|
3582
3415
|
}
|
|
3583
|
-
async
|
|
3584
|
-
|
|
3416
|
+
async leaveRoom(roomId, userId) {
|
|
3417
|
+
const roomState = this.rooms.get(roomId);
|
|
3418
|
+
if (!roomState) {
|
|
3585
3419
|
return;
|
|
3586
|
-
this.state = "disconnecting";
|
|
3587
|
-
if (this.simulateLatency > 0) {
|
|
3588
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency / 2));
|
|
3589
3420
|
}
|
|
3590
|
-
|
|
3591
|
-
this.connectedUrl = null;
|
|
3592
|
-
this.closeHandlers.forEach((handler) => handler(1000, "Normal closure"));
|
|
3593
|
-
this.messageQueue = [];
|
|
3421
|
+
roomState.users.delete(userId);
|
|
3594
3422
|
}
|
|
3595
|
-
async
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
if (this.simulateLatency > 0) {
|
|
3600
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
3423
|
+
async broadcastToRoom(roomId, event, _excludeUserId) {
|
|
3424
|
+
const roomState = this.rooms.get(roomId);
|
|
3425
|
+
if (!roomState) {
|
|
3426
|
+
throw new Error(`Room ${roomId} not found`);
|
|
3601
3427
|
}
|
|
3602
|
-
this.
|
|
3603
|
-
}
|
|
3604
|
-
onMessage(handler) {
|
|
3605
|
-
this.messageHandlers.add(handler);
|
|
3606
|
-
}
|
|
3607
|
-
onError(handler) {
|
|
3608
|
-
this.errorHandlers.add(handler);
|
|
3609
|
-
}
|
|
3610
|
-
onClose(handler) {
|
|
3611
|
-
this.closeHandlers.add(handler);
|
|
3612
|
-
}
|
|
3613
|
-
getState() {
|
|
3614
|
-
return this.state;
|
|
3428
|
+
await this.addEventToHistory(roomId, event);
|
|
3615
3429
|
}
|
|
3616
|
-
|
|
3617
|
-
return this.
|
|
3430
|
+
async getRoomState(roomId) {
|
|
3431
|
+
return this.rooms.get(roomId) || null;
|
|
3618
3432
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3433
|
+
async getUsersInRoom(roomId) {
|
|
3434
|
+
const roomState = this.rooms.get(roomId);
|
|
3435
|
+
if (!roomState) {
|
|
3436
|
+
return [];
|
|
3622
3437
|
}
|
|
3623
|
-
|
|
3624
|
-
}
|
|
3625
|
-
simulateError(error) {
|
|
3626
|
-
this.errorHandlers.forEach((handler) => handler(error));
|
|
3627
|
-
}
|
|
3628
|
-
simulateClose(code, reason) {
|
|
3629
|
-
this.state = "disconnected";
|
|
3630
|
-
this.closeHandlers.forEach((handler) => handler(code, reason));
|
|
3631
|
-
}
|
|
3632
|
-
getMessageQueue() {
|
|
3633
|
-
return [...this.messageQueue];
|
|
3438
|
+
return Array.from(roomState.users.values());
|
|
3634
3439
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3440
|
+
async addEventToHistory(roomId, event) {
|
|
3441
|
+
const roomState = this.rooms.get(roomId);
|
|
3442
|
+
if (!roomState) {
|
|
3443
|
+
throw new Error(`Room ${roomId} not found`);
|
|
3444
|
+
}
|
|
3445
|
+
roomState.eventHistory.push(event);
|
|
3446
|
+
if (roomState.room.maxHistory && roomState.eventHistory.length > roomState.room.maxHistory) {
|
|
3447
|
+
roomState.eventHistory = roomState.eventHistory.slice(-roomState.room.maxHistory);
|
|
3448
|
+
}
|
|
3637
3449
|
}
|
|
3638
|
-
|
|
3639
|
-
|
|
3450
|
+
async getEventHistory(roomId, limit) {
|
|
3451
|
+
const roomState = this.rooms.get(roomId);
|
|
3452
|
+
if (!roomState) {
|
|
3453
|
+
return [];
|
|
3454
|
+
}
|
|
3455
|
+
const history = roomState.eventHistory;
|
|
3456
|
+
return limit ? history.slice(-limit) : history;
|
|
3640
3457
|
}
|
|
3641
|
-
|
|
3642
|
-
|
|
3458
|
+
async updateUserStatus(roomId, userId, status) {
|
|
3459
|
+
const roomState = this.rooms.get(roomId);
|
|
3460
|
+
if (!roomState) {
|
|
3461
|
+
throw new Error(`Room ${roomId} not found`);
|
|
3462
|
+
}
|
|
3463
|
+
const user = roomState.users.get(userId);
|
|
3464
|
+
if (user) {
|
|
3465
|
+
user.status = status;
|
|
3466
|
+
user.lastActivity = Date.now();
|
|
3467
|
+
}
|
|
3643
3468
|
}
|
|
3644
|
-
|
|
3645
|
-
|
|
3469
|
+
}
|
|
3470
|
+
// src/abstractions/EventEmitter.ts
|
|
3471
|
+
class TypedEventEmitter {
|
|
3472
|
+
listeners = new Map;
|
|
3473
|
+
onceListeners = new Map;
|
|
3474
|
+
on(event, listener) {
|
|
3475
|
+
if (!this.listeners.has(event)) {
|
|
3476
|
+
this.listeners.set(event, new Set);
|
|
3477
|
+
}
|
|
3478
|
+
this.listeners.get(event).add(listener);
|
|
3479
|
+
return () => this.off(event, listener);
|
|
3646
3480
|
}
|
|
3647
|
-
|
|
3648
|
-
if (this.
|
|
3649
|
-
this.
|
|
3481
|
+
once(event, listener) {
|
|
3482
|
+
if (!this.onceListeners.has(event)) {
|
|
3483
|
+
this.onceListeners.set(event, new Set);
|
|
3650
3484
|
}
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
type: "connection",
|
|
3654
|
-
payload: {
|
|
3655
|
-
clientId,
|
|
3656
|
-
authenticated: options.authenticated ?? false,
|
|
3657
|
-
userId: options.userId,
|
|
3658
|
-
metadata: options.metadata
|
|
3659
|
-
},
|
|
3660
|
-
timestamp: Date.now()
|
|
3661
|
-
};
|
|
3662
|
-
this.simulateMessage(message, true);
|
|
3485
|
+
this.onceListeners.get(event).add(listener);
|
|
3486
|
+
return () => this.off(event, listener);
|
|
3663
3487
|
}
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
type: "disconnect",
|
|
3668
|
-
payload: {
|
|
3669
|
-
clientId,
|
|
3670
|
-
reason
|
|
3671
|
-
},
|
|
3672
|
-
timestamp: Date.now()
|
|
3673
|
-
};
|
|
3674
|
-
this.simulateMessage(message, true);
|
|
3488
|
+
off(event, listener) {
|
|
3489
|
+
this.listeners.get(event)?.delete(listener);
|
|
3490
|
+
this.onceListeners.get(event)?.delete(listener);
|
|
3675
3491
|
}
|
|
3676
|
-
async
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
}
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3492
|
+
async emit(event, data) {
|
|
3493
|
+
const listeners = this.listeners.get(event);
|
|
3494
|
+
const onceListeners = this.onceListeners.get(event);
|
|
3495
|
+
if (onceListeners) {
|
|
3496
|
+
const listenersToCall = Array.from(onceListeners);
|
|
3497
|
+
this.onceListeners.delete(event);
|
|
3498
|
+
for (const listener of listenersToCall) {
|
|
3499
|
+
await listener(data);
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
if (listeners) {
|
|
3503
|
+
for (const listener of Array.from(listeners)) {
|
|
3504
|
+
await listener(data);
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3688
3507
|
}
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
}
|
|
3508
|
+
removeAllListeners(event) {
|
|
3509
|
+
if (event) {
|
|
3510
|
+
this.listeners.delete(event);
|
|
3511
|
+
this.onceListeners.delete(event);
|
|
3512
|
+
} else {
|
|
3513
|
+
this.listeners.clear();
|
|
3514
|
+
this.onceListeners.clear();
|
|
3515
|
+
}
|
|
3697
3516
|
}
|
|
3698
|
-
|
|
3699
|
-
const
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
payload: message.payload || {},
|
|
3703
|
-
timestamp: message.timestamp || Date.now()
|
|
3704
|
-
};
|
|
3705
|
-
this.messageHandlers.forEach((handler) => handler(fullMessage));
|
|
3517
|
+
listenerCount(event) {
|
|
3518
|
+
const regular = this.listeners.get(event)?.size ?? 0;
|
|
3519
|
+
const once = this.onceListeners.get(event)?.size ?? 0;
|
|
3520
|
+
return regular + once;
|
|
3706
3521
|
}
|
|
3707
3522
|
}
|
|
3708
|
-
// src/adapters/mock/
|
|
3709
|
-
class
|
|
3710
|
-
|
|
3523
|
+
// src/adapters/mock/MockAuthAdapter.ts
|
|
3524
|
+
class MockAuthAdapter {
|
|
3525
|
+
tokens = new Map;
|
|
3526
|
+
revokedTokens = new Set;
|
|
3711
3527
|
simulateLatency = 0;
|
|
3712
|
-
|
|
3528
|
+
shouldFailAuth = false;
|
|
3529
|
+
tokenCounter = 0;
|
|
3530
|
+
currentToken = null;
|
|
3713
3531
|
constructor(options) {
|
|
3714
3532
|
this.simulateLatency = options?.simulateLatency ?? 0;
|
|
3715
|
-
this.
|
|
3533
|
+
this.shouldFailAuth = options?.shouldFailAuth ?? false;
|
|
3716
3534
|
}
|
|
3717
|
-
async
|
|
3535
|
+
async validateToken(token) {
|
|
3718
3536
|
await this.simulateDelay();
|
|
3719
|
-
if (this.
|
|
3720
|
-
throw new Error("Mock
|
|
3537
|
+
if (this.shouldFailAuth) {
|
|
3538
|
+
throw new Error("Mock auth validation failed");
|
|
3721
3539
|
}
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
return null;
|
|
3725
|
-
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
3726
|
-
this.storage.delete(key);
|
|
3727
|
-
return null;
|
|
3540
|
+
if (this.revokedTokens.has(token)) {
|
|
3541
|
+
throw new Error("Token has been revoked");
|
|
3728
3542
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
await this.simulateDelay();
|
|
3733
|
-
if (this.shouldFailOperations) {
|
|
3734
|
-
throw new Error("Mock storage operation failed");
|
|
3543
|
+
const payload = this.tokens.get(token);
|
|
3544
|
+
if (!payload) {
|
|
3545
|
+
throw new Error("Invalid token");
|
|
3735
3546
|
}
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
}
|
|
3740
|
-
|
|
3547
|
+
if (payload.expiresAt && payload.expiresAt < Date.now()) {
|
|
3548
|
+
this.tokens.delete(token);
|
|
3549
|
+
throw new Error("Token expired");
|
|
3550
|
+
}
|
|
3551
|
+
return payload;
|
|
3741
3552
|
}
|
|
3742
|
-
async
|
|
3553
|
+
async generateToken(payload) {
|
|
3743
3554
|
await this.simulateDelay();
|
|
3744
|
-
if (this.
|
|
3745
|
-
throw new Error("Mock
|
|
3555
|
+
if (this.shouldFailAuth) {
|
|
3556
|
+
throw new Error("Mock token generation failed");
|
|
3746
3557
|
}
|
|
3747
|
-
this.
|
|
3558
|
+
const token = `mock-token-${++this.tokenCounter}`;
|
|
3559
|
+
const expiresAt = payload.expiresAt ?? Date.now() + 3600000;
|
|
3560
|
+
this.tokens.set(token, { ...payload, expiresAt });
|
|
3561
|
+
return token;
|
|
3748
3562
|
}
|
|
3749
|
-
async
|
|
3563
|
+
async refreshToken(token) {
|
|
3750
3564
|
await this.simulateDelay();
|
|
3751
|
-
if (this.
|
|
3752
|
-
throw new Error("Mock
|
|
3565
|
+
if (this.shouldFailAuth) {
|
|
3566
|
+
throw new Error("Mock token refresh failed");
|
|
3753
3567
|
}
|
|
3754
|
-
const
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3568
|
+
const payload = await this.validateToken(token);
|
|
3569
|
+
await this.revokeToken(token);
|
|
3570
|
+
const newPayload = {
|
|
3571
|
+
...payload,
|
|
3572
|
+
expiresAt: Date.now() + 3600000
|
|
3573
|
+
};
|
|
3574
|
+
return this.generateToken(newPayload);
|
|
3575
|
+
}
|
|
3576
|
+
async revokeToken(token) {
|
|
3577
|
+
await this.simulateDelay();
|
|
3578
|
+
if (this.shouldFailAuth) {
|
|
3579
|
+
throw new Error("Mock token revocation failed");
|
|
3580
|
+
}
|
|
3581
|
+
this.tokens.delete(token);
|
|
3582
|
+
this.revokedTokens.add(token);
|
|
3583
|
+
}
|
|
3584
|
+
getCurrentToken() {
|
|
3585
|
+
return this.currentToken;
|
|
3586
|
+
}
|
|
3587
|
+
isAuthRequired() {
|
|
3588
|
+
return false;
|
|
3589
|
+
}
|
|
3590
|
+
getAuthTimeout() {
|
|
3591
|
+
return 1e4;
|
|
3592
|
+
}
|
|
3593
|
+
async simulateDelay() {
|
|
3594
|
+
if (this.simulateLatency > 0) {
|
|
3595
|
+
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3598
|
+
async createTestToken(userId, options) {
|
|
3599
|
+
const payload = {
|
|
3600
|
+
userId,
|
|
3601
|
+
permissions: options?.permissions ?? ["read", "write"],
|
|
3602
|
+
metadata: options?.metadata,
|
|
3603
|
+
expiresAt: options?.expiresIn ? Date.now() + options.expiresIn : undefined
|
|
3604
|
+
};
|
|
3605
|
+
const token = await this.generateToken(payload);
|
|
3606
|
+
this.currentToken = token;
|
|
3607
|
+
return token;
|
|
3608
|
+
}
|
|
3609
|
+
getTokenCount() {
|
|
3610
|
+
return this.tokens.size;
|
|
3611
|
+
}
|
|
3612
|
+
getRevokedTokenCount() {
|
|
3613
|
+
return this.revokedTokens.size;
|
|
3614
|
+
}
|
|
3615
|
+
clearTokens() {
|
|
3616
|
+
this.tokens.clear();
|
|
3617
|
+
this.revokedTokens.clear();
|
|
3618
|
+
this.currentToken = null;
|
|
3619
|
+
}
|
|
3620
|
+
setFailAuth(fail) {
|
|
3621
|
+
this.shouldFailAuth = fail;
|
|
3622
|
+
}
|
|
3623
|
+
setCurrentToken(token) {
|
|
3624
|
+
this.currentToken = token;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
// src/adapters/mock/MockRTCDataChannel.ts
|
|
3628
|
+
class MockRTCDataChannel {
|
|
3629
|
+
label;
|
|
3630
|
+
ordered;
|
|
3631
|
+
id;
|
|
3632
|
+
_readyState = "connecting";
|
|
3633
|
+
_bufferedAmount = 0;
|
|
3634
|
+
_linkedChannel = null;
|
|
3635
|
+
messageQueue = [];
|
|
3636
|
+
config;
|
|
3637
|
+
static channelIdCounter = 0;
|
|
3638
|
+
onopen = null;
|
|
3639
|
+
onclose = null;
|
|
3640
|
+
onerror = null;
|
|
3641
|
+
onmessage = null;
|
|
3642
|
+
onbufferedamountlow = null;
|
|
3643
|
+
bufferedAmountLowThreshold = 0;
|
|
3644
|
+
constructor(label, options, config) {
|
|
3645
|
+
this.label = label;
|
|
3646
|
+
this.ordered = options?.ordered ?? true;
|
|
3647
|
+
this.id = options?.id ?? MockRTCDataChannel.channelIdCounter++;
|
|
3648
|
+
this.config = {
|
|
3649
|
+
simulateLatency: config?.simulateLatency ?? 1,
|
|
3650
|
+
maxMessageSize: config?.maxMessageSize ?? 262144
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
get readyState() {
|
|
3654
|
+
return this._readyState;
|
|
3655
|
+
}
|
|
3656
|
+
get bufferedAmount() {
|
|
3657
|
+
return this._bufferedAmount;
|
|
3658
|
+
}
|
|
3659
|
+
send(data) {
|
|
3660
|
+
if (this._readyState !== "open") {
|
|
3661
|
+
throw new Error(`Cannot send on channel in state: ${this._readyState}`);
|
|
3662
|
+
}
|
|
3663
|
+
const size = typeof data === "string" ? data.length : data.byteLength;
|
|
3664
|
+
if (size > this.config.maxMessageSize) {
|
|
3665
|
+
throw new Error(`Message size ${size} exceeds maximum ${this.config.maxMessageSize}`);
|
|
3666
|
+
}
|
|
3667
|
+
this._bufferedAmount += size;
|
|
3668
|
+
if (this._linkedChannel && this._linkedChannel._readyState === "open") {
|
|
3669
|
+
setTimeout(() => {
|
|
3670
|
+
this._bufferedAmount -= size;
|
|
3671
|
+
if (this._bufferedAmount <= this.bufferedAmountLowThreshold) {
|
|
3672
|
+
this.onbufferedamountlow?.();
|
|
3673
|
+
}
|
|
3674
|
+
this._linkedChannel?.onmessage?.({ data });
|
|
3675
|
+
}, this.config.simulateLatency);
|
|
3676
|
+
} else {
|
|
3677
|
+
this.messageQueue.push(data);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
close() {
|
|
3681
|
+
if (this._readyState === "closed" || this._readyState === "closing") {
|
|
3682
|
+
return;
|
|
3683
|
+
}
|
|
3684
|
+
this._readyState = "closing";
|
|
3685
|
+
setTimeout(() => {
|
|
3686
|
+
this._readyState = "closed";
|
|
3687
|
+
this.onclose?.();
|
|
3688
|
+
if (this._linkedChannel && this._linkedChannel._readyState !== "closed") {
|
|
3689
|
+
this._linkedChannel._handleRemoteClose();
|
|
3690
|
+
}
|
|
3691
|
+
}, this.config.simulateLatency);
|
|
3692
|
+
}
|
|
3693
|
+
_linkChannel(channel) {
|
|
3694
|
+
this._linkedChannel = channel;
|
|
3695
|
+
if (this._readyState === "open" && channel._readyState === "open") {
|
|
3696
|
+
this._flushQueue();
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
_open() {
|
|
3700
|
+
if (this._readyState === "connecting") {
|
|
3701
|
+
this._readyState = "open";
|
|
3702
|
+
this.onopen?.();
|
|
3703
|
+
if (this._linkedChannel?._readyState === "open") {
|
|
3704
|
+
this._flushQueue();
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
_handleRemoteClose() {
|
|
3709
|
+
if (this._readyState !== "closed" && this._readyState !== "closing") {
|
|
3710
|
+
this._readyState = "closed";
|
|
3711
|
+
this.onclose?.();
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
_getLinkedChannel() {
|
|
3715
|
+
return this._linkedChannel;
|
|
3716
|
+
}
|
|
3717
|
+
_flushQueue() {
|
|
3718
|
+
for (const data of this.messageQueue) {
|
|
3719
|
+
const size = typeof data === "string" ? data.length : data.byteLength;
|
|
3720
|
+
setTimeout(() => {
|
|
3721
|
+
this._bufferedAmount -= size;
|
|
3722
|
+
this._linkedChannel?.onmessage?.({ data });
|
|
3723
|
+
}, this.config.simulateLatency);
|
|
3724
|
+
}
|
|
3725
|
+
this.messageQueue = [];
|
|
3726
|
+
}
|
|
3727
|
+
static resetIdCounter() {
|
|
3728
|
+
MockRTCDataChannel.channelIdCounter = 0;
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
// src/adapters/mock/MockRTCPeerConnection.ts
|
|
3732
|
+
class MockRTCPeerConnection {
|
|
3733
|
+
_signalingState = "stable";
|
|
3734
|
+
_iceConnectionState = "new";
|
|
3735
|
+
_iceGatheringState = "new";
|
|
3736
|
+
_connectionState = "new";
|
|
3737
|
+
_localDescription = null;
|
|
3738
|
+
_remoteDescription = null;
|
|
3739
|
+
_pendingLocalDescription = null;
|
|
3740
|
+
_pendingRemoteDescription = null;
|
|
3741
|
+
_localCandidates = [];
|
|
3742
|
+
_remoteCandidates = [];
|
|
3743
|
+
_pendingRemoteCandidates = [];
|
|
3744
|
+
_dataChannels = new Map;
|
|
3745
|
+
onicecandidate = null;
|
|
3746
|
+
onicecandidateerror = null;
|
|
3747
|
+
oniceconnectionstatechange = null;
|
|
3748
|
+
onicegatheringstatechange = null;
|
|
3749
|
+
onconnectionstatechange = null;
|
|
3750
|
+
onsignalingstatechange = null;
|
|
3751
|
+
ondatachannel = null;
|
|
3752
|
+
onnegotiationneeded = null;
|
|
3753
|
+
_linkedPeer = null;
|
|
3754
|
+
config;
|
|
3755
|
+
offerCounter = 0;
|
|
3756
|
+
iceUfrag;
|
|
3757
|
+
icePwd;
|
|
3758
|
+
constructor(config) {
|
|
3759
|
+
this.config = {
|
|
3760
|
+
simulateLatency: config?.simulateLatency ?? 10,
|
|
3761
|
+
iceFailureProbability: config?.iceFailureProbability ?? 0,
|
|
3762
|
+
iceGatheringTime: config?.iceGatheringTime ?? 50,
|
|
3763
|
+
autoGenerateIceCandidates: config?.autoGenerateIceCandidates ?? true,
|
|
3764
|
+
iceCandidateCount: config?.iceCandidateCount ?? 3,
|
|
3765
|
+
dataChannelConfig: config?.dataChannelConfig
|
|
3766
|
+
};
|
|
3767
|
+
this.iceUfrag = "mock" + Math.random().toString(36).substring(2, 6);
|
|
3768
|
+
this.icePwd = "mock" + Math.random().toString(36).substring(2, 26);
|
|
3769
|
+
}
|
|
3770
|
+
get signalingState() {
|
|
3771
|
+
return this._signalingState;
|
|
3772
|
+
}
|
|
3773
|
+
get iceConnectionState() {
|
|
3774
|
+
return this._iceConnectionState;
|
|
3775
|
+
}
|
|
3776
|
+
get iceGatheringState() {
|
|
3777
|
+
return this._iceGatheringState;
|
|
3778
|
+
}
|
|
3779
|
+
get connectionState() {
|
|
3780
|
+
return this._connectionState;
|
|
3781
|
+
}
|
|
3782
|
+
get localDescription() {
|
|
3783
|
+
return this._localDescription;
|
|
3784
|
+
}
|
|
3785
|
+
get remoteDescription() {
|
|
3786
|
+
return this._remoteDescription;
|
|
3787
|
+
}
|
|
3788
|
+
get pendingLocalDescription() {
|
|
3789
|
+
return this._pendingLocalDescription;
|
|
3790
|
+
}
|
|
3791
|
+
get pendingRemoteDescription() {
|
|
3792
|
+
return this._pendingRemoteDescription;
|
|
3793
|
+
}
|
|
3794
|
+
get currentLocalDescription() {
|
|
3795
|
+
return this._localDescription;
|
|
3796
|
+
}
|
|
3797
|
+
get currentRemoteDescription() {
|
|
3798
|
+
return this._remoteDescription;
|
|
3799
|
+
}
|
|
3800
|
+
static createLinkedPair(configA, configB) {
|
|
3801
|
+
const peerA = new MockRTCPeerConnection(configA);
|
|
3802
|
+
const peerB = new MockRTCPeerConnection(configB);
|
|
3803
|
+
peerA._linkedPeer = peerB;
|
|
3804
|
+
peerB._linkedPeer = peerA;
|
|
3805
|
+
return [peerA, peerB];
|
|
3806
|
+
}
|
|
3807
|
+
async createOffer() {
|
|
3808
|
+
if (this._signalingState === "closed") {
|
|
3809
|
+
throw new Error("Cannot create offer on closed connection");
|
|
3810
|
+
}
|
|
3811
|
+
await this.simulateDelay();
|
|
3812
|
+
this.offerCounter++;
|
|
3813
|
+
const sdp = this.generateMockSDP("offer");
|
|
3814
|
+
return {
|
|
3815
|
+
type: "offer",
|
|
3816
|
+
sdp
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
async createAnswer() {
|
|
3820
|
+
if (this._signalingState !== "have-remote-offer") {
|
|
3821
|
+
throw new Error(`Cannot create answer in signaling state: ${this._signalingState}`);
|
|
3822
|
+
}
|
|
3823
|
+
await this.simulateDelay();
|
|
3824
|
+
const sdp = this.generateMockSDP("answer");
|
|
3825
|
+
return {
|
|
3826
|
+
type: "answer",
|
|
3827
|
+
sdp
|
|
3828
|
+
};
|
|
3829
|
+
}
|
|
3830
|
+
async setLocalDescription(description) {
|
|
3831
|
+
if (this._signalingState === "closed") {
|
|
3832
|
+
throw new Error("Cannot set local description on closed connection");
|
|
3833
|
+
}
|
|
3834
|
+
await this.simulateDelay();
|
|
3835
|
+
if (!description) {
|
|
3836
|
+
if (this._signalingState === "have-remote-offer") {
|
|
3837
|
+
description = await this.createAnswer();
|
|
3838
|
+
} else {
|
|
3839
|
+
description = await this.createOffer();
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
this._localDescription = description;
|
|
3843
|
+
this._pendingLocalDescription = null;
|
|
3844
|
+
if (description.type === "offer") {
|
|
3845
|
+
this._signalingState = "have-local-offer";
|
|
3846
|
+
} else if (description.type === "answer") {
|
|
3847
|
+
this._signalingState = "stable";
|
|
3848
|
+
this.startIceConnection();
|
|
3849
|
+
}
|
|
3850
|
+
this.onsignalingstatechange?.();
|
|
3851
|
+
if (this.config.autoGenerateIceCandidates) {
|
|
3852
|
+
this.startIceGathering();
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
async setRemoteDescription(description) {
|
|
3856
|
+
if (this._signalingState === "closed") {
|
|
3857
|
+
throw new Error("Cannot set remote description on closed connection");
|
|
3858
|
+
}
|
|
3859
|
+
await this.simulateDelay();
|
|
3860
|
+
this._remoteDescription = description;
|
|
3861
|
+
this._pendingRemoteDescription = null;
|
|
3862
|
+
if (description.type === "offer") {
|
|
3863
|
+
this._signalingState = "have-remote-offer";
|
|
3864
|
+
} else if (description.type === "answer") {
|
|
3865
|
+
this._signalingState = "stable";
|
|
3866
|
+
this.startIceConnection();
|
|
3867
|
+
}
|
|
3868
|
+
this.onsignalingstatechange?.();
|
|
3869
|
+
for (const candidate of this._pendingRemoteCandidates) {
|
|
3870
|
+
this._remoteCandidates.push(candidate);
|
|
3871
|
+
}
|
|
3872
|
+
this._pendingRemoteCandidates = [];
|
|
3873
|
+
if (description.type === "offer" && this._linkedPeer) {
|
|
3874
|
+
this.receiveDataChannelsFromPeer();
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
async addIceCandidate(candidate) {
|
|
3878
|
+
if (this._signalingState === "closed") {
|
|
3879
|
+
throw new Error("Cannot add ICE candidate on closed connection");
|
|
3880
|
+
}
|
|
3881
|
+
if (!candidate) {
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
if (!this._remoteDescription) {
|
|
3885
|
+
this._pendingRemoteCandidates.push(candidate);
|
|
3886
|
+
return;
|
|
3887
|
+
}
|
|
3888
|
+
await this.simulateDelay();
|
|
3889
|
+
this._remoteCandidates.push(candidate);
|
|
3890
|
+
if (this._iceConnectionState === "new") {
|
|
3891
|
+
this._iceConnectionState = "checking";
|
|
3892
|
+
this.oniceconnectionstatechange?.();
|
|
3893
|
+
}
|
|
3894
|
+
}
|
|
3895
|
+
createDataChannel(label, options) {
|
|
3896
|
+
if (this._signalingState === "closed") {
|
|
3897
|
+
throw new Error("Cannot create data channel on closed connection");
|
|
3898
|
+
}
|
|
3899
|
+
const channel = new MockRTCDataChannel(label, options, this.config.dataChannelConfig);
|
|
3900
|
+
this._dataChannels.set(label, channel);
|
|
3901
|
+
setTimeout(() => {
|
|
3902
|
+
this.onnegotiationneeded?.();
|
|
3903
|
+
}, 0);
|
|
3904
|
+
return channel;
|
|
3905
|
+
}
|
|
3906
|
+
close() {
|
|
3907
|
+
if (this._signalingState === "closed") {
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
this._signalingState = "closed";
|
|
3911
|
+
this._iceConnectionState = "closed";
|
|
3912
|
+
this._connectionState = "closed";
|
|
3913
|
+
for (const channel of this._dataChannels.values()) {
|
|
3914
|
+
channel.close();
|
|
3915
|
+
}
|
|
3916
|
+
this._dataChannels.clear();
|
|
3917
|
+
this.onsignalingstatechange?.();
|
|
3918
|
+
this.oniceconnectionstatechange?.();
|
|
3919
|
+
this.onconnectionstatechange?.();
|
|
3920
|
+
}
|
|
3921
|
+
async getStats() {
|
|
3922
|
+
return new Map([
|
|
3923
|
+
[
|
|
3924
|
+
"peer-connection",
|
|
3925
|
+
{
|
|
3926
|
+
type: "peer-connection",
|
|
3927
|
+
timestamp: Date.now(),
|
|
3928
|
+
dataChannelsOpened: this._dataChannels.size,
|
|
3929
|
+
localCandidates: this._localCandidates.length,
|
|
3930
|
+
remoteCandidates: this._remoteCandidates.length,
|
|
3931
|
+
signalingState: this._signalingState,
|
|
3932
|
+
iceConnectionState: this._iceConnectionState,
|
|
3933
|
+
connectionState: this._connectionState
|
|
3934
|
+
}
|
|
3935
|
+
]
|
|
3936
|
+
]);
|
|
3937
|
+
}
|
|
3938
|
+
restartIce() {
|
|
3939
|
+
this.iceUfrag = "mock" + Math.random().toString(36).substring(2, 6);
|
|
3940
|
+
this.icePwd = "mock" + Math.random().toString(36).substring(2, 26);
|
|
3941
|
+
this._localCandidates = [];
|
|
3942
|
+
this._remoteCandidates = [];
|
|
3943
|
+
this._iceGatheringState = "new";
|
|
3944
|
+
this._iceConnectionState = "new";
|
|
3945
|
+
this.onicegatheringstatechange?.();
|
|
3946
|
+
this.oniceconnectionstatechange?.();
|
|
3947
|
+
}
|
|
3948
|
+
getDataChannels() {
|
|
3949
|
+
return new Map(this._dataChannels);
|
|
3950
|
+
}
|
|
3951
|
+
getLocalCandidates() {
|
|
3952
|
+
return [...this._localCandidates];
|
|
3953
|
+
}
|
|
3954
|
+
getRemoteCandidates() {
|
|
3955
|
+
return [...this._remoteCandidates];
|
|
3956
|
+
}
|
|
3957
|
+
getLinkedPeer() {
|
|
3958
|
+
return this._linkedPeer;
|
|
3959
|
+
}
|
|
3960
|
+
_forceConnected() {
|
|
3961
|
+
this._iceConnectionState = "connected";
|
|
3962
|
+
this._connectionState = "connected";
|
|
3963
|
+
this.oniceconnectionstatechange?.();
|
|
3964
|
+
this.onconnectionstatechange?.();
|
|
3965
|
+
for (const channel of this._dataChannels.values()) {
|
|
3966
|
+
channel._open();
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
async simulateDelay() {
|
|
3970
|
+
if (this.config.simulateLatency > 0) {
|
|
3971
|
+
await new Promise((resolve) => setTimeout(resolve, this.config.simulateLatency));
|
|
3972
|
+
}
|
|
3973
|
+
}
|
|
3974
|
+
generateMockSDP(type) {
|
|
3975
|
+
const sessionId = Date.now();
|
|
3976
|
+
const version = this.offerCounter;
|
|
3977
|
+
const lines = [
|
|
3978
|
+
"v=0",
|
|
3979
|
+
`o=- ${sessionId} ${version} IN IP4 127.0.0.1`,
|
|
3980
|
+
"s=-",
|
|
3981
|
+
"t=0 0",
|
|
3982
|
+
"a=group:BUNDLE 0",
|
|
3983
|
+
"a=extmap-allow-mixed",
|
|
3984
|
+
"a=msid-semantic: WMS",
|
|
3985
|
+
"m=application 9 UDP/DTLS/SCTP webrtc-datachannel",
|
|
3986
|
+
"c=IN IP4 0.0.0.0",
|
|
3987
|
+
`a=ice-ufrag:${this.iceUfrag}`,
|
|
3988
|
+
`a=ice-pwd:${this.icePwd}`,
|
|
3989
|
+
"a=ice-options:trickle",
|
|
3990
|
+
"a=fingerprint:sha-256 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
|
|
3991
|
+
type === "offer" ? "a=setup:actpass" : "a=setup:active",
|
|
3992
|
+
"a=mid:0",
|
|
3993
|
+
"a=sctp-port:5000",
|
|
3994
|
+
"a=max-message-size:262144"
|
|
3995
|
+
];
|
|
3996
|
+
return lines.join(`\r
|
|
3997
|
+
`) + `\r
|
|
3998
|
+
`;
|
|
3999
|
+
}
|
|
4000
|
+
startIceGathering() {
|
|
4001
|
+
if (this._iceGatheringState !== "new") {
|
|
4002
|
+
return;
|
|
4003
|
+
}
|
|
4004
|
+
this._iceGatheringState = "gathering";
|
|
4005
|
+
this.onicegatheringstatechange?.();
|
|
4006
|
+
const candidateDelay = this.config.iceGatheringTime / this.config.iceCandidateCount;
|
|
4007
|
+
for (let i = 0;i < this.config.iceCandidateCount; i++) {
|
|
4008
|
+
setTimeout(() => {
|
|
4009
|
+
if (this._signalingState === "closed")
|
|
4010
|
+
return;
|
|
4011
|
+
if (Math.random() < this.config.iceFailureProbability) {
|
|
4012
|
+
this.onicecandidateerror?.({
|
|
4013
|
+
errorCode: 701,
|
|
4014
|
+
errorText: "Simulated ICE failure"
|
|
4015
|
+
});
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
const candidate = {
|
|
4019
|
+
candidate: `candidate:${i} 1 udp ${2000000000 - i * 100} 192.168.1.${100 + i} ${50000 + i} typ host generation 0 ufrag ${this.iceUfrag}`,
|
|
4020
|
+
sdpMid: "0",
|
|
4021
|
+
sdpMLineIndex: 0,
|
|
4022
|
+
usernameFragment: this.iceUfrag
|
|
4023
|
+
};
|
|
4024
|
+
this._localCandidates.push(candidate);
|
|
4025
|
+
this.onicecandidate?.({ candidate });
|
|
4026
|
+
if (i === this.config.iceCandidateCount - 1) {
|
|
4027
|
+
setTimeout(() => {
|
|
4028
|
+
if (this._signalingState === "closed")
|
|
4029
|
+
return;
|
|
4030
|
+
this._iceGatheringState = "complete";
|
|
4031
|
+
this.onicegatheringstatechange?.();
|
|
4032
|
+
this.onicecandidate?.({ candidate: null });
|
|
4033
|
+
}, this.config.simulateLatency);
|
|
4034
|
+
}
|
|
4035
|
+
}, candidateDelay * i);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
startIceConnection() {
|
|
4039
|
+
this._iceConnectionState = "checking";
|
|
4040
|
+
this._connectionState = "connecting";
|
|
4041
|
+
this.oniceconnectionstatechange?.();
|
|
4042
|
+
this.onconnectionstatechange?.();
|
|
4043
|
+
setTimeout(() => {
|
|
4044
|
+
if (this._signalingState === "closed")
|
|
4045
|
+
return;
|
|
4046
|
+
this._iceConnectionState = "connected";
|
|
4047
|
+
this._connectionState = "connected";
|
|
4048
|
+
this.oniceconnectionstatechange?.();
|
|
4049
|
+
this.onconnectionstatechange?.();
|
|
4050
|
+
this.openDataChannels();
|
|
4051
|
+
}, this.config.iceGatheringTime + this.config.simulateLatency);
|
|
4052
|
+
}
|
|
4053
|
+
openDataChannels() {
|
|
4054
|
+
for (const channel of this._dataChannels.values()) {
|
|
4055
|
+
channel._open();
|
|
4056
|
+
}
|
|
4057
|
+
if (this._linkedPeer) {
|
|
4058
|
+
for (const [label, localChannel] of this._dataChannels) {
|
|
4059
|
+
const remoteChannel = this._linkedPeer._dataChannels.get(label);
|
|
4060
|
+
if (remoteChannel) {
|
|
4061
|
+
localChannel._linkChannel(remoteChannel);
|
|
4062
|
+
remoteChannel._linkChannel(localChannel);
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
for (const channel of this._linkedPeer._dataChannels.values()) {
|
|
4066
|
+
if (channel.readyState === "connecting") {
|
|
4067
|
+
channel._open();
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
}
|
|
4072
|
+
receiveDataChannelsFromPeer() {
|
|
4073
|
+
if (!this._linkedPeer)
|
|
4074
|
+
return;
|
|
4075
|
+
for (const [label, peerChannel] of this._linkedPeer._dataChannels) {
|
|
4076
|
+
if (!this._dataChannels.has(label)) {
|
|
4077
|
+
const localChannel = new MockRTCDataChannel(label, { ordered: peerChannel.ordered, id: peerChannel.id }, this.config.dataChannelConfig);
|
|
4078
|
+
this._dataChannels.set(label, localChannel);
|
|
4079
|
+
localChannel._linkChannel(peerChannel);
|
|
4080
|
+
peerChannel._linkChannel(localChannel);
|
|
4081
|
+
this.ondatachannel?.({ channel: localChannel });
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
// src/adapters/mock/MockStorageAdapter.ts
|
|
4087
|
+
class MockStorageAdapter {
|
|
4088
|
+
storage = new Map;
|
|
4089
|
+
simulateLatency = 0;
|
|
4090
|
+
shouldFailOperations = false;
|
|
4091
|
+
constructor(options) {
|
|
4092
|
+
this.simulateLatency = options?.simulateLatency ?? 0;
|
|
4093
|
+
this.shouldFailOperations = options?.shouldFailOperations ?? false;
|
|
4094
|
+
}
|
|
4095
|
+
async get(key) {
|
|
4096
|
+
await this.simulateDelay();
|
|
4097
|
+
if (this.shouldFailOperations) {
|
|
4098
|
+
throw new Error("Mock storage operation failed");
|
|
4099
|
+
}
|
|
4100
|
+
const item = this.storage.get(key);
|
|
4101
|
+
if (!item)
|
|
4102
|
+
return null;
|
|
4103
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
4104
|
+
this.storage.delete(key);
|
|
4105
|
+
return null;
|
|
4106
|
+
}
|
|
4107
|
+
return item.value;
|
|
4108
|
+
}
|
|
4109
|
+
async set(key, value, ttl) {
|
|
4110
|
+
await this.simulateDelay();
|
|
4111
|
+
if (this.shouldFailOperations) {
|
|
4112
|
+
throw new Error("Mock storage operation failed");
|
|
4113
|
+
}
|
|
4114
|
+
const item = {
|
|
4115
|
+
value,
|
|
4116
|
+
expiresAt: ttl ? Date.now() + ttl : undefined
|
|
4117
|
+
};
|
|
4118
|
+
this.storage.set(key, item);
|
|
4119
|
+
}
|
|
4120
|
+
async delete(key) {
|
|
4121
|
+
await this.simulateDelay();
|
|
4122
|
+
if (this.shouldFailOperations) {
|
|
4123
|
+
throw new Error("Mock storage operation failed");
|
|
4124
|
+
}
|
|
4125
|
+
this.storage.delete(key);
|
|
4126
|
+
}
|
|
4127
|
+
async exists(key) {
|
|
4128
|
+
await this.simulateDelay();
|
|
4129
|
+
if (this.shouldFailOperations) {
|
|
4130
|
+
throw new Error("Mock storage operation failed");
|
|
4131
|
+
}
|
|
4132
|
+
const item = this.storage.get(key);
|
|
4133
|
+
if (!item)
|
|
4134
|
+
return false;
|
|
4135
|
+
if (item.expiresAt && item.expiresAt < Date.now()) {
|
|
3758
4136
|
this.storage.delete(key);
|
|
3759
4137
|
return false;
|
|
3760
4138
|
}
|
|
@@ -3855,117 +4233,645 @@ class MockStorageAdapter {
|
|
|
3855
4233
|
this.shouldFailOperations = fail;
|
|
3856
4234
|
}
|
|
3857
4235
|
}
|
|
3858
|
-
// src/adapters/mock/
|
|
3859
|
-
class
|
|
3860
|
-
|
|
3861
|
-
|
|
4236
|
+
// src/adapters/mock/MockTransportAdapter.ts
|
|
4237
|
+
class MockTransportAdapter {
|
|
4238
|
+
state = "disconnected";
|
|
4239
|
+
messageHandlers = new Set;
|
|
4240
|
+
errorHandlers = new Set;
|
|
4241
|
+
closeHandlers = new Set;
|
|
4242
|
+
messageQueue = [];
|
|
3862
4243
|
simulateLatency = 0;
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
4244
|
+
shouldFailConnection = false;
|
|
4245
|
+
connectedUrl = null;
|
|
4246
|
+
connectionAttempts = 0;
|
|
3866
4247
|
constructor(options) {
|
|
3867
4248
|
this.simulateLatency = options?.simulateLatency ?? 0;
|
|
3868
|
-
this.
|
|
4249
|
+
this.shouldFailConnection = options?.shouldFailConnection ?? false;
|
|
3869
4250
|
}
|
|
3870
|
-
async
|
|
3871
|
-
|
|
3872
|
-
if (this.
|
|
3873
|
-
|
|
4251
|
+
async connect(url, _options) {
|
|
4252
|
+
this.connectionAttempts++;
|
|
4253
|
+
if (this.shouldFailConnection) {
|
|
4254
|
+
this.state = "disconnected";
|
|
4255
|
+
const error = new Error("Mock connection failed");
|
|
4256
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
4257
|
+
throw error;
|
|
3874
4258
|
}
|
|
3875
|
-
|
|
3876
|
-
|
|
4259
|
+
this.state = "connecting";
|
|
4260
|
+
this.connectedUrl = url;
|
|
4261
|
+
if (this.simulateLatency > 0) {
|
|
4262
|
+
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
3877
4263
|
}
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
4264
|
+
this.state = "connected";
|
|
4265
|
+
}
|
|
4266
|
+
async disconnect() {
|
|
4267
|
+
if (this.state === "disconnected")
|
|
4268
|
+
return;
|
|
4269
|
+
this.state = "disconnecting";
|
|
4270
|
+
if (this.simulateLatency > 0) {
|
|
4271
|
+
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency / 2));
|
|
3881
4272
|
}
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
4273
|
+
this.state = "disconnected";
|
|
4274
|
+
this.connectedUrl = null;
|
|
4275
|
+
this.closeHandlers.forEach((handler) => handler(1000, "Normal closure"));
|
|
4276
|
+
this.messageQueue = [];
|
|
4277
|
+
}
|
|
4278
|
+
async send(message) {
|
|
4279
|
+
if (this.state !== "connected") {
|
|
4280
|
+
throw new Error("Not connected");
|
|
3885
4281
|
}
|
|
3886
|
-
|
|
4282
|
+
if (this.simulateLatency > 0) {
|
|
4283
|
+
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
4284
|
+
}
|
|
4285
|
+
this.messageQueue.push(message);
|
|
3887
4286
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
4287
|
+
onMessage(handler) {
|
|
4288
|
+
this.messageHandlers.add(handler);
|
|
4289
|
+
}
|
|
4290
|
+
onError(handler) {
|
|
4291
|
+
this.errorHandlers.add(handler);
|
|
4292
|
+
}
|
|
4293
|
+
onClose(handler) {
|
|
4294
|
+
this.closeHandlers.add(handler);
|
|
4295
|
+
}
|
|
4296
|
+
getState() {
|
|
4297
|
+
return this.state;
|
|
4298
|
+
}
|
|
4299
|
+
isConnected() {
|
|
4300
|
+
return this.state === "connected";
|
|
4301
|
+
}
|
|
4302
|
+
simulateMessage(message, bypassConnectionCheck = false) {
|
|
4303
|
+
if (!bypassConnectionCheck && this.state !== "connected") {
|
|
4304
|
+
throw new Error("Cannot simulate message when not connected");
|
|
3892
4305
|
}
|
|
3893
|
-
|
|
3894
|
-
const expiresAt = payload.expiresAt ?? Date.now() + 3600000;
|
|
3895
|
-
this.tokens.set(token, { ...payload, expiresAt });
|
|
3896
|
-
return token;
|
|
4306
|
+
this.messageHandlers.forEach((handler) => handler(message));
|
|
3897
4307
|
}
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4308
|
+
simulateError(error) {
|
|
4309
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
4310
|
+
}
|
|
4311
|
+
simulateClose(code, reason) {
|
|
4312
|
+
this.state = "disconnected";
|
|
4313
|
+
this.closeHandlers.forEach((handler) => handler(code, reason));
|
|
4314
|
+
}
|
|
4315
|
+
getMessageQueue() {
|
|
4316
|
+
return [...this.messageQueue];
|
|
4317
|
+
}
|
|
4318
|
+
clearMessageQueue() {
|
|
4319
|
+
this.messageQueue = [];
|
|
4320
|
+
}
|
|
4321
|
+
getConnectedUrl() {
|
|
4322
|
+
return this.connectedUrl;
|
|
4323
|
+
}
|
|
4324
|
+
getConnectionAttempts() {
|
|
4325
|
+
return this.connectionAttempts;
|
|
4326
|
+
}
|
|
4327
|
+
resetConnectionAttempts() {
|
|
4328
|
+
this.connectionAttempts = 0;
|
|
4329
|
+
}
|
|
4330
|
+
async simulateConnection(clientId, options = {}) {
|
|
4331
|
+
if (this.state !== "connected") {
|
|
4332
|
+
this.state = "connected";
|
|
4333
|
+
}
|
|
4334
|
+
const message = {
|
|
4335
|
+
id: `conn-${clientId}`,
|
|
4336
|
+
type: "connection",
|
|
4337
|
+
payload: {
|
|
4338
|
+
clientId,
|
|
4339
|
+
authenticated: options.authenticated ?? false,
|
|
4340
|
+
userId: options.userId,
|
|
4341
|
+
metadata: options.metadata
|
|
4342
|
+
},
|
|
4343
|
+
timestamp: Date.now()
|
|
4344
|
+
};
|
|
4345
|
+
this.simulateMessage(message, true);
|
|
4346
|
+
}
|
|
4347
|
+
async simulateDisconnect(clientId, reason = "Normal closure") {
|
|
4348
|
+
const message = {
|
|
4349
|
+
id: `disconnect-${clientId}`,
|
|
4350
|
+
type: "disconnect",
|
|
4351
|
+
payload: {
|
|
4352
|
+
clientId,
|
|
4353
|
+
reason
|
|
4354
|
+
},
|
|
4355
|
+
timestamp: Date.now()
|
|
4356
|
+
};
|
|
4357
|
+
this.simulateMessage(message, true);
|
|
4358
|
+
}
|
|
4359
|
+
async simulateClientMessage(clientId, messageType, messagePayload = {}) {
|
|
4360
|
+
const message = {
|
|
4361
|
+
id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
4362
|
+
type: "server_message",
|
|
4363
|
+
payload: {
|
|
4364
|
+
clientId,
|
|
4365
|
+
type: messageType,
|
|
4366
|
+
...messagePayload
|
|
4367
|
+
},
|
|
4368
|
+
timestamp: Date.now()
|
|
4369
|
+
};
|
|
4370
|
+
this.simulateMessage(message, true);
|
|
4371
|
+
}
|
|
4372
|
+
getSentMessages(clientId) {
|
|
4373
|
+
return this.messageQueue.filter((msg) => {
|
|
4374
|
+
const payload = msg.payload;
|
|
4375
|
+
return payload?.clientId === clientId;
|
|
4376
|
+
}).map((msg) => {
|
|
4377
|
+
const { clientId: _, ...rest } = msg.payload;
|
|
4378
|
+
return rest;
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
simulateIncomingMessage(message) {
|
|
4382
|
+
const fullMessage = {
|
|
4383
|
+
id: message.id || `msg-${Date.now()}`,
|
|
4384
|
+
type: message.type,
|
|
4385
|
+
payload: message.payload || {},
|
|
4386
|
+
timestamp: message.timestamp || Date.now()
|
|
4387
|
+
};
|
|
4388
|
+
this.messageHandlers.forEach((handler) => handler(fullMessage));
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
// src/adapters/webrtc/WebRTCSignalingAdapter.ts
|
|
4392
|
+
class WebRTCSignalingAdapter {
|
|
4393
|
+
client;
|
|
4394
|
+
config;
|
|
4395
|
+
peers = new Map;
|
|
4396
|
+
eventUnsubscribers = [];
|
|
4397
|
+
onPeerConnected = null;
|
|
4398
|
+
onPeerDisconnected = null;
|
|
4399
|
+
onPeerMessage = null;
|
|
4400
|
+
onError = null;
|
|
4401
|
+
onStateChange = null;
|
|
4402
|
+
constructor(client, config) {
|
|
4403
|
+
this.client = client;
|
|
4404
|
+
this.config = {
|
|
4405
|
+
useMock: config?.useMock ?? true,
|
|
4406
|
+
iceServers: config?.iceServers,
|
|
4407
|
+
mockConfig: config?.mockConfig,
|
|
4408
|
+
dataChannelLabel: config?.dataChannelLabel ?? "data",
|
|
4409
|
+
orderedDelivery: config?.orderedDelivery ?? true
|
|
4410
|
+
};
|
|
4411
|
+
this.setupSignalingListeners();
|
|
4412
|
+
}
|
|
4413
|
+
async connectToPeer(peerId) {
|
|
4414
|
+
if (this.peers.has(peerId)) {
|
|
4415
|
+
throw new Error(`Already connected or connecting to peer: ${peerId}`);
|
|
4416
|
+
}
|
|
4417
|
+
const connection = this.createPeerConnection();
|
|
4418
|
+
const peerState = {
|
|
4419
|
+
peerId,
|
|
4420
|
+
connection,
|
|
4421
|
+
dataChannel: null,
|
|
4422
|
+
state: "new",
|
|
4423
|
+
createdAt: Date.now()
|
|
4424
|
+
};
|
|
4425
|
+
this.peers.set(peerId, peerState);
|
|
4426
|
+
this.setupConnectionHandlers(peerState);
|
|
4427
|
+
const dataChannel = connection.createDataChannel(this.config.dataChannelLabel, {
|
|
4428
|
+
ordered: this.config.orderedDelivery
|
|
4429
|
+
});
|
|
4430
|
+
peerState.dataChannel = dataChannel;
|
|
4431
|
+
this.setupDataChannelHandlers(peerState, dataChannel);
|
|
4432
|
+
this.updatePeerState(peerState, "connecting");
|
|
4433
|
+
const offer = await connection.createOffer();
|
|
4434
|
+
await connection.setLocalDescription(offer);
|
|
4435
|
+
await this.sendSignal(peerId, "webrtc:offer", {
|
|
4436
|
+
sdp: offer.sdp,
|
|
4437
|
+
type: offer.type
|
|
4438
|
+
});
|
|
4439
|
+
}
|
|
4440
|
+
disconnectFromPeer(peerId) {
|
|
4441
|
+
const peer = this.peers.get(peerId);
|
|
4442
|
+
if (!peer)
|
|
4443
|
+
return;
|
|
4444
|
+
this.sendSignal(peerId, "webrtc:disconnect", {}).catch(() => {});
|
|
4445
|
+
peer.dataChannel?.close();
|
|
4446
|
+
peer.connection.close();
|
|
4447
|
+
this.updatePeerState(peer, "disconnected");
|
|
4448
|
+
this.peers.delete(peerId);
|
|
4449
|
+
this.onPeerDisconnected?.(peerId);
|
|
4450
|
+
}
|
|
4451
|
+
sendToPeer(peerId, data) {
|
|
4452
|
+
const peer = this.peers.get(peerId);
|
|
4453
|
+
if (!peer || !peer.dataChannel) {
|
|
4454
|
+
throw new Error(`No data channel to peer: ${peerId}`);
|
|
4455
|
+
}
|
|
4456
|
+
if (peer.dataChannel.readyState !== "open") {
|
|
4457
|
+
throw new Error(`Data channel not open for peer: ${peerId} (state: ${peer.dataChannel.readyState})`);
|
|
4458
|
+
}
|
|
4459
|
+
peer.dataChannel.send(data);
|
|
4460
|
+
}
|
|
4461
|
+
broadcastToPeers(data) {
|
|
4462
|
+
for (const peerId of this.getConnectedPeers()) {
|
|
4463
|
+
try {
|
|
4464
|
+
this.sendToPeer(peerId, data);
|
|
4465
|
+
} catch (error) {
|
|
4466
|
+
console.warn(`Failed to send to peer ${peerId}:`, error);
|
|
4467
|
+
}
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
getConnectedPeers() {
|
|
4471
|
+
return Array.from(this.peers.entries()).filter(([_, peer]) => peer.state === "connected").map(([peerId]) => peerId);
|
|
4472
|
+
}
|
|
4473
|
+
getAllPeers() {
|
|
4474
|
+
return Array.from(this.peers.keys());
|
|
4475
|
+
}
|
|
4476
|
+
getPeerState(peerId) {
|
|
4477
|
+
return this.peers.get(peerId)?.state ?? null;
|
|
4478
|
+
}
|
|
4479
|
+
getPeerInfo(peerId) {
|
|
4480
|
+
return this.peers.get(peerId) ?? null;
|
|
4481
|
+
}
|
|
4482
|
+
isConnectedToPeer(peerId) {
|
|
4483
|
+
return this.peers.get(peerId)?.state === "connected";
|
|
4484
|
+
}
|
|
4485
|
+
dispose() {
|
|
4486
|
+
for (const peerId of this.getAllPeers()) {
|
|
4487
|
+
this.disconnectFromPeer(peerId);
|
|
4488
|
+
}
|
|
4489
|
+
for (const unsubscribe of this.eventUnsubscribers) {
|
|
4490
|
+
unsubscribe();
|
|
4491
|
+
}
|
|
4492
|
+
this.eventUnsubscribers = [];
|
|
4493
|
+
}
|
|
4494
|
+
createPeerConnection() {
|
|
4495
|
+
if (this.config.useMock) {
|
|
4496
|
+
return new MockRTCPeerConnection(this.config.mockConfig);
|
|
4497
|
+
} else {
|
|
4498
|
+
throw new Error("Real WebRTC not yet implemented. Use useMock: true");
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
setupSignalingListeners() {
|
|
4502
|
+
const unsubscribe = this.client.on("event_received", ({ event }) => {
|
|
4503
|
+
this.handleSignalingMessage(event).catch((error) => {
|
|
4504
|
+
console.error("Error handling signaling message:", error);
|
|
4505
|
+
});
|
|
4506
|
+
});
|
|
4507
|
+
this.eventUnsubscribers.push(unsubscribe);
|
|
4508
|
+
}
|
|
4509
|
+
async handleSignalingMessage(event) {
|
|
4510
|
+
const eventAny = event;
|
|
4511
|
+
const type = eventAny.type;
|
|
4512
|
+
const fromUserId = eventAny.metadata?.userId;
|
|
4513
|
+
const myUserId = this.client.getUserId();
|
|
4514
|
+
if (!fromUserId || fromUserId === myUserId) {
|
|
4515
|
+
return;
|
|
4516
|
+
}
|
|
4517
|
+
const data = eventAny.data;
|
|
4518
|
+
const targetPeerId = data?.targetPeerId;
|
|
4519
|
+
if (targetPeerId && targetPeerId !== myUserId) {
|
|
4520
|
+
return;
|
|
4521
|
+
}
|
|
4522
|
+
switch (type) {
|
|
4523
|
+
case "webrtc:offer":
|
|
4524
|
+
await this.handleOffer(fromUserId, data ?? {});
|
|
4525
|
+
break;
|
|
4526
|
+
case "webrtc:answer":
|
|
4527
|
+
await this.handleAnswer(fromUserId, data ?? {});
|
|
4528
|
+
break;
|
|
4529
|
+
case "webrtc:ice_candidate":
|
|
4530
|
+
await this.handleIceCandidate(fromUserId, data ?? {});
|
|
4531
|
+
break;
|
|
4532
|
+
case "webrtc:disconnect":
|
|
4533
|
+
this.handlePeerDisconnect(fromUserId);
|
|
4534
|
+
break;
|
|
4535
|
+
}
|
|
4536
|
+
}
|
|
4537
|
+
async handleOffer(peerId, data) {
|
|
4538
|
+
if (this.peers.has(peerId)) {
|
|
4539
|
+
console.warn(`Received offer from peer ${peerId} but already have a connection`);
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4542
|
+
const connection = this.createPeerConnection();
|
|
4543
|
+
const peerState = {
|
|
4544
|
+
peerId,
|
|
4545
|
+
connection,
|
|
4546
|
+
dataChannel: null,
|
|
4547
|
+
state: "connecting",
|
|
4548
|
+
createdAt: Date.now()
|
|
4549
|
+
};
|
|
4550
|
+
this.peers.set(peerId, peerState);
|
|
4551
|
+
this.setupConnectionHandlers(peerState);
|
|
4552
|
+
connection.ondatachannel = (event) => {
|
|
4553
|
+
peerState.dataChannel = event.channel;
|
|
4554
|
+
this.setupDataChannelHandlers(peerState, event.channel);
|
|
4555
|
+
};
|
|
4556
|
+
await connection.setRemoteDescription({
|
|
4557
|
+
type: "offer",
|
|
4558
|
+
sdp: data.sdp
|
|
4559
|
+
});
|
|
4560
|
+
const answer = await connection.createAnswer();
|
|
4561
|
+
await connection.setLocalDescription(answer);
|
|
4562
|
+
await this.sendSignal(peerId, "webrtc:answer", {
|
|
4563
|
+
sdp: answer.sdp,
|
|
4564
|
+
type: answer.type
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4567
|
+
async handleAnswer(peerId, data) {
|
|
4568
|
+
const peer = this.peers.get(peerId);
|
|
4569
|
+
if (!peer) {
|
|
4570
|
+
console.warn(`Received answer from unknown peer: ${peerId}`);
|
|
4571
|
+
return;
|
|
4572
|
+
}
|
|
4573
|
+
await peer.connection.setRemoteDescription({
|
|
4574
|
+
type: "answer",
|
|
4575
|
+
sdp: data.sdp
|
|
4576
|
+
});
|
|
4577
|
+
}
|
|
4578
|
+
async handleIceCandidate(peerId, data) {
|
|
4579
|
+
const peer = this.peers.get(peerId);
|
|
4580
|
+
if (!peer) {
|
|
4581
|
+
console.warn(`Received ICE candidate from unknown peer: ${peerId}`);
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4584
|
+
const candidate = data.candidate;
|
|
4585
|
+
if (candidate) {
|
|
4586
|
+
await peer.connection.addIceCandidate(candidate);
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
handlePeerDisconnect(peerId) {
|
|
4590
|
+
const peer = this.peers.get(peerId);
|
|
4591
|
+
if (!peer)
|
|
4592
|
+
return;
|
|
4593
|
+
peer.dataChannel?.close();
|
|
4594
|
+
peer.connection.close();
|
|
4595
|
+
this.updatePeerState(peer, "disconnected");
|
|
4596
|
+
this.peers.delete(peerId);
|
|
4597
|
+
this.onPeerDisconnected?.(peerId);
|
|
4598
|
+
}
|
|
4599
|
+
setupConnectionHandlers(peer) {
|
|
4600
|
+
const connection = peer.connection;
|
|
4601
|
+
connection.onicecandidate = (event) => {
|
|
4602
|
+
if (event.candidate) {
|
|
4603
|
+
this.sendSignal(peer.peerId, "webrtc:ice_candidate", {
|
|
4604
|
+
candidate: event.candidate
|
|
4605
|
+
}).catch((error) => {
|
|
4606
|
+
console.error("Failed to send ICE candidate:", error);
|
|
4607
|
+
});
|
|
4608
|
+
}
|
|
4609
|
+
};
|
|
4610
|
+
connection.onconnectionstatechange = () => {
|
|
4611
|
+
const state = connection.connectionState;
|
|
4612
|
+
if (state === "connected") {
|
|
4613
|
+
peer.connectedAt = Date.now();
|
|
4614
|
+
this.updatePeerState(peer, "connected");
|
|
4615
|
+
} else if (state === "disconnected" || state === "closed") {
|
|
4616
|
+
this.updatePeerState(peer, "disconnected");
|
|
4617
|
+
this.peers.delete(peer.peerId);
|
|
4618
|
+
this.onPeerDisconnected?.(peer.peerId);
|
|
4619
|
+
} else if (state === "failed") {
|
|
4620
|
+
this.updatePeerState(peer, "failed");
|
|
4621
|
+
this.onError?.(peer.peerId, new Error("Connection failed"));
|
|
4622
|
+
}
|
|
4623
|
+
};
|
|
4624
|
+
connection.oniceconnectionstatechange = () => {
|
|
4625
|
+
if (connection.iceConnectionState === "failed") {
|
|
4626
|
+
this.updatePeerState(peer, "failed");
|
|
4627
|
+
this.onError?.(peer.peerId, new Error("ICE connection failed"));
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
}
|
|
4631
|
+
setupDataChannelHandlers(peer, channel) {
|
|
4632
|
+
channel.onopen = () => {
|
|
4633
|
+
this.updatePeerState(peer, "connected");
|
|
4634
|
+
peer.connectedAt = Date.now();
|
|
4635
|
+
this.onPeerConnected?.(peer.peerId, channel);
|
|
4636
|
+
};
|
|
4637
|
+
channel.onclose = () => {
|
|
4638
|
+
if (peer.state === "connected") {
|
|
4639
|
+
this.updatePeerState(peer, "disconnected");
|
|
4640
|
+
this.onPeerDisconnected?.(peer.peerId);
|
|
4641
|
+
}
|
|
4642
|
+
};
|
|
4643
|
+
channel.onerror = (event) => {
|
|
4644
|
+
this.onError?.(peer.peerId, event.error);
|
|
4645
|
+
};
|
|
4646
|
+
channel.onmessage = (event) => {
|
|
4647
|
+
this.onPeerMessage?.(peer.peerId, event.data);
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
updatePeerState(peer, state) {
|
|
4651
|
+
const oldState = peer.state;
|
|
4652
|
+
peer.state = state;
|
|
4653
|
+
if (oldState !== state) {
|
|
4654
|
+
this.onStateChange?.(peer.peerId, state);
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
async sendSignal(peerId, type, data) {
|
|
4658
|
+
const myUserId = this.client.getUserId();
|
|
4659
|
+
const roomId = this.client.getCurrentRoomId();
|
|
4660
|
+
if (!roomId) {
|
|
4661
|
+
throw new Error("Cannot send WebRTC signal: not in a room");
|
|
4662
|
+
}
|
|
4663
|
+
const signalEvent = {
|
|
4664
|
+
id: `webrtc-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
4665
|
+
type,
|
|
4666
|
+
timestamp: Date.now(),
|
|
4667
|
+
userId: myUserId || "",
|
|
4668
|
+
roomId,
|
|
4669
|
+
data: {
|
|
4670
|
+
...data,
|
|
4671
|
+
targetPeerId: peerId
|
|
4672
|
+
},
|
|
4673
|
+
metadata: {
|
|
4674
|
+
userId: myUserId,
|
|
4675
|
+
timestamp: Date.now(),
|
|
4676
|
+
roomId
|
|
4677
|
+
}
|
|
4678
|
+
};
|
|
4679
|
+
await this.client.broadcast(signalEvent);
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
// node_modules/ws/wrapper.mjs
|
|
4683
|
+
var import_stream = __toESM(require_stream(), 1);
|
|
4684
|
+
var import_receiver = __toESM(require_receiver(), 1);
|
|
4685
|
+
var import_sender = __toESM(require_sender(), 1);
|
|
4686
|
+
var import_websocket = __toESM(require_websocket(), 1);
|
|
4687
|
+
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
4688
|
+
|
|
4689
|
+
// src/adapters/websocket/WebSocketClientTransportAdapter.ts
|
|
4690
|
+
class WebSocketClientTransportAdapter {
|
|
4691
|
+
ws;
|
|
4692
|
+
state = "disconnected";
|
|
4693
|
+
messageHandlers = new Set;
|
|
4694
|
+
errorHandlers = new Set;
|
|
4695
|
+
closeHandlers = new Set;
|
|
4696
|
+
config;
|
|
4697
|
+
heartbeatTimer;
|
|
4698
|
+
connectionTimer;
|
|
4699
|
+
constructor(config) {
|
|
4700
|
+
this.config = {
|
|
4701
|
+
headers: config?.headers ?? {},
|
|
4702
|
+
connectionTimeout: config?.connectionTimeout ?? 30000,
|
|
4703
|
+
enableHeartbeat: config?.enableHeartbeat ?? true,
|
|
4704
|
+
heartbeatInterval: config?.heartbeatInterval ?? 30000
|
|
4705
|
+
};
|
|
4706
|
+
}
|
|
4707
|
+
async connect(url, options) {
|
|
4708
|
+
if (this.state === "connected" || this.state === "connecting") {
|
|
4709
|
+
throw new Error("Already connected or connecting");
|
|
4710
|
+
}
|
|
4711
|
+
this.state = "connecting";
|
|
4712
|
+
return new Promise((resolve, reject) => {
|
|
4713
|
+
try {
|
|
4714
|
+
const wsOptions = {};
|
|
4715
|
+
if (Object.keys(this.config.headers).length > 0) {
|
|
4716
|
+
wsOptions.headers = this.config.headers;
|
|
4717
|
+
}
|
|
4718
|
+
this.ws = new import_websocket.default(url, wsOptions);
|
|
4719
|
+
this.connectionTimer = setTimeout(() => {
|
|
4720
|
+
if (this.state === "connecting") {
|
|
4721
|
+
this.ws?.terminate();
|
|
4722
|
+
this.state = "disconnected";
|
|
4723
|
+
const error = new Error("Connection timeout");
|
|
4724
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
4725
|
+
reject(error);
|
|
4726
|
+
}
|
|
4727
|
+
}, this.config.connectionTimeout);
|
|
4728
|
+
this.ws.on("open", () => {
|
|
4729
|
+
if (this.connectionTimer) {
|
|
4730
|
+
clearTimeout(this.connectionTimer);
|
|
4731
|
+
this.connectionTimer = undefined;
|
|
4732
|
+
}
|
|
4733
|
+
this.state = "connected";
|
|
4734
|
+
if (this.config.enableHeartbeat) {
|
|
4735
|
+
this.startHeartbeat();
|
|
4736
|
+
}
|
|
4737
|
+
resolve();
|
|
4738
|
+
});
|
|
4739
|
+
this.ws.on("message", (data) => {
|
|
4740
|
+
try {
|
|
4741
|
+
const message = JSON.parse(data.toString());
|
|
4742
|
+
this.messageHandlers.forEach((handler) => handler(message));
|
|
4743
|
+
} catch (error) {
|
|
4744
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
4745
|
+
}
|
|
4746
|
+
});
|
|
4747
|
+
this.ws.on("error", (error) => {
|
|
4748
|
+
if (this.connectionTimer) {
|
|
4749
|
+
clearTimeout(this.connectionTimer);
|
|
4750
|
+
this.connectionTimer = undefined;
|
|
4751
|
+
}
|
|
4752
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
4753
|
+
if (this.state === "connecting") {
|
|
4754
|
+
this.state = "disconnected";
|
|
4755
|
+
reject(error);
|
|
4756
|
+
}
|
|
4757
|
+
});
|
|
4758
|
+
this.ws.on("close", (code, reason) => {
|
|
4759
|
+
if (this.connectionTimer) {
|
|
4760
|
+
clearTimeout(this.connectionTimer);
|
|
4761
|
+
this.connectionTimer = undefined;
|
|
4762
|
+
}
|
|
4763
|
+
this.stopHeartbeat();
|
|
4764
|
+
const wasConnected = this.state === "connected";
|
|
4765
|
+
this.state = "disconnected";
|
|
4766
|
+
if (wasConnected) {
|
|
4767
|
+
this.closeHandlers.forEach((handler) => handler(code, reason.toString()));
|
|
4768
|
+
}
|
|
4769
|
+
});
|
|
4770
|
+
if (this.config.enableHeartbeat) {
|
|
4771
|
+
this.ws.on("pong", () => {});
|
|
4772
|
+
}
|
|
4773
|
+
} catch (error) {
|
|
4774
|
+
if (this.connectionTimer) {
|
|
4775
|
+
clearTimeout(this.connectionTimer);
|
|
4776
|
+
this.connectionTimer = undefined;
|
|
4777
|
+
}
|
|
4778
|
+
this.state = "disconnected";
|
|
4779
|
+
reject(error);
|
|
4780
|
+
}
|
|
4781
|
+
});
|
|
4782
|
+
}
|
|
4783
|
+
async disconnect() {
|
|
4784
|
+
if (this.state === "disconnected") {
|
|
4785
|
+
return;
|
|
4786
|
+
}
|
|
4787
|
+
this.state = "disconnecting";
|
|
4788
|
+
this.stopHeartbeat();
|
|
4789
|
+
if (this.connectionTimer) {
|
|
4790
|
+
clearTimeout(this.connectionTimer);
|
|
4791
|
+
this.connectionTimer = undefined;
|
|
4792
|
+
}
|
|
4793
|
+
if (this.ws) {
|
|
4794
|
+
this.ws.close(1000, "Client disconnecting");
|
|
4795
|
+
await new Promise((resolve) => {
|
|
4796
|
+
const timeout = setTimeout(() => {
|
|
4797
|
+
this.ws?.terminate();
|
|
4798
|
+
resolve();
|
|
4799
|
+
}, 5000);
|
|
4800
|
+
const onClose = () => {
|
|
4801
|
+
clearTimeout(timeout);
|
|
4802
|
+
resolve();
|
|
4803
|
+
};
|
|
4804
|
+
this.ws?.once("close", onClose);
|
|
4805
|
+
});
|
|
4806
|
+
this.ws = undefined;
|
|
3902
4807
|
}
|
|
3903
|
-
|
|
3904
|
-
await this.revokeToken(token);
|
|
3905
|
-
const newPayload = {
|
|
3906
|
-
...payload,
|
|
3907
|
-
expiresAt: Date.now() + 3600000
|
|
3908
|
-
};
|
|
3909
|
-
return this.generateToken(newPayload);
|
|
4808
|
+
this.state = "disconnected";
|
|
3910
4809
|
}
|
|
3911
|
-
async
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
4810
|
+
async send(message) {
|
|
4811
|
+
if (this.state !== "connected") {
|
|
4812
|
+
throw new Error("Not connected");
|
|
4813
|
+
}
|
|
4814
|
+
if (!this.ws || this.ws.readyState !== import_websocket.default.OPEN) {
|
|
4815
|
+
throw new Error("WebSocket connection is not open");
|
|
4816
|
+
}
|
|
4817
|
+
try {
|
|
4818
|
+
const data = JSON.stringify(message);
|
|
4819
|
+
this.ws.send(data);
|
|
4820
|
+
} catch (error) {
|
|
4821
|
+
this.errorHandlers.forEach((handler) => handler(error));
|
|
4822
|
+
throw error;
|
|
3915
4823
|
}
|
|
3916
|
-
this.tokens.delete(token);
|
|
3917
|
-
this.revokedTokens.add(token);
|
|
3918
4824
|
}
|
|
3919
|
-
|
|
3920
|
-
|
|
4825
|
+
onMessage(handler) {
|
|
4826
|
+
this.messageHandlers.add(handler);
|
|
3921
4827
|
}
|
|
3922
|
-
|
|
3923
|
-
|
|
4828
|
+
onError(handler) {
|
|
4829
|
+
this.errorHandlers.add(handler);
|
|
3924
4830
|
}
|
|
3925
|
-
|
|
3926
|
-
|
|
4831
|
+
onClose(handler) {
|
|
4832
|
+
this.closeHandlers.add(handler);
|
|
3927
4833
|
}
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
await new Promise((resolve) => setTimeout(resolve, this.simulateLatency));
|
|
3931
|
-
}
|
|
4834
|
+
getState() {
|
|
4835
|
+
return this.state;
|
|
3932
4836
|
}
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
userId,
|
|
3936
|
-
permissions: options?.permissions ?? ["read", "write"],
|
|
3937
|
-
metadata: options?.metadata,
|
|
3938
|
-
expiresAt: options?.expiresIn ? Date.now() + options.expiresIn : undefined
|
|
3939
|
-
};
|
|
3940
|
-
const token = await this.generateToken(payload);
|
|
3941
|
-
this.currentToken = token;
|
|
3942
|
-
return token;
|
|
4837
|
+
isConnected() {
|
|
4838
|
+
return this.state === "connected" && this.ws?.readyState === import_websocket.default.OPEN;
|
|
3943
4839
|
}
|
|
3944
|
-
|
|
3945
|
-
return this.
|
|
4840
|
+
getWebSocket() {
|
|
4841
|
+
return this.ws;
|
|
3946
4842
|
}
|
|
3947
|
-
|
|
3948
|
-
|
|
4843
|
+
setHeaders(headers) {
|
|
4844
|
+
if (this.state !== "disconnected") {
|
|
4845
|
+
throw new Error("Cannot set headers while connected");
|
|
4846
|
+
}
|
|
4847
|
+
this.config.headers = { ...this.config.headers, ...headers };
|
|
3949
4848
|
}
|
|
3950
|
-
|
|
3951
|
-
this.
|
|
3952
|
-
this.revokedTokens.clear();
|
|
3953
|
-
this.currentToken = null;
|
|
4849
|
+
setAuthToken(token) {
|
|
4850
|
+
this.setHeaders({ Authorization: `Bearer ${token}` });
|
|
3954
4851
|
}
|
|
3955
|
-
|
|
3956
|
-
this.
|
|
4852
|
+
startHeartbeat() {
|
|
4853
|
+
if (this.heartbeatTimer) {
|
|
4854
|
+
clearInterval(this.heartbeatTimer);
|
|
4855
|
+
}
|
|
4856
|
+
this.heartbeatTimer = setInterval(() => {
|
|
4857
|
+
if (this.ws && this.ws.readyState === import_websocket.default.OPEN) {
|
|
4858
|
+
const heartbeatMessage = {
|
|
4859
|
+
id: `heartbeat-${Date.now()}`,
|
|
4860
|
+
type: "heartbeat",
|
|
4861
|
+
payload: { timestamp: Date.now() },
|
|
4862
|
+
timestamp: Date.now()
|
|
4863
|
+
};
|
|
4864
|
+
this.ws.send(JSON.stringify(heartbeatMessage));
|
|
4865
|
+
}
|
|
4866
|
+
}, this.config.heartbeatInterval);
|
|
3957
4867
|
}
|
|
3958
|
-
|
|
3959
|
-
this.
|
|
4868
|
+
stopHeartbeat() {
|
|
4869
|
+
if (this.heartbeatTimer) {
|
|
4870
|
+
clearInterval(this.heartbeatTimer);
|
|
4871
|
+
this.heartbeatTimer = undefined;
|
|
4872
|
+
}
|
|
3960
4873
|
}
|
|
3961
4874
|
}
|
|
3962
|
-
// node_modules/ws/wrapper.mjs
|
|
3963
|
-
var import_stream = __toESM(require_stream(), 1);
|
|
3964
|
-
var import_receiver = __toESM(require_receiver(), 1);
|
|
3965
|
-
var import_sender = __toESM(require_sender(), 1);
|
|
3966
|
-
var import_websocket = __toESM(require_websocket(), 1);
|
|
3967
|
-
var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
3968
|
-
|
|
3969
4875
|
// src/adapters/websocket/WebSocketServerTransportAdapter.ts
|
|
3970
4876
|
class WebSocketServerTransportAdapter {
|
|
3971
4877
|
state = "disconnected";
|
|
@@ -4325,8 +5231,8 @@ class WebSocketServerTransportAdapter {
|
|
|
4325
5231
|
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
|
4326
5232
|
}
|
|
4327
5233
|
}
|
|
4328
|
-
// src/adapters/websocket/
|
|
4329
|
-
class
|
|
5234
|
+
// src/adapters/websocket/BrowserWebSocketTransportAdapter.ts
|
|
5235
|
+
class BrowserWebSocketTransportAdapter {
|
|
4330
5236
|
ws;
|
|
4331
5237
|
state = "disconnected";
|
|
4332
5238
|
messageHandlers = new Set;
|
|
@@ -4335,36 +5241,38 @@ class WebSocketClientTransportAdapter {
|
|
|
4335
5241
|
config;
|
|
4336
5242
|
heartbeatTimer;
|
|
4337
5243
|
connectionTimer;
|
|
5244
|
+
authToken;
|
|
4338
5245
|
constructor(config) {
|
|
4339
5246
|
this.config = {
|
|
4340
|
-
headers: config?.headers ?? {},
|
|
4341
5247
|
connectionTimeout: config?.connectionTimeout ?? 30000,
|
|
4342
5248
|
enableHeartbeat: config?.enableHeartbeat ?? true,
|
|
4343
|
-
heartbeatInterval: config?.heartbeatInterval ?? 30000
|
|
5249
|
+
heartbeatInterval: config?.heartbeatInterval ?? 30000,
|
|
5250
|
+
authToken: config?.authToken
|
|
4344
5251
|
};
|
|
5252
|
+
this.authToken = config?.authToken;
|
|
4345
5253
|
}
|
|
4346
|
-
|
|
5254
|
+
setAuthToken(token) {
|
|
5255
|
+
this.authToken = token;
|
|
5256
|
+
}
|
|
5257
|
+
async connect(url, _options) {
|
|
4347
5258
|
if (this.state === "connected" || this.state === "connecting") {
|
|
4348
5259
|
throw new Error("Already connected or connecting");
|
|
4349
5260
|
}
|
|
4350
5261
|
this.state = "connecting";
|
|
4351
5262
|
return new Promise((resolve, reject) => {
|
|
4352
5263
|
try {
|
|
4353
|
-
const
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
}
|
|
4357
|
-
this.ws = new import_websocket.default(url, wsOptions);
|
|
4358
|
-
this.connectionTimer = setTimeout(() => {
|
|
5264
|
+
const wsUrl = this.authToken ? `${url}${url.includes("?") ? "&" : "?"}token=${encodeURIComponent(this.authToken)}` : url;
|
|
5265
|
+
this.ws = new WebSocket(wsUrl);
|
|
5266
|
+
this.connectionTimer = window.setTimeout(() => {
|
|
4359
5267
|
if (this.state === "connecting") {
|
|
4360
|
-
this.ws?.
|
|
5268
|
+
this.ws?.close();
|
|
4361
5269
|
this.state = "disconnected";
|
|
4362
5270
|
const error = new Error("Connection timeout");
|
|
4363
5271
|
this.errorHandlers.forEach((handler) => handler(error));
|
|
4364
5272
|
reject(error);
|
|
4365
5273
|
}
|
|
4366
5274
|
}, this.config.connectionTimeout);
|
|
4367
|
-
this.ws.
|
|
5275
|
+
this.ws.onopen = () => {
|
|
4368
5276
|
if (this.connectionTimer) {
|
|
4369
5277
|
clearTimeout(this.connectionTimer);
|
|
4370
5278
|
this.connectionTimer = undefined;
|
|
@@ -4374,41 +5282,44 @@ class WebSocketClientTransportAdapter {
|
|
|
4374
5282
|
this.startHeartbeat();
|
|
4375
5283
|
}
|
|
4376
5284
|
resolve();
|
|
4377
|
-
}
|
|
4378
|
-
this.ws.
|
|
5285
|
+
};
|
|
5286
|
+
this.ws.onmessage = (event) => {
|
|
4379
5287
|
try {
|
|
4380
|
-
const message = JSON.parse(data
|
|
5288
|
+
const message = JSON.parse(event.data);
|
|
4381
5289
|
this.messageHandlers.forEach((handler) => handler(message));
|
|
4382
5290
|
} catch (error) {
|
|
4383
|
-
|
|
5291
|
+
const err = new Error(`Failed to parse message: ${error}`);
|
|
5292
|
+
this.errorHandlers.forEach((handler) => handler(err));
|
|
4384
5293
|
}
|
|
4385
|
-
}
|
|
4386
|
-
this.ws.
|
|
5294
|
+
};
|
|
5295
|
+
this.ws.onerror = (_event) => {
|
|
4387
5296
|
if (this.connectionTimer) {
|
|
4388
5297
|
clearTimeout(this.connectionTimer);
|
|
4389
5298
|
this.connectionTimer = undefined;
|
|
4390
5299
|
}
|
|
5300
|
+
const error = new Error("WebSocket error occurred");
|
|
4391
5301
|
this.errorHandlers.forEach((handler) => handler(error));
|
|
4392
5302
|
if (this.state === "connecting") {
|
|
4393
5303
|
this.state = "disconnected";
|
|
4394
5304
|
reject(error);
|
|
4395
5305
|
}
|
|
4396
|
-
}
|
|
4397
|
-
this.ws.
|
|
5306
|
+
};
|
|
5307
|
+
this.ws.onclose = (event) => {
|
|
4398
5308
|
if (this.connectionTimer) {
|
|
4399
5309
|
clearTimeout(this.connectionTimer);
|
|
4400
5310
|
this.connectionTimer = undefined;
|
|
4401
5311
|
}
|
|
4402
5312
|
this.stopHeartbeat();
|
|
4403
5313
|
const wasConnected = this.state === "connected";
|
|
5314
|
+
const wasConnecting = this.state === "connecting";
|
|
4404
5315
|
this.state = "disconnected";
|
|
4405
5316
|
if (wasConnected) {
|
|
4406
|
-
this.closeHandlers.forEach((handler) => handler(code, reason
|
|
5317
|
+
this.closeHandlers.forEach((handler) => handler(event.code, event.reason || "Connection closed"));
|
|
4407
5318
|
}
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
}
|
|
5319
|
+
if (wasConnecting) {
|
|
5320
|
+
reject(new Error(`Connection closed: ${event.reason}`));
|
|
5321
|
+
}
|
|
5322
|
+
};
|
|
4412
5323
|
} catch (error) {
|
|
4413
5324
|
if (this.connectionTimer) {
|
|
4414
5325
|
clearTimeout(this.connectionTimer);
|
|
@@ -4433,14 +5344,18 @@ class WebSocketClientTransportAdapter {
|
|
|
4433
5344
|
this.ws.close(1000, "Client disconnecting");
|
|
4434
5345
|
await new Promise((resolve) => {
|
|
4435
5346
|
const timeout = setTimeout(() => {
|
|
4436
|
-
this.ws?.terminate();
|
|
4437
5347
|
resolve();
|
|
4438
5348
|
}, 5000);
|
|
4439
5349
|
const onClose = () => {
|
|
4440
5350
|
clearTimeout(timeout);
|
|
4441
5351
|
resolve();
|
|
4442
5352
|
};
|
|
4443
|
-
this.ws
|
|
5353
|
+
if (this.ws) {
|
|
5354
|
+
this.ws.addEventListener("close", onClose, { once: true });
|
|
5355
|
+
} else {
|
|
5356
|
+
clearTimeout(timeout);
|
|
5357
|
+
resolve();
|
|
5358
|
+
}
|
|
4444
5359
|
});
|
|
4445
5360
|
this.ws = undefined;
|
|
4446
5361
|
}
|
|
@@ -4450,7 +5365,7 @@ class WebSocketClientTransportAdapter {
|
|
|
4450
5365
|
if (this.state !== "connected") {
|
|
4451
5366
|
throw new Error("Not connected");
|
|
4452
5367
|
}
|
|
4453
|
-
if (!this.ws || this.ws.readyState !==
|
|
5368
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
4454
5369
|
throw new Error("WebSocket connection is not open");
|
|
4455
5370
|
}
|
|
4456
5371
|
try {
|
|
@@ -4474,26 +5389,17 @@ class WebSocketClientTransportAdapter {
|
|
|
4474
5389
|
return this.state;
|
|
4475
5390
|
}
|
|
4476
5391
|
isConnected() {
|
|
4477
|
-
return this.state === "connected" && this.ws?.readyState ===
|
|
5392
|
+
return this.state === "connected" && this.ws?.readyState === WebSocket.OPEN;
|
|
4478
5393
|
}
|
|
4479
5394
|
getWebSocket() {
|
|
4480
5395
|
return this.ws;
|
|
4481
5396
|
}
|
|
4482
|
-
setHeaders(headers) {
|
|
4483
|
-
if (this.state !== "disconnected") {
|
|
4484
|
-
throw new Error("Cannot set headers while connected");
|
|
4485
|
-
}
|
|
4486
|
-
this.config.headers = { ...this.config.headers, ...headers };
|
|
4487
|
-
}
|
|
4488
|
-
setAuthToken(token) {
|
|
4489
|
-
this.setHeaders({ Authorization: `Bearer ${token}` });
|
|
4490
|
-
}
|
|
4491
5397
|
startHeartbeat() {
|
|
4492
5398
|
if (this.heartbeatTimer) {
|
|
4493
5399
|
clearInterval(this.heartbeatTimer);
|
|
4494
5400
|
}
|
|
4495
|
-
this.heartbeatTimer = setInterval(() => {
|
|
4496
|
-
if (this.ws && this.ws.readyState ===
|
|
5401
|
+
this.heartbeatTimer = window.setInterval(() => {
|
|
5402
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
4497
5403
|
const heartbeatMessage = {
|
|
4498
5404
|
id: `heartbeat-${Date.now()}`,
|
|
4499
5405
|
type: "heartbeat",
|
|
@@ -4511,6 +5417,24 @@ class WebSocketClientTransportAdapter {
|
|
|
4511
5417
|
}
|
|
4512
5418
|
}
|
|
4513
5419
|
}
|
|
5420
|
+
// src/types/room.ts
|
|
5421
|
+
function serializeRoomState(state) {
|
|
5422
|
+
return {
|
|
5423
|
+
room: state.room,
|
|
5424
|
+
users: Object.fromEntries(state.users),
|
|
5425
|
+
eventHistory: state.eventHistory,
|
|
5426
|
+
locks: Object.fromEntries(state.locks)
|
|
5427
|
+
};
|
|
5428
|
+
}
|
|
5429
|
+
function deserializeRoomState(serialized) {
|
|
5430
|
+
return {
|
|
5431
|
+
room: serialized.room,
|
|
5432
|
+
users: new Map(Object.entries(serialized.users)),
|
|
5433
|
+
eventHistory: serialized.eventHistory,
|
|
5434
|
+
locks: new Map(Object.entries(serialized.locks))
|
|
5435
|
+
};
|
|
5436
|
+
}
|
|
5437
|
+
|
|
4514
5438
|
// src/client/BaseClient.ts
|
|
4515
5439
|
class BaseClient extends TypedEventEmitter {
|
|
4516
5440
|
transport;
|
|
@@ -4595,7 +5519,10 @@ class BaseClient extends TypedEventEmitter {
|
|
|
4595
5519
|
this.authToken = null;
|
|
4596
5520
|
this.userId = null;
|
|
4597
5521
|
this.reconnectAttempts = 0;
|
|
4598
|
-
await this.emit("disconnected", {
|
|
5522
|
+
await this.emit("disconnected", {
|
|
5523
|
+
code: 1000,
|
|
5524
|
+
reason: "Client disconnected"
|
|
5525
|
+
});
|
|
4599
5526
|
}
|
|
4600
5527
|
}
|
|
4601
5528
|
async reconnect() {
|
|
@@ -4764,14 +5691,19 @@ class BaseClient extends TypedEventEmitter {
|
|
|
4764
5691
|
}
|
|
4765
5692
|
await this.emit("authenticated", { userId: this.userId || "" });
|
|
4766
5693
|
} else {
|
|
4767
|
-
await this.emit("authentication_failed", {
|
|
5694
|
+
await this.emit("authentication_failed", {
|
|
5695
|
+
error: payload.error || "Authentication failed"
|
|
5696
|
+
});
|
|
4768
5697
|
}
|
|
4769
5698
|
}
|
|
4770
5699
|
async handleRoomJoined(payload) {
|
|
4771
5700
|
this.currentRoomId = payload.roomId;
|
|
4772
5701
|
const roomState = deserializeRoomState(payload.state);
|
|
4773
5702
|
this.currentRoomState = roomState;
|
|
4774
|
-
await this.emit("room_joined", {
|
|
5703
|
+
await this.emit("room_joined", {
|
|
5704
|
+
roomId: payload.roomId,
|
|
5705
|
+
state: roomState
|
|
5706
|
+
});
|
|
4775
5707
|
}
|
|
4776
5708
|
async handleRoomLeft(payload) {
|
|
4777
5709
|
if (this.currentRoomId === payload.roomId) {
|
|
@@ -4965,6 +5897,30 @@ class PresenceClient extends TypedEventEmitter {
|
|
|
4965
5897
|
});
|
|
4966
5898
|
}
|
|
4967
5899
|
}
|
|
5900
|
+
// src/types/experimental.ts
|
|
5901
|
+
class ExperimentalFeatureError extends Error {
|
|
5902
|
+
feature;
|
|
5903
|
+
configKey;
|
|
5904
|
+
constructor(feature, configKey) {
|
|
5905
|
+
super(`Experimental feature '${feature}' is not enabled.
|
|
5906
|
+
|
|
5907
|
+
` + `To enable, set '${String(configKey)}: true' in your server configuration:
|
|
5908
|
+
|
|
5909
|
+
` + ` const server = new ServerBuilder()
|
|
5910
|
+
` + ` .withExperimentalFeatures({ ${String(configKey)}: true })
|
|
5911
|
+
` + ` .build();
|
|
5912
|
+
|
|
5913
|
+
` + `⚠️ WARNING: Experimental APIs are for development only.
|
|
5914
|
+
` + `For production use, submit a feature request:
|
|
5915
|
+
` + `https://github.com/your-org/control-tower-core/issues/new
|
|
5916
|
+
|
|
5917
|
+
` + `See docs/EXPERIMENTAL_BROADCAST.md for details.`);
|
|
5918
|
+
this.feature = feature;
|
|
5919
|
+
this.configKey = configKey;
|
|
5920
|
+
this.name = "ExperimentalFeatureError";
|
|
5921
|
+
}
|
|
5922
|
+
}
|
|
5923
|
+
|
|
4968
5924
|
// src/server/ExperimentalAPI.ts
|
|
4969
5925
|
class ExperimentalAPI {
|
|
4970
5926
|
config;
|
|
@@ -5198,12 +6154,17 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5198
6154
|
throw new Error("Server is already running");
|
|
5199
6155
|
}
|
|
5200
6156
|
try {
|
|
5201
|
-
await this.transport.connect(`ws://localhost:${port}`, {
|
|
6157
|
+
await this.transport.connect(`ws://localhost:${port}`, {
|
|
6158
|
+
url: `ws://localhost:${port}`
|
|
6159
|
+
});
|
|
5202
6160
|
this.running = true;
|
|
5203
6161
|
this.initialized = true;
|
|
5204
6162
|
await this.emit("started", { port });
|
|
5205
6163
|
} catch (error) {
|
|
5206
|
-
await this.emit("error", {
|
|
6164
|
+
await this.emit("error", {
|
|
6165
|
+
error,
|
|
6166
|
+
context: "server_start"
|
|
6167
|
+
});
|
|
5207
6168
|
throw error;
|
|
5208
6169
|
}
|
|
5209
6170
|
}
|
|
@@ -5235,7 +6196,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5235
6196
|
this.initialized = true;
|
|
5236
6197
|
await this.emit("started", { port: 0 });
|
|
5237
6198
|
} catch (error) {
|
|
5238
|
-
await this.emit("error", {
|
|
6199
|
+
await this.emit("error", {
|
|
6200
|
+
error,
|
|
6201
|
+
context: "server_initialize"
|
|
6202
|
+
});
|
|
5239
6203
|
throw error;
|
|
5240
6204
|
}
|
|
5241
6205
|
}
|
|
@@ -5251,7 +6215,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5251
6215
|
try {
|
|
5252
6216
|
await this.transport.disconnect();
|
|
5253
6217
|
} catch (error) {
|
|
5254
|
-
await this.emit("error", {
|
|
6218
|
+
await this.emit("error", {
|
|
6219
|
+
error,
|
|
6220
|
+
context: "server_stop"
|
|
6221
|
+
});
|
|
5255
6222
|
}
|
|
5256
6223
|
this.clients.clear();
|
|
5257
6224
|
this.clientMessageHandlers.clear();
|
|
@@ -5478,10 +6445,16 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5478
6445
|
await this.handleLockRelease(clientId, message.payload);
|
|
5479
6446
|
break;
|
|
5480
6447
|
case "ping":
|
|
5481
|
-
await this.sendToClient(clientId, {
|
|
6448
|
+
await this.sendToClient(clientId, {
|
|
6449
|
+
type: "pong",
|
|
6450
|
+
timestamp: Date.now()
|
|
6451
|
+
});
|
|
5482
6452
|
break;
|
|
5483
6453
|
case "heartbeat":
|
|
5484
|
-
await this.sendToClient(clientId, {
|
|
6454
|
+
await this.sendToClient(clientId, {
|
|
6455
|
+
type: "heartbeat_ack",
|
|
6456
|
+
timestamp: Date.now()
|
|
6457
|
+
});
|
|
5485
6458
|
break;
|
|
5486
6459
|
default:
|
|
5487
6460
|
await this.sendToClient(clientId, {
|
|
@@ -5494,13 +6467,20 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5494
6467
|
type: "error",
|
|
5495
6468
|
error: error.message
|
|
5496
6469
|
});
|
|
5497
|
-
await this.emit("error", {
|
|
6470
|
+
await this.emit("error", {
|
|
6471
|
+
error,
|
|
6472
|
+
context: `client_${clientId}`
|
|
6473
|
+
});
|
|
5498
6474
|
}
|
|
5499
6475
|
};
|
|
5500
6476
|
}
|
|
5501
6477
|
async handleAuthenticate(clientId, payload) {
|
|
5502
6478
|
if (!this.auth) {
|
|
5503
|
-
await this.sendToClient(clientId, {
|
|
6479
|
+
await this.sendToClient(clientId, {
|
|
6480
|
+
type: "auth_result",
|
|
6481
|
+
success: false,
|
|
6482
|
+
error: "Authentication not configured"
|
|
6483
|
+
});
|
|
5504
6484
|
return;
|
|
5505
6485
|
}
|
|
5506
6486
|
try {
|
|
@@ -5509,15 +6489,28 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5509
6489
|
if (client) {
|
|
5510
6490
|
client.userId = tokenPayload.userId;
|
|
5511
6491
|
client.authenticated = true;
|
|
6492
|
+
this.clientUserMap.set(clientId, tokenPayload.userId);
|
|
6493
|
+
if (!this.userClientMap.has(tokenPayload.userId)) {
|
|
6494
|
+
this.userClientMap.set(tokenPayload.userId, new Set);
|
|
6495
|
+
}
|
|
6496
|
+
this.userClientMap.get(tokenPayload.userId).add(clientId);
|
|
5512
6497
|
await this.emit("client_authenticated", {
|
|
5513
6498
|
clientId,
|
|
5514
6499
|
userId: tokenPayload.userId,
|
|
5515
6500
|
metadata: tokenPayload.metadata
|
|
5516
6501
|
});
|
|
5517
6502
|
}
|
|
5518
|
-
await this.sendToClient(clientId, {
|
|
6503
|
+
await this.sendToClient(clientId, {
|
|
6504
|
+
type: "auth_result",
|
|
6505
|
+
success: true,
|
|
6506
|
+
userId: tokenPayload.userId
|
|
6507
|
+
});
|
|
5519
6508
|
} catch (error) {
|
|
5520
|
-
await this.sendToClient(clientId, {
|
|
6509
|
+
await this.sendToClient(clientId, {
|
|
6510
|
+
type: "auth_result",
|
|
6511
|
+
success: false,
|
|
6512
|
+
error: error.message
|
|
6513
|
+
});
|
|
5521
6514
|
}
|
|
5522
6515
|
}
|
|
5523
6516
|
async handleJoinRoom(clientId, payload) {
|
|
@@ -5529,17 +6522,26 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5529
6522
|
await this.handleAuthenticate(clientId, { token: payload.token });
|
|
5530
6523
|
}
|
|
5531
6524
|
if (!client.authenticated) {
|
|
5532
|
-
await this.sendToClient(clientId, {
|
|
6525
|
+
await this.sendToClient(clientId, {
|
|
6526
|
+
type: "error",
|
|
6527
|
+
error: "Authentication required to join room"
|
|
6528
|
+
});
|
|
5533
6529
|
return;
|
|
5534
6530
|
}
|
|
5535
6531
|
try {
|
|
5536
6532
|
if (client.roomIds.has(payload.roomId)) {
|
|
5537
|
-
await this.sendToClient(clientId, {
|
|
6533
|
+
await this.sendToClient(clientId, {
|
|
6534
|
+
type: "error",
|
|
6535
|
+
error: "Already in this room"
|
|
6536
|
+
});
|
|
5538
6537
|
return;
|
|
5539
6538
|
}
|
|
5540
6539
|
let roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
5541
6540
|
if (!roomState) {
|
|
5542
|
-
const roomConfig = {
|
|
6541
|
+
const roomConfig = {
|
|
6542
|
+
...this.config.defaultRoomConfig,
|
|
6543
|
+
id: payload.roomId
|
|
6544
|
+
};
|
|
5543
6545
|
const room = await this.roomManager.createRoom(payload.roomId, roomConfig);
|
|
5544
6546
|
roomState = await this.roomManager.getRoomState(payload.roomId);
|
|
5545
6547
|
await this.emit("room_created", { room });
|
|
@@ -5557,7 +6559,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5557
6559
|
};
|
|
5558
6560
|
await this.roomManager.joinRoom(payload.roomId, user);
|
|
5559
6561
|
client.roomIds.add(payload.roomId);
|
|
5560
|
-
await this.emit("client_joined_room", {
|
|
6562
|
+
await this.emit("client_joined_room", {
|
|
6563
|
+
clientId,
|
|
6564
|
+
roomId: payload.roomId
|
|
6565
|
+
});
|
|
5561
6566
|
const updatedRoomState = await this.roomManager.getRoomState(payload.roomId);
|
|
5562
6567
|
if (!updatedRoomState) {
|
|
5563
6568
|
throw new Error("Failed to get updated room state after joining");
|
|
@@ -5576,7 +6581,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5576
6581
|
});
|
|
5577
6582
|
}
|
|
5578
6583
|
} catch (error) {
|
|
5579
|
-
await this.sendToClient(clientId, {
|
|
6584
|
+
await this.sendToClient(clientId, {
|
|
6585
|
+
type: "error",
|
|
6586
|
+
error: error.message
|
|
6587
|
+
});
|
|
5580
6588
|
}
|
|
5581
6589
|
}
|
|
5582
6590
|
async handleLeaveRoom(clientId, payload) {
|
|
@@ -5601,7 +6609,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5601
6609
|
async handleBroadcastEvent(clientId, payload) {
|
|
5602
6610
|
const client = this.clients.get(clientId);
|
|
5603
6611
|
if (!client || !client.roomIds.has(payload.roomId)) {
|
|
5604
|
-
await this.sendToClient(clientId, {
|
|
6612
|
+
await this.sendToClient(clientId, {
|
|
6613
|
+
type: "error",
|
|
6614
|
+
error: "Not in specified room"
|
|
6615
|
+
});
|
|
5605
6616
|
return;
|
|
5606
6617
|
}
|
|
5607
6618
|
const enrichedEvent = {
|
|
@@ -5624,7 +6635,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5624
6635
|
async handleLockRequest(clientId, payload) {
|
|
5625
6636
|
const client = this.clients.get(clientId);
|
|
5626
6637
|
if (!client || !client.roomIds.has(payload.roomId)) {
|
|
5627
|
-
await this.sendToClient(clientId, {
|
|
6638
|
+
await this.sendToClient(clientId, {
|
|
6639
|
+
type: "error",
|
|
6640
|
+
error: "Not in specified room"
|
|
6641
|
+
});
|
|
5628
6642
|
return;
|
|
5629
6643
|
}
|
|
5630
6644
|
try {
|
|
@@ -5638,10 +6652,18 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5638
6652
|
userId: client.userId,
|
|
5639
6653
|
roomId: payload.roomId,
|
|
5640
6654
|
data: { lock, action: "acquired" },
|
|
5641
|
-
metadata: {
|
|
6655
|
+
metadata: {
|
|
6656
|
+
userId: client.userId,
|
|
6657
|
+
timestamp: Date.now(),
|
|
6658
|
+
roomId: payload.roomId
|
|
6659
|
+
}
|
|
5642
6660
|
});
|
|
5643
6661
|
} catch (error) {
|
|
5644
|
-
await this.sendToClient(clientId, {
|
|
6662
|
+
await this.sendToClient(clientId, {
|
|
6663
|
+
type: "lock_denied",
|
|
6664
|
+
request: payload.request,
|
|
6665
|
+
reason: error.message
|
|
6666
|
+
});
|
|
5645
6667
|
}
|
|
5646
6668
|
}
|
|
5647
6669
|
async handleLockRelease(clientId, payload) {
|
|
@@ -5652,7 +6674,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5652
6674
|
try {
|
|
5653
6675
|
await this.lockManager.releaseLock(payload.lockId);
|
|
5654
6676
|
await this.emit("lock_released", { lockId: payload.lockId, clientId });
|
|
5655
|
-
await this.sendToClient(clientId, {
|
|
6677
|
+
await this.sendToClient(clientId, {
|
|
6678
|
+
type: "lock_released",
|
|
6679
|
+
lockId: payload.lockId
|
|
6680
|
+
});
|
|
5656
6681
|
for (const roomId of client.roomIds) {
|
|
5657
6682
|
await this.roomManager.broadcastToRoom(roomId, {
|
|
5658
6683
|
id: this.generateId(),
|
|
@@ -5665,7 +6690,10 @@ class BaseServer extends TypedEventEmitter {
|
|
|
5665
6690
|
});
|
|
5666
6691
|
}
|
|
5667
6692
|
} catch (error) {
|
|
5668
|
-
await this.sendToClient(clientId, {
|
|
6693
|
+
await this.sendToClient(clientId, {
|
|
6694
|
+
type: "error",
|
|
6695
|
+
error: error.message
|
|
6696
|
+
});
|
|
5669
6697
|
}
|
|
5670
6698
|
}
|
|
5671
6699
|
async sendToClient(clientId, message) {
|
|
@@ -5871,6 +6899,7 @@ class ServerBuilder {
|
|
|
5871
6899
|
export {
|
|
5872
6900
|
WebSocketServerTransportAdapter,
|
|
5873
6901
|
WebSocketClientTransportAdapter,
|
|
6902
|
+
WebRTCSignalingAdapter,
|
|
5874
6903
|
TypedEventEmitter,
|
|
5875
6904
|
ServerBuilder,
|
|
5876
6905
|
RoomManager,
|
|
@@ -5878,6 +6907,8 @@ export {
|
|
|
5878
6907
|
PresenceClient,
|
|
5879
6908
|
MockTransportAdapter,
|
|
5880
6909
|
MockStorageAdapter,
|
|
6910
|
+
MockRTCPeerConnection,
|
|
6911
|
+
MockRTCDataChannel,
|
|
5881
6912
|
MockAuthAdapter,
|
|
5882
6913
|
LockManager,
|
|
5883
6914
|
ExperimentalFeatureError,
|
|
@@ -5886,8 +6917,9 @@ export {
|
|
|
5886
6917
|
DefaultPresenceManager,
|
|
5887
6918
|
DefaultLockManager,
|
|
5888
6919
|
ClientBuilder,
|
|
6920
|
+
BrowserWebSocketTransportAdapter,
|
|
5889
6921
|
BaseServer,
|
|
5890
6922
|
BaseClient
|
|
5891
6923
|
};
|
|
5892
6924
|
|
|
5893
|
-
//# debugId=
|
|
6925
|
+
//# debugId=94389897A03B4D7064756E2164756E21
|