@digo-labs/ws 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +1 -0
- package/dist/client.js +12 -8
- package/dist/client.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.js +30 -19
- package/dist/server.js.map +1 -1
- package/dist/start-hub.cjs +32491 -0
- package/dist/start-hub.cjs.map +1 -0
- package/package.json +26 -4
package/dist/client.d.ts
CHANGED
package/dist/client.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
var c = Object.defineProperty;
|
|
2
2
|
var h = (i, e, s) => e in i ? c(i, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : i[e] = s;
|
|
3
3
|
var t = (i, e, s) => h(i, typeof e != "symbol" ? e + "" : e, s);
|
|
4
|
-
import { WebsocketClientEvent as n } from "@digo-labs/common";
|
|
5
|
-
class
|
|
4
|
+
import { WS_URL as a, WebsocketClientEvent as n } from "@digo-labs/common";
|
|
5
|
+
class b {
|
|
6
6
|
constructor(e) {
|
|
7
7
|
t(this, "url");
|
|
8
|
+
t(this, "app");
|
|
8
9
|
t(this, "id");
|
|
9
10
|
t(this, "reconnectEnabled");
|
|
10
11
|
t(this, "reconnectInterval");
|
|
@@ -17,11 +18,11 @@ class d {
|
|
|
17
18
|
t(this, "openHandlers", []);
|
|
18
19
|
t(this, "closeHandlers", []);
|
|
19
20
|
t(this, "errorHandlers", []);
|
|
20
|
-
this.url =
|
|
21
|
+
this.url = e.url ?? a, this.app = e.app, this.id = e.id ?? crypto.randomUUID(), this.reconnectEnabled = e.reconnect ?? !0, this.reconnectInterval = e.reconnectInterval ?? 1e3, this.maxReconnectInterval = e.maxReconnectInterval ?? 1e4, this.reconnectDelay = this.reconnectInterval, this.open();
|
|
21
22
|
}
|
|
22
23
|
open() {
|
|
23
24
|
this.socket = new WebSocket(this.url), this.socket.onopen = () => {
|
|
24
|
-
this.reconnectDelay = this.reconnectInterval, this.sendToHub({ type: n.IDENTIFY, id: this.id }), this.subscribedChannels.size > 0 && this.sendToHub({ type: n.SUBSCRIBE, channels: [...this.subscribedChannels] }), this.openHandlers.forEach((e) => e());
|
|
25
|
+
this.reconnectDelay = this.reconnectInterval, this.sendToHub({ type: n.IDENTIFY, id: this.id, app: this.app }), this.subscribedChannels.size > 0 && this.sendToHub({ type: n.SUBSCRIBE, channels: [...this.subscribedChannels] }), this.openHandlers.forEach((e) => e());
|
|
25
26
|
}, this.socket.onmessage = (e) => {
|
|
26
27
|
let s;
|
|
27
28
|
try {
|
|
@@ -32,9 +33,12 @@ class d {
|
|
|
32
33
|
const r = this.listeners.get(s.channel);
|
|
33
34
|
r == null || r.forEach((o) => o(s.data));
|
|
34
35
|
}, this.socket.onclose = (e) => {
|
|
35
|
-
this.closeHandlers.forEach((s) => s()), e.code !== 4e3 && this.alive && this.reconnectEnabled
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
if (this.closeHandlers.forEach((s) => s()), e.code !== 4e3 && this.alive && this.reconnectEnabled) {
|
|
37
|
+
const s = this.reconnectDelay * (0.5 + Math.random());
|
|
38
|
+
setTimeout(() => {
|
|
39
|
+
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval), this.open();
|
|
40
|
+
}, s);
|
|
41
|
+
}
|
|
38
42
|
}, this.socket.onerror = (e) => {
|
|
39
43
|
this.errorHandlers.forEach((s) => s(e));
|
|
40
44
|
};
|
|
@@ -71,6 +75,6 @@ class d {
|
|
|
71
75
|
}
|
|
72
76
|
}
|
|
73
77
|
export {
|
|
74
|
-
|
|
78
|
+
b as WebsocketClient
|
|
75
79
|
};
|
|
76
80
|
//# sourceMappingURL=client.js.map
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sources":["../src/client.ts"],"sourcesContent":["import { WebsocketConnectionConfig, WebsocketClientEvent, WebsocketClientMessage, WebsocketHubMessage, WebsocketListener } from '@digo-labs/common';\n\nexport class WebsocketClient {\n private url: string;\n private id: string;\n private reconnectEnabled: boolean;\n private reconnectInterval: number;\n private maxReconnectInterval: number;\n private reconnectDelay: number;\n\n private socket: WebSocket | null = null;\n private alive = true;\n private listeners = new Map<string, Set<WebsocketListener>>();\n private subscribedChannels = new Set<string>();\n private openHandlers: (() => void)[] = [];\n private closeHandlers: (() => void)[] = [];\n private errorHandlers: ((error: Event) => void)[] = [];\n\n constructor(properties: WebsocketConnectionConfig) {\n this.url =
|
|
1
|
+
{"version":3,"file":"client.js","sources":["../src/client.ts"],"sourcesContent":["import { WebsocketConnectionConfig, WebsocketClientEvent, WebsocketClientMessage, WebsocketHubMessage, WebsocketListener, WS_URL } from '@digo-labs/common';\n\nexport class WebsocketClient {\n private url: string;\n private app: string;\n private id: string;\n private reconnectEnabled: boolean;\n private reconnectInterval: number;\n private maxReconnectInterval: number;\n private reconnectDelay: number;\n\n private socket: WebSocket | null = null;\n private alive = true;\n private listeners = new Map<string, Set<WebsocketListener>>();\n private subscribedChannels = new Set<string>();\n private openHandlers: (() => void)[] = [];\n private closeHandlers: (() => void)[] = [];\n private errorHandlers: ((error: Event) => void)[] = [];\n\n constructor(properties: WebsocketConnectionConfig) {\n this.url = properties.url ?? WS_URL;\n this.app = properties.app;\n this.id = properties.id ?? crypto.randomUUID();\n this.reconnectEnabled = properties.reconnect ?? true;\n this.reconnectInterval = properties.reconnectInterval ?? 1000;\n this.maxReconnectInterval = properties.maxReconnectInterval ?? 10000;\n this.reconnectDelay = this.reconnectInterval;\n\n this.open();\n }\n\n private open(): void {\n this.socket = new WebSocket(this.url);\n\n this.socket.onopen = () => {\n this.reconnectDelay = this.reconnectInterval;\n\n this.sendToHub({ type: WebsocketClientEvent.IDENTIFY, id: this.id, app: this.app });\n\n if (this.subscribedChannels.size > 0) {\n this.sendToHub({ type: WebsocketClientEvent.SUBSCRIBE, channels: [...this.subscribedChannels] });\n }\n\n this.openHandlers.forEach(h => h());\n };\n\n this.socket.onmessage = (event) => {\n let message: WebsocketHubMessage;\n\n try {\n message = JSON.parse(event.data as string);\n } catch { return; }\n\n const channelListeners = this.listeners.get(message.channel);\n\n channelListeners?.forEach(listener => listener(message.data));\n };\n\n this.socket.onclose = (event) => {\n this.closeHandlers.forEach(h => h());\n\n if (event.code === 4000) return;\n\n if (this.alive && this.reconnectEnabled) {\n const jitteredDelay = this.reconnectDelay * (0.5 + Math.random());\n\n setTimeout(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval);\n this.open();\n }, jitteredDelay);\n }\n };\n\n this.socket.onerror = (error) => {\n this.errorHandlers.forEach(h => h(error));\n };\n }\n\n private sendToHub(message: WebsocketClientMessage): void {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(JSON.stringify(message));\n }\n }\n\n public send(channel: string, data?: unknown): void {\n this.sendToHub({ type: WebsocketClientEvent.MESSAGE, channel, data });\n }\n\n public setState(channel: string, data?: unknown): void {\n this.sendToHub({ type: WebsocketClientEvent.STATE, channel, data });\n }\n\n public subscribe(channel: string, listener: WebsocketListener): void {\n if (!this.listeners.has(channel)) this.listeners.set(channel, new Set());\n\n this.listeners.get(channel)!.add(listener);\n\n if (!this.subscribedChannels.has(channel)) {\n this.subscribedChannels.add(channel);\n this.sendToHub({ type: WebsocketClientEvent.SUBSCRIBE, channels: [channel] });\n }\n }\n\n public unsubscribe(channel: string, listener?: WebsocketListener): void {\n if (listener) {\n this.listeners.get(channel)?.delete(listener);\n\n if (this.listeners.get(channel)?.size === 0) {\n this.listeners.delete(channel);\n this.subscribedChannels.delete(channel);\n this.sendToHub({ type: WebsocketClientEvent.UNSUBSCRIBE, channels: [channel] });\n }\n } else {\n this.listeners.delete(channel);\n this.subscribedChannels.delete(channel);\n this.sendToHub({ type: WebsocketClientEvent.UNSUBSCRIBE, channels: [channel] });\n }\n }\n\n public onOpen(handler: () => void): void { this.openHandlers.push(handler); }\n\n public onClose(handler: () => void): void { this.closeHandlers.push(handler); }\n\n public onError(handler: (error: Event) => void): void { this.errorHandlers.push(handler); }\n\n public close(): void {\n this.alive = false;\n this.socket?.close();\n }\n}\n"],"names":["WebsocketClient","properties","__publicField","WS_URL","WebsocketClientEvent","h","event","message","channelListeners","listener","jitteredDelay","error","_a","channel","data","_b","handler"],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
var S = Object.defineProperty;
|
|
2
|
-
var
|
|
3
|
-
var h = (i, t, e) =>
|
|
2
|
+
var p = (i, t, e) => t in i ? S(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
|
|
3
|
+
var h = (i, t, e) => p(i, typeof t != "symbol" ? t + "" : t, e);
|
|
4
|
+
import E from "node:http";
|
|
4
5
|
import { WebSocketServer as b, WebSocket as l } from "ws";
|
|
5
|
-
import { WebsocketLogEvent as
|
|
6
|
-
class
|
|
6
|
+
import { WebsocketLogEvent as r, WebsocketClientEvent as o, WebSocketHubEvent as d, WEBSOCKET_HUB_CHANNEL as c } from "@digo-labs/common";
|
|
7
|
+
class g {
|
|
7
8
|
constructor(t) {
|
|
9
|
+
h(this, "httpServer");
|
|
8
10
|
h(this, "wss");
|
|
9
11
|
h(this, "clients", /* @__PURE__ */ new Set());
|
|
10
12
|
h(this, "state", /* @__PURE__ */ new Map());
|
|
11
13
|
h(this, "logs", []);
|
|
12
14
|
h(this, "messageHandlers", []);
|
|
13
15
|
h(this, "heartbeatInterval");
|
|
14
|
-
this.
|
|
16
|
+
this.httpServer = E.createServer((e, a) => {
|
|
17
|
+
if (e.method === "GET" && e.url === "/health") {
|
|
18
|
+
a.writeHead(200, { "Content-Type": "application/json" }), a.end(JSON.stringify({ ok: !0, clients: this.clients.size }));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
a.writeHead(404), a.end();
|
|
22
|
+
}), this.wss = new b({ server: this.httpServer }), this.heartbeatInterval = setInterval(() => this.heartbeat(), 3e4), this.wss.on("close", () => clearInterval(this.heartbeatInterval)), this.wss.on("connection", (e) => this.handleConnection(e)), this.httpServer.listen(t.port);
|
|
15
23
|
}
|
|
16
24
|
heartbeat() {
|
|
17
25
|
let t = !1;
|
|
@@ -26,6 +34,7 @@ class w {
|
|
|
26
34
|
handleConnection(t) {
|
|
27
35
|
const e = {
|
|
28
36
|
id: "unknown",
|
|
37
|
+
app: "unknown",
|
|
29
38
|
ws: t,
|
|
30
39
|
channels: /* @__PURE__ */ new Set(),
|
|
31
40
|
alive: !0
|
|
@@ -42,17 +51,17 @@ class w {
|
|
|
42
51
|
}
|
|
43
52
|
this.handleMessage(e, t, s);
|
|
44
53
|
}), t.on("close", () => {
|
|
45
|
-
this.clients.delete(e), this.addLog(e.id,
|
|
54
|
+
this.clients.delete(e), this.addLog(e.id, r.DISCONNECT, ""), this.broadcastStatus();
|
|
46
55
|
});
|
|
47
56
|
}
|
|
48
57
|
handleMessage(t, e, a) {
|
|
49
58
|
switch (a.type) {
|
|
50
|
-
case
|
|
59
|
+
case o.IDENTIFY: {
|
|
51
60
|
const s = [...this.clients].find((n) => n.id === a.id && n !== t);
|
|
52
|
-
s && (s.ws.close(4e3), this.clients.delete(s)), t.id = a.id, this.addLog(a.id,
|
|
61
|
+
s && (s.ws.close(4e3), this.clients.delete(s)), t.id = a.id, t.app = a.app, this.addLog(a.id, r.CONNECT, ""), this.broadcastStatus();
|
|
53
62
|
break;
|
|
54
63
|
}
|
|
55
|
-
case
|
|
64
|
+
case o.SUBSCRIBE: {
|
|
56
65
|
a.channels.forEach((s) => {
|
|
57
66
|
if (t.channels.add(s), this.state.has(s)) {
|
|
58
67
|
const n = {
|
|
@@ -62,30 +71,30 @@ class w {
|
|
|
62
71
|
};
|
|
63
72
|
e.send(JSON.stringify(n));
|
|
64
73
|
}
|
|
65
|
-
}), this.addLog(t.id,
|
|
74
|
+
}), this.addLog(t.id, r.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
|
|
66
75
|
break;
|
|
67
76
|
}
|
|
68
|
-
case
|
|
69
|
-
a.channels.forEach((s) => t.channels.delete(s)), this.addLog(t.id,
|
|
77
|
+
case o.UNSUBSCRIBE: {
|
|
78
|
+
a.channels.forEach((s) => t.channels.delete(s)), this.addLog(t.id, r.UNSUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
|
|
70
79
|
break;
|
|
71
80
|
}
|
|
72
|
-
case
|
|
81
|
+
case o.MESSAGE: {
|
|
73
82
|
const s = {
|
|
74
83
|
type: d.MESSAGE,
|
|
75
84
|
channel: a.channel,
|
|
76
85
|
data: a.data
|
|
77
86
|
};
|
|
78
|
-
this.broadcast(t, a.channel, s), this.messageHandlers.forEach((n) => n(a.channel, a.data)), this.addLog(t.id,
|
|
87
|
+
this.broadcast(t, a.channel, s), this.messageHandlers.forEach((n) => n(a.channel, a.data)), this.addLog(t.id, r.MESSAGE, a.channel, a.data), this.broadcastStatus();
|
|
79
88
|
break;
|
|
80
89
|
}
|
|
81
|
-
case
|
|
90
|
+
case o.STATE: {
|
|
82
91
|
this.state.set(a.channel, a.data);
|
|
83
92
|
const s = {
|
|
84
93
|
type: d.STATE,
|
|
85
94
|
channel: a.channel,
|
|
86
95
|
data: a.data
|
|
87
96
|
};
|
|
88
|
-
this.broadcast(t, a.channel, s), this.messageHandlers.forEach((n) => n(a.channel, a.data)), this.addLog(t.id,
|
|
97
|
+
this.broadcast(t, a.channel, s), this.messageHandlers.forEach((n) => n(a.channel, a.data)), this.addLog(t.id, r.STATE, a.channel, a.data), this.broadcastStatus();
|
|
89
98
|
break;
|
|
90
99
|
}
|
|
91
100
|
}
|
|
@@ -101,7 +110,7 @@ class w {
|
|
|
101
110
|
}
|
|
102
111
|
broadcastStatus() {
|
|
103
112
|
const t = {
|
|
104
|
-
clients: [...this.clients].map((s) => ({ id: s.id, channels: [...s.channels] })),
|
|
113
|
+
clients: [...this.clients].map((s) => ({ id: s.id, app: s.app, channels: [...s.channels] })),
|
|
105
114
|
state: Object.fromEntries([...this.state].filter(([s]) => s !== c)),
|
|
106
115
|
logs: this.logs
|
|
107
116
|
};
|
|
@@ -122,10 +131,12 @@ class w {
|
|
|
122
131
|
return this.state.get(t);
|
|
123
132
|
}
|
|
124
133
|
close() {
|
|
125
|
-
clearInterval(this.heartbeatInterval), this.
|
|
134
|
+
clearInterval(this.heartbeatInterval), this.clients.forEach((t) => {
|
|
135
|
+
t.ws.readyState === l.OPEN && t.ws.close(1001, "Hub shutting down");
|
|
136
|
+
}), this.wss.close(), this.httpServer.close();
|
|
126
137
|
}
|
|
127
138
|
}
|
|
128
139
|
export {
|
|
129
|
-
|
|
140
|
+
g as WebsocketHub
|
|
130
141
|
};
|
|
131
142
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../src/server.ts"],"sourcesContent":["import { WebSocketServer, WebSocket } from 'ws';\n\nimport { WebsocketClientMessage, WebsocketClientEvent, WebSocketHubEvent, WebsocketHubMessage, WebsocketHubLogEntry, WebsocketLogEvent, WebsocketHubStatus, WebsocketsHubConfig, WEBSOCKET_HUB_CHANNEL } from '@digo-labs/common';\n\ninterface ConnectedClient {\n id: string;\n ws: WebSocket;\n channels: Set<string>;\n alive: boolean;\n}\n\nexport class WebsocketHub {\n private wss: WebSocketServer;\n\n private clients = new Set<ConnectedClient>();\n private state = new Map<string, unknown>();\n\n private logs: WebsocketHubLogEntry[] = [];\n private messageHandlers: ((channel: string, data: unknown) => void)[] = [];\n private heartbeatInterval: ReturnType<typeof setInterval>;\n\n constructor(config: WebsocketsHubConfig) {\n this.wss = new WebSocketServer({ port: config.port });\n\n this.heartbeatInterval = setInterval(() => this.heartbeat(), 30000);\n\n this.wss.on('close', () => clearInterval(this.heartbeatInterval));\n this.wss.on('connection', socket => this.handleConnection(socket));\n }\n\n private heartbeat(): void {\n let terminated = false;\n\n this.clients.forEach((client) => {\n if (!client.alive) {\n client.ws.terminate();\n\n this.clients.delete(client);\n\n terminated = true;\n\n return;\n }\n\n client.alive = false;\n\n client.ws.ping();\n });\n\n if (terminated) this.broadcastStatus();\n }\n\n private handleConnection(socket: WebSocket): void {\n const client: ConnectedClient = {\n id: 'unknown',\n ws: socket,\n channels: new Set(),\n alive: true,\n };\n\n this.clients.add(client);\n\n socket.on('error', () => {});\n\n socket.on('pong', () => {\n client.alive = true;\n });\n\n socket.on('message', (raw) => {\n let message: WebsocketClientMessage;\n\n try {\n message = JSON.parse(raw.toString());\n } catch { return; }\n\n this.handleMessage(client, socket, message);\n });\n\n socket.on('close', () => {\n this.clients.delete(client);\n\n this.addLog(client.id, WebsocketLogEvent.DISCONNECT, '');\n\n this.broadcastStatus();\n });\n }\n\n private handleMessage(client: ConnectedClient, socket: WebSocket, message: WebsocketClientMessage): void {\n switch (message.type) {\n case WebsocketClientEvent.IDENTIFY: {\n const existing = [...this.clients].find(c => c.id === message.id && c !== client);\n\n if (existing) {\n existing.ws.close(4000);\n\n this.clients.delete(existing);\n }\n\n client.id = message.id;\n\n this.addLog(message.id, WebsocketLogEvent.CONNECT, '');\n\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.SUBSCRIBE: {\n message.channels.forEach((channel) => {\n client.channels.add(channel);\n\n if (this.state.has(channel)) {\n const stateMessage: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: channel,\n data: this.state.get(channel),\n };\n\n socket.send(JSON.stringify(stateMessage));\n }\n });\n\n this.addLog(client.id, WebsocketLogEvent.SUBSCRIBE, message.channels.join(', '));\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.UNSUBSCRIBE: {\n message.channels.forEach(channel => client.channels.delete(channel));\n\n this.addLog(client.id, WebsocketLogEvent.UNSUBSCRIBE, message.channels.join(', '));\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.MESSAGE: {\n const outgoing: WebsocketHubMessage = {\n type: WebSocketHubEvent.MESSAGE,\n channel: message.channel,\n data: message.data,\n };\n\n this.broadcast(client, message.channel, outgoing);\n\n this.messageHandlers.forEach(h => h(message.channel, message.data));\n\n this.addLog(client.id, WebsocketLogEvent.MESSAGE, message.channel, message.data);\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.STATE: {\n this.state.set(message.channel, message.data);\n\n const outgoing: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: message.channel,\n data: message.data,\n };\n\n this.broadcast(client, message.channel, outgoing);\n\n this.messageHandlers.forEach(h => h(message.channel, message.data));\n\n this.addLog(client.id, WebsocketLogEvent.STATE, message.channel, message.data);\n\n this.broadcastStatus();\n break;\n }\n }\n }\n\n private addLog(sender: string, type: WebsocketLogEvent, channel: string, data?: unknown): void {\n this.logs.push({ timestamp: Date.now(), sender, type, channel, data });\n\n if (this.logs.length > 808) this.logs.shift();\n }\n\n private broadcast(sender: ConnectedClient, channel: string, message: WebsocketHubMessage): void {\n const payload = JSON.stringify(message);\n\n this.clients.forEach((client) => {\n if (client !== sender && client.channels.has(channel) && client.ws.readyState === WebSocket.OPEN) {\n client.ws.send(payload);\n }\n });\n }\n\n private broadcastStatus(): void {\n const status: WebsocketHubStatus = {\n clients: [...this.clients].map(c => ({ id: c.id, channels: [...c.channels] })),\n state: Object.fromEntries([...this.state].filter(([k]) => k !== WEBSOCKET_HUB_CHANNEL)),\n logs: this.logs,\n };\n\n this.state.set(WEBSOCKET_HUB_CHANNEL, status);\n\n const message: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: WEBSOCKET_HUB_CHANNEL,\n data: status,\n };\n\n const payload = JSON.stringify(message);\n\n this.clients.forEach((client) => {\n if (client.channels.has(WEBSOCKET_HUB_CHANNEL) && client.ws.readyState === WebSocket.OPEN) {\n client.ws.send(payload);\n }\n });\n }\n\n public onMessage(handler: (channel: string, data: unknown) => void): void { this.messageHandlers.push(handler); }\n\n public getState(channel: string): unknown { return this.state.get(channel); }\n\n public close(): void {\n clearInterval(this.heartbeatInterval);\n this.wss.close();\n }\n}\n"],"names":["WebsocketHub","config","__publicField","WebSocketServer","socket","terminated","client","raw","message","WebsocketLogEvent","WebsocketClientEvent","existing","c","channel","stateMessage","WebSocketHubEvent","outgoing","h","sender","type","data","payload","WebSocket","status","k","WEBSOCKET_HUB_CHANNEL","handler"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../src/server.ts"],"sourcesContent":["import http from 'node:http';\nimport { WebSocketServer, WebSocket } from 'ws';\n\nimport { WebsocketClientMessage, WebsocketClientEvent, WebSocketHubEvent, WebsocketHubMessage, WebsocketHubLogEntry, WebsocketLogEvent, WebsocketHubStatus, WebsocketsHubConfig, WEBSOCKET_HUB_CHANNEL } from '@digo-labs/common';\n\ninterface ConnectedClient {\n id: string;\n app: string;\n ws: WebSocket;\n channels: Set<string>;\n alive: boolean;\n}\n\nexport class WebsocketHub {\n private httpServer: http.Server;\n private wss: WebSocketServer;\n\n private clients = new Set<ConnectedClient>();\n private state = new Map<string, unknown>();\n\n private logs: WebsocketHubLogEntry[] = [];\n private messageHandlers: ((channel: string, data: unknown) => void)[] = [];\n private heartbeatInterval: ReturnType<typeof setInterval>;\n\n constructor(config: WebsocketsHubConfig) {\n this.httpServer = http.createServer((req, res) => {\n if (req.method === 'GET' && req.url === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true, clients: this.clients.size }));\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n this.wss = new WebSocketServer({ server: this.httpServer });\n\n this.heartbeatInterval = setInterval(() => this.heartbeat(), 30000);\n\n this.wss.on('close', () => clearInterval(this.heartbeatInterval));\n this.wss.on('connection', socket => this.handleConnection(socket));\n\n this.httpServer.listen(config.port);\n }\n\n private heartbeat(): void {\n let terminated = false;\n\n this.clients.forEach((client) => {\n if (!client.alive) {\n client.ws.terminate();\n\n this.clients.delete(client);\n\n terminated = true;\n\n return;\n }\n\n client.alive = false;\n\n client.ws.ping();\n });\n\n if (terminated) this.broadcastStatus();\n }\n\n private handleConnection(socket: WebSocket): void {\n const client: ConnectedClient = {\n id: 'unknown',\n app: 'unknown',\n ws: socket,\n channels: new Set(),\n alive: true,\n };\n\n this.clients.add(client);\n\n socket.on('error', () => {});\n\n socket.on('pong', () => {\n client.alive = true;\n });\n\n socket.on('message', (raw) => {\n let message: WebsocketClientMessage;\n\n try {\n message = JSON.parse(raw.toString());\n } catch { return; }\n\n this.handleMessage(client, socket, message);\n });\n\n socket.on('close', () => {\n this.clients.delete(client);\n\n this.addLog(client.id, WebsocketLogEvent.DISCONNECT, '');\n\n this.broadcastStatus();\n });\n }\n\n private handleMessage(client: ConnectedClient, socket: WebSocket, message: WebsocketClientMessage): void {\n switch (message.type) {\n case WebsocketClientEvent.IDENTIFY: {\n const existing = [...this.clients].find(c => c.id === message.id && c !== client);\n\n if (existing) {\n existing.ws.close(4000);\n\n this.clients.delete(existing);\n }\n\n client.id = message.id;\n client.app = message.app;\n\n this.addLog(message.id, WebsocketLogEvent.CONNECT, '');\n\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.SUBSCRIBE: {\n message.channels.forEach((channel) => {\n client.channels.add(channel);\n\n if (this.state.has(channel)) {\n const stateMessage: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: channel,\n data: this.state.get(channel),\n };\n\n socket.send(JSON.stringify(stateMessage));\n }\n });\n\n this.addLog(client.id, WebsocketLogEvent.SUBSCRIBE, message.channels.join(', '));\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.UNSUBSCRIBE: {\n message.channels.forEach(channel => client.channels.delete(channel));\n\n this.addLog(client.id, WebsocketLogEvent.UNSUBSCRIBE, message.channels.join(', '));\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.MESSAGE: {\n const outgoing: WebsocketHubMessage = {\n type: WebSocketHubEvent.MESSAGE,\n channel: message.channel,\n data: message.data,\n };\n\n this.broadcast(client, message.channel, outgoing);\n\n this.messageHandlers.forEach(h => h(message.channel, message.data));\n\n this.addLog(client.id, WebsocketLogEvent.MESSAGE, message.channel, message.data);\n this.broadcastStatus();\n break;\n }\n\n case WebsocketClientEvent.STATE: {\n this.state.set(message.channel, message.data);\n\n const outgoing: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: message.channel,\n data: message.data,\n };\n\n this.broadcast(client, message.channel, outgoing);\n\n this.messageHandlers.forEach(h => h(message.channel, message.data));\n\n this.addLog(client.id, WebsocketLogEvent.STATE, message.channel, message.data);\n\n this.broadcastStatus();\n break;\n }\n }\n }\n\n private addLog(sender: string, type: WebsocketLogEvent, channel: string, data?: unknown): void {\n this.logs.push({ timestamp: Date.now(), sender, type, channel, data });\n\n if (this.logs.length > 808) this.logs.shift();\n }\n\n private broadcast(sender: ConnectedClient, channel: string, message: WebsocketHubMessage): void {\n const payload = JSON.stringify(message);\n\n this.clients.forEach((client) => {\n if (client !== sender && client.channels.has(channel) && client.ws.readyState === WebSocket.OPEN) {\n client.ws.send(payload);\n }\n });\n }\n\n private broadcastStatus(): void {\n const status: WebsocketHubStatus = {\n clients: [...this.clients].map(client => ({ id: client.id, app: client.app, channels: [...client.channels] })),\n state: Object.fromEntries([...this.state].filter(([k]) => k !== WEBSOCKET_HUB_CHANNEL)),\n logs: this.logs,\n };\n\n this.state.set(WEBSOCKET_HUB_CHANNEL, status);\n\n const message: WebsocketHubMessage = {\n type: WebSocketHubEvent.STATE,\n channel: WEBSOCKET_HUB_CHANNEL,\n data: status,\n };\n\n const payload = JSON.stringify(message);\n\n this.clients.forEach((client) => {\n if (client.channels.has(WEBSOCKET_HUB_CHANNEL) && client.ws.readyState === WebSocket.OPEN) {\n client.ws.send(payload);\n }\n });\n }\n\n public onMessage(handler: (channel: string, data: unknown) => void): void { this.messageHandlers.push(handler); }\n\n public getState(channel: string): unknown { return this.state.get(channel); }\n\n public close(): void {\n clearInterval(this.heartbeatInterval);\n\n this.clients.forEach((client) => {\n if (client.ws.readyState === WebSocket.OPEN) {\n client.ws.close(1001, 'Hub shutting down');\n }\n });\n\n this.wss.close();\n this.httpServer.close();\n }\n}\n"],"names":["WebsocketHub","config","__publicField","http","req","res","WebSocketServer","socket","terminated","client","raw","message","WebsocketLogEvent","WebsocketClientEvent","existing","c","channel","stateMessage","WebSocketHubEvent","outgoing","h","sender","type","data","payload","WebSocket","status","k","WEBSOCKET_HUB_CHANNEL","handler"],"mappings":"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"}
|