@digo-labs/ws 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @digo-labs/ws
2
+
3
+ The low-level websocket client (`WebsocketClient`), server primitives (`@digo-labs/ws/server`), and the `digo-ws-hub` CLI for local development. Most apps use the typed wrapper in [`@digo-labs/services`](https://design.digo-labs.com/docs/guide/package-services) instead.
4
+
5
+ **📖 Full documentation:** https://design.digo-labs.com/docs/guide/package-ws
6
+
7
+ The Digo design-system guide is the single source of truth for this package — its full API, examples, and patterns live there. This README stays thin on purpose and points to it.
8
+
9
+ ```bash
10
+ npm install @digo-labs/ws
11
+ ```
package/dist/client.js CHANGED
@@ -1,80 +1,98 @@
1
- var c = Object.defineProperty;
2
- var h = (i, e, s) => e in i ? c(i, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : i[e] = s;
3
- var t = (i, e, s) => h(i, typeof e != "symbol" ? e + "" : e, s);
4
- import { WS_URL as a, WebsocketClientEvent as n } from "@digo-labs/common";
5
- class b {
6
- constructor(e) {
7
- t(this, "url");
8
- t(this, "app");
9
- t(this, "id");
10
- t(this, "reconnectEnabled");
11
- t(this, "reconnectInterval");
12
- t(this, "maxReconnectInterval");
13
- t(this, "reconnectDelay");
14
- t(this, "socket", null);
15
- t(this, "alive", !0);
16
- t(this, "listeners", /* @__PURE__ */ new Map());
17
- t(this, "subscribedChannels", /* @__PURE__ */ new Set());
18
- t(this, "openHandlers", []);
19
- t(this, "closeHandlers", []);
20
- t(this, "errorHandlers", []);
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();
22
- }
23
- open() {
24
- this.socket = new WebSocket(this.url), this.socket.onopen = () => {
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());
26
- }, this.socket.onmessage = (e) => {
27
- let s;
28
- try {
29
- s = JSON.parse(e.data);
30
- } catch {
31
- return;
32
- }
33
- const r = this.listeners.get(s.channel);
34
- r == null || r.forEach((o) => o(s.data));
35
- }, this.socket.onclose = (e) => {
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
- }
42
- }, this.socket.onerror = (e) => {
43
- this.errorHandlers.forEach((s) => s(e));
44
- };
45
- }
46
- sendToHub(e) {
47
- var s;
48
- ((s = this.socket) == null ? void 0 : s.readyState) === WebSocket.OPEN && this.socket.send(JSON.stringify(e));
49
- }
50
- send(e, s) {
51
- this.sendToHub({ type: n.MESSAGE, channel: e, data: s });
52
- }
53
- setState(e, s) {
54
- this.sendToHub({ type: n.STATE, channel: e, data: s });
55
- }
56
- subscribe(e, s) {
57
- this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(s), this.subscribedChannels.has(e) || (this.subscribedChannels.add(e), this.sendToHub({ type: n.SUBSCRIBE, channels: [e] }));
58
- }
59
- unsubscribe(e, s) {
60
- var r, o;
61
- s ? ((r = this.listeners.get(e)) == null || r.delete(s), ((o = this.listeners.get(e)) == null ? void 0 : o.size) === 0 && (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({ type: n.UNSUBSCRIBE, channels: [e] }))) : (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({ type: n.UNSUBSCRIBE, channels: [e] }));
62
- }
63
- onOpen(e) {
64
- this.openHandlers.push(e);
65
- }
66
- onClose(e) {
67
- this.closeHandlers.push(e);
68
- }
69
- onError(e) {
70
- this.errorHandlers.push(e);
71
- }
72
- close() {
73
- var e;
74
- this.alive = !1, (e = this.socket) == null || e.close();
75
- }
76
- }
77
- export {
78
- b as WebsocketClient
1
+ import { WS_URL as e, WebsocketClientEvent as t } from "@digo-labs/common";
2
+ //#region src/client.ts
3
+ var n = class {
4
+ url;
5
+ app;
6
+ id;
7
+ reconnectEnabled;
8
+ reconnectInterval;
9
+ maxReconnectInterval;
10
+ reconnectDelay;
11
+ socket = null;
12
+ alive = !0;
13
+ listeners = /* @__PURE__ */ new Map();
14
+ subscribedChannels = /* @__PURE__ */ new Set();
15
+ openHandlers = [];
16
+ closeHandlers = [];
17
+ errorHandlers = [];
18
+ constructor(t) {
19
+ this.url = t.url ?? e, this.app = t.app ?? "unknown", this.id = t.id ?? crypto.randomUUID(), this.reconnectEnabled = t.reconnect ?? !0, this.reconnectInterval = t.reconnectInterval ?? 1e3, this.maxReconnectInterval = t.maxReconnectInterval ?? 1e4, this.reconnectDelay = this.reconnectInterval, this.open();
20
+ }
21
+ open() {
22
+ this.socket = new WebSocket(this.url), this.socket.onopen = () => {
23
+ this.reconnectDelay = this.reconnectInterval, this.sendToHub({
24
+ type: t.IDENTIFY,
25
+ id: this.id,
26
+ app: this.app
27
+ }), this.subscribedChannels.size > 0 && this.sendToHub({
28
+ type: t.SUBSCRIBE,
29
+ channels: [...this.subscribedChannels]
30
+ }), this.openHandlers.forEach((e) => e());
31
+ }, this.socket.onmessage = (e) => {
32
+ let t;
33
+ try {
34
+ t = JSON.parse(e.data);
35
+ } catch {
36
+ return;
37
+ }
38
+ this.listeners.get(t.channel)?.forEach((e) => e(t.data));
39
+ }, this.socket.onclose = (e) => {
40
+ if (this.closeHandlers.forEach((e) => e()), e.code !== 4e3 && this.alive && this.reconnectEnabled) {
41
+ let e = this.reconnectDelay * (.5 + Math.random());
42
+ setTimeout(() => {
43
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval), this.open();
44
+ }, e);
45
+ }
46
+ }, this.socket.onerror = (e) => {
47
+ this.errorHandlers.forEach((t) => t(e));
48
+ };
49
+ }
50
+ sendToHub(e) {
51
+ this.socket?.readyState === WebSocket.OPEN && this.socket.send(JSON.stringify(e));
52
+ }
53
+ send(e, n) {
54
+ this.sendToHub({
55
+ type: t.MESSAGE,
56
+ channel: e,
57
+ data: n
58
+ });
59
+ }
60
+ setState(e, n) {
61
+ this.sendToHub({
62
+ type: t.STATE,
63
+ channel: e,
64
+ data: n
65
+ });
66
+ }
67
+ subscribe(e, n) {
68
+ this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(n), this.subscribedChannels.has(e) || (this.subscribedChannels.add(e), this.sendToHub({
69
+ type: t.SUBSCRIBE,
70
+ channels: [e]
71
+ }));
72
+ }
73
+ unsubscribe(e, n) {
74
+ n ? (this.listeners.get(e)?.delete(n), this.listeners.get(e)?.size === 0 && (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({
75
+ type: t.UNSUBSCRIBE,
76
+ channels: [e]
77
+ }))) : (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({
78
+ type: t.UNSUBSCRIBE,
79
+ channels: [e]
80
+ }));
81
+ }
82
+ onOpen(e) {
83
+ this.openHandlers.push(e);
84
+ }
85
+ onClose(e) {
86
+ this.closeHandlers.push(e);
87
+ }
88
+ onError(e) {
89
+ this.errorHandlers.push(e);
90
+ }
91
+ close() {
92
+ this.alive = !1, this.socket?.close();
93
+ }
79
94
  };
80
- //# sourceMappingURL=client.js.map
95
+ //#endregion
96
+ export { n as WebsocketClient };
97
+
98
+ //# sourceMappingURL=client.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"client.js","names":[],"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 ?? 'unknown';\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: channel, data: data });\n }\n\n public setState(channel: string, data?: unknown): void {\n this.sendToHub({ type: WebsocketClientEvent.STATE, channel: channel, data: 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"],"mappings":"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"}
package/dist/server.js CHANGED
@@ -1,142 +1,150 @@
1
- var S = Object.defineProperty;
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";
5
- import { WebSocketServer as b, WebSocket as l } from "ws";
6
- import { WebsocketLogEvent as r, WebsocketClientEvent as o, WebSocketHubEvent as d, WEBSOCKET_HUB_CHANNEL as c } from "@digo-labs/common";
7
- class g {
8
- constructor(t) {
9
- h(this, "httpServer");
10
- h(this, "wss");
11
- h(this, "clients", /* @__PURE__ */ new Set());
12
- h(this, "state", /* @__PURE__ */ new Map());
13
- h(this, "logs", []);
14
- h(this, "messageHandlers", []);
15
- h(this, "heartbeatInterval");
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);
23
- }
24
- heartbeat() {
25
- let t = !1;
26
- this.clients.forEach((e) => {
27
- if (!e.alive) {
28
- e.ws.terminate(), this.clients.delete(e), t = !0;
29
- return;
30
- }
31
- e.alive = !1, e.ws.ping();
32
- }), t && this.broadcastStatus();
33
- }
34
- handleConnection(t) {
35
- const e = {
36
- id: "unknown",
37
- app: "unknown",
38
- ws: t,
39
- channels: /* @__PURE__ */ new Set(),
40
- alive: !0
41
- };
42
- this.clients.add(e), t.on("error", () => {
43
- }), t.on("pong", () => {
44
- e.alive = !0;
45
- }), t.on("message", (a) => {
46
- let s;
47
- try {
48
- s = JSON.parse(a.toString());
49
- } catch {
50
- return;
51
- }
52
- this.handleMessage(e, t, s);
53
- }), t.on("close", () => {
54
- this.clients.delete(e), this.addLog(e.id, r.DISCONNECT, ""), this.broadcastStatus();
55
- });
56
- }
57
- handleMessage(t, e, a) {
58
- switch (a.type) {
59
- case o.IDENTIFY: {
60
- const s = [...this.clients].find((n) => n.id === a.id && n !== t);
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();
62
- break;
63
- }
64
- case o.SUBSCRIBE: {
65
- a.channels.forEach((s) => {
66
- if (t.channels.add(s), this.state.has(s)) {
67
- const n = {
68
- type: d.STATE,
69
- channel: s,
70
- data: this.state.get(s)
71
- };
72
- e.send(JSON.stringify(n));
73
- }
74
- }), this.addLog(t.id, r.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
75
- break;
76
- }
77
- case o.UNSUBSCRIBE: {
78
- a.channels.forEach((s) => t.channels.delete(s)), this.addLog(t.id, r.UNSUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
79
- break;
80
- }
81
- case o.MESSAGE: {
82
- const s = {
83
- type: d.MESSAGE,
84
- channel: a.channel,
85
- data: a.data
86
- };
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();
88
- break;
89
- }
90
- case o.STATE: {
91
- this.state.set(a.channel, a.data);
92
- const s = {
93
- type: d.STATE,
94
- channel: a.channel,
95
- data: a.data
96
- };
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();
98
- break;
99
- }
100
- }
101
- }
102
- addLog(t, e, a, s) {
103
- this.logs.push({ timestamp: Date.now(), sender: t, type: e, channel: a, data: s }), this.logs.length > 808 && this.logs.shift();
104
- }
105
- broadcast(t, e, a) {
106
- const s = JSON.stringify(a);
107
- this.clients.forEach((n) => {
108
- n !== t && n.channels.has(e) && n.ws.readyState === l.OPEN && n.ws.send(s);
109
- });
110
- }
111
- broadcastStatus() {
112
- const t = {
113
- clients: [...this.clients].map((s) => ({ id: s.id, app: s.app, channels: [...s.channels] })),
114
- state: Object.fromEntries([...this.state].filter(([s]) => s !== c)),
115
- logs: this.logs
116
- };
117
- this.state.set(c, t);
118
- const e = {
119
- type: d.STATE,
120
- channel: c,
121
- data: t
122
- }, a = JSON.stringify(e);
123
- this.clients.forEach((s) => {
124
- s.channels.has(c) && s.ws.readyState === l.OPEN && s.ws.send(a);
125
- });
126
- }
127
- onMessage(t) {
128
- this.messageHandlers.push(t);
129
- }
130
- getState(t) {
131
- return this.state.get(t);
132
- }
133
- close() {
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();
137
- }
138
- }
139
- export {
140
- g as WebsocketHub
1
+ import { WEBSOCKET_HUB_CHANNEL as e, WebSocketHubEvent as t, WebsocketClientEvent as n, WebsocketLogEvent as r } from "@digo-labs/common";
2
+ import i from "node:http";
3
+ import { WebSocket as a, WebSocketServer as o } from "ws";
4
+ //#region src/server.ts
5
+ var s = class {
6
+ httpServer;
7
+ wss;
8
+ clients = /* @__PURE__ */ new Set();
9
+ state = /* @__PURE__ */ new Map();
10
+ logs = [];
11
+ messageHandlers = [];
12
+ heartbeatInterval;
13
+ constructor(e) {
14
+ this.httpServer = i.createServer((e, t) => {
15
+ if (e.method === "GET" && e.url === "/health") {
16
+ t.writeHead(200, { "Content-Type": "application/json" }), t.end(JSON.stringify({
17
+ ok: !0,
18
+ clients: this.clients.size
19
+ }));
20
+ return;
21
+ }
22
+ t.writeHead(404), t.end();
23
+ }), this.wss = new o({ 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(e.port);
24
+ }
25
+ heartbeat() {
26
+ let e = !1;
27
+ this.clients.forEach((t) => {
28
+ if (!t.alive) {
29
+ t.ws.terminate(), this.clients.delete(t), e = !0;
30
+ return;
31
+ }
32
+ t.alive = !1, t.ws.ping();
33
+ }), e && this.broadcastStatus();
34
+ }
35
+ handleConnection(e) {
36
+ let t = {
37
+ id: "unknown",
38
+ app: "unknown",
39
+ ws: e,
40
+ channels: /* @__PURE__ */ new Set(),
41
+ alive: !0
42
+ };
43
+ this.clients.add(t), e.on("error", () => {}), e.on("pong", () => {
44
+ t.alive = !0;
45
+ }), e.on("message", (n) => {
46
+ let r;
47
+ try {
48
+ r = JSON.parse(n.toString());
49
+ } catch {
50
+ return;
51
+ }
52
+ this.handleMessage(t, e, r);
53
+ }), e.on("close", () => {
54
+ this.clients.delete(t), this.addLog(t.id, r.DISCONNECT, ""), this.broadcastStatus();
55
+ });
56
+ }
57
+ handleMessage(e, i, a) {
58
+ switch (a.type) {
59
+ case n.IDENTIFY: {
60
+ let t = [...this.clients].find((t) => t.id === a.id && t !== e);
61
+ t && (t.ws.close(4e3), this.clients.delete(t)), e.id = a.id, e.app = a.app, this.addLog(a.id, r.CONNECT, ""), this.broadcastStatus();
62
+ break;
63
+ }
64
+ case n.SUBSCRIBE:
65
+ a.channels.forEach((n) => {
66
+ if (e.channels.add(n), this.state.has(n)) {
67
+ let e = {
68
+ type: t.STATE,
69
+ channel: n,
70
+ data: this.state.get(n)
71
+ };
72
+ i.send(JSON.stringify(e));
73
+ }
74
+ }), this.addLog(e.id, r.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
75
+ break;
76
+ case n.UNSUBSCRIBE:
77
+ a.channels.forEach((t) => e.channels.delete(t)), this.addLog(e.id, r.UNSUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
78
+ break;
79
+ case n.MESSAGE: {
80
+ let n = {
81
+ type: t.MESSAGE,
82
+ channel: a.channel,
83
+ data: a.data
84
+ };
85
+ this.broadcast(e, a.channel, n), this.messageHandlers.forEach((e) => e(a.channel, a.data)), this.addLog(e.id, r.MESSAGE, a.channel, a.data), this.broadcastStatus();
86
+ break;
87
+ }
88
+ case n.STATE: {
89
+ this.state.set(a.channel, a.data);
90
+ let n = {
91
+ type: t.STATE,
92
+ channel: a.channel,
93
+ data: a.data
94
+ };
95
+ this.broadcast(e, a.channel, n), this.messageHandlers.forEach((e) => e(a.channel, a.data)), this.addLog(e.id, r.STATE, a.channel, a.data), this.broadcastStatus();
96
+ break;
97
+ }
98
+ }
99
+ }
100
+ addLog(e, t, n, r) {
101
+ this.logs.push({
102
+ timestamp: Date.now(),
103
+ sender: e,
104
+ type: t,
105
+ channel: n,
106
+ data: r
107
+ }), this.logs.length > 808 && this.logs.shift();
108
+ }
109
+ broadcast(e, t, n) {
110
+ let r = JSON.stringify(n);
111
+ this.clients.forEach((n) => {
112
+ n !== e && n.channels.has(t) && n.ws.readyState === a.OPEN && n.ws.send(r);
113
+ });
114
+ }
115
+ broadcastStatus() {
116
+ let n = {
117
+ clients: [...this.clients].map((e) => ({
118
+ id: e.id,
119
+ app: e.app,
120
+ channels: [...e.channels]
121
+ })),
122
+ state: Object.fromEntries([...this.state].filter(([t]) => t !== e)),
123
+ logs: this.logs
124
+ };
125
+ this.state.set(e, n);
126
+ let r = {
127
+ type: t.STATE,
128
+ channel: e,
129
+ data: n
130
+ }, i = JSON.stringify(r);
131
+ this.clients.forEach((t) => {
132
+ t.channels.has(e) && t.ws.readyState === a.OPEN && t.ws.send(i);
133
+ });
134
+ }
135
+ onMessage(e) {
136
+ this.messageHandlers.push(e);
137
+ }
138
+ getState(e) {
139
+ return this.state.get(e);
140
+ }
141
+ close() {
142
+ clearInterval(this.heartbeatInterval), this.clients.forEach((e) => {
143
+ e.ws.readyState === a.OPEN && e.ws.close(1001, "Hub shutting down");
144
+ }), this.wss.close(), this.httpServer.close();
145
+ }
141
146
  };
142
- //# sourceMappingURL=server.js.map
147
+ //#endregion
148
+ export { s as WebsocketHub };
149
+
150
+ //# sourceMappingURL=server.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"server.js","names":[],"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: sender, type: type, channel: channel, data: 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"],"mappings":"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"}