@digo-labs/ws 0.1.3 → 0.1.6

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 CHANGED
@@ -1,6 +1,7 @@
1
1
  import { WebsocketConnectionConfig, WebsocketListener } from '@digo-labs/common';
2
2
  export declare class WebsocketClient {
3
3
  private url;
4
+ private app;
4
5
  private id;
5
6
  private reconnectEnabled;
6
7
  private reconnectInterval;
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 d {
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 = `ws://${e.host}:${e.port}`, this.id = e.id, this.reconnectEnabled = e.reconnect ?? !0, this.reconnectInterval = e.reconnectInterval ?? 1e3, this.maxReconnectInterval = e.maxReconnectInterval ?? 1e4, this.reconnectDelay = this.reconnectInterval, this.open();
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 && setTimeout(() => {
36
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval), this.open();
37
- }, this.reconnectDelay);
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
- d as WebsocketClient
78
+ b as WebsocketClient
75
79
  };
76
80
  //# sourceMappingURL=client.js.map
@@ -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 = `ws://${properties.host}:${properties.port}`;\n this.id = properties.id;\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 });\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 setTimeout(() => {\n this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval);\n this.open();\n }, this.reconnectDelay);\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","WebsocketClientEvent","h","event","message","channelListeners","listener","error","_a","channel","data","_b","handler"],"mappings":";;;;AAEO,MAAMA,EAAgB;AAAA,EAgB3B,YAAYC,GAAuC;AAf3C,IAAAC,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AACA,IAAAA,EAAA;AAEA,IAAAA,EAAA,gBAAkC;AAClC,IAAAA,EAAA,eAAQ;AACR,IAAAA,EAAA,uCAAgB,IAAA;AAChB,IAAAA,EAAA,gDAAyB,IAAA;AACzB,IAAAA,EAAA,sBAAgC,CAAA;AAChC,IAAAA,EAAA,uBAAgC,CAAA;AAChC,IAAAA,EAAA,uBAA4C,CAAA;AAGlD,SAAK,MAAuB,QAAQD,EAAW,IAAI,IAAIA,EAAW,IAAI,IACtE,KAAK,KAAuBA,EAAW,IACvC,KAAK,mBAAuBA,EAAW,aAAa,IACpD,KAAK,oBAAuBA,EAAW,qBAAqB,KAC5D,KAAK,uBAAuBA,EAAW,wBAAwB,KAC/D,KAAK,iBAAuB,KAAK,mBAEjC,KAAK,KAAA;AAAA,EACP;AAAA,EAEQ,OAAa;AACnB,SAAK,SAAS,IAAI,UAAU,KAAK,GAAG,GAEpC,KAAK,OAAO,SAAS,MAAM;AACzB,WAAK,iBAAiB,KAAK,mBAE3B,KAAK,UAAU,EAAE,MAAME,EAAqB,UAAU,IAAI,KAAK,IAAI,GAE/D,KAAK,mBAAmB,OAAO,KACjC,KAAK,UAAU,EAAE,MAAMA,EAAqB,WAAW,UAAU,CAAC,GAAG,KAAK,kBAAkB,GAAG,GAGjG,KAAK,aAAa,QAAQ,CAAAC,MAAKA,EAAA,CAAG;AAAA,IACpC,GAEA,KAAK,OAAO,YAAY,CAACC,MAAU;AACjC,UAAIC;AAEJ,UAAI;AACF,QAAAA,IAAU,KAAK,MAAMD,EAAM,IAAc;AAAA,MAC3C,QAAQ;AAAE;AAAA,MAAQ;AAElB,YAAME,IAAmB,KAAK,UAAU,IAAID,EAAQ,OAAO;AAE3D,MAAAC,KAAA,QAAAA,EAAkB,QAAQ,CAAAC,MAAYA,EAASF,EAAQ,IAAI;AAAA,IAC7D,GAEA,KAAK,OAAO,UAAU,CAACD,MAAU;AAG/B,MAFA,KAAK,cAAc,QAAQ,CAAAD,MAAKA,EAAA,CAAG,GAE/BC,EAAM,SAAS,OAEf,KAAK,SAAS,KAAK,oBACrB,WAAW,MAAM;AACf,aAAK,iBAAiB,KAAK,IAAI,KAAK,iBAAiB,GAAG,KAAK,oBAAoB,GACjF,KAAK,KAAA;AAAA,MACP,GAAG,KAAK,cAAc;AAAA,IAE1B,GAEA,KAAK,OAAO,UAAU,CAACI,MAAU;AAC/B,WAAK,cAAc,QAAQ,CAAAL,MAAKA,EAAEK,CAAK,CAAC;AAAA,IAC1C;AAAA,EACF;AAAA,EAEQ,UAAUH,GAAuC;;AACvD,MAAII,IAAA,KAAK,WAAL,gBAAAA,EAAa,gBAAe,UAAU,QACxC,KAAK,OAAO,KAAK,KAAK,UAAUJ,CAAO,CAAC;AAAA,EAE5C;AAAA,EAEO,KAAKK,GAAiBC,GAAsB;AACjD,SAAK,UAAU,EAAE,MAAMT,EAAqB,SAAS,SAAAQ,GAAS,MAAAC,GAAM;AAAA,EACtE;AAAA,EAEO,SAASD,GAAiBC,GAAsB;AACrD,SAAK,UAAU,EAAE,MAAMT,EAAqB,OAAO,SAAAQ,GAAS,MAAAC,GAAM;AAAA,EACpE;AAAA,EAEO,UAAUD,GAAiBH,GAAmC;AACnE,IAAK,KAAK,UAAU,IAAIG,CAAO,KAAG,KAAK,UAAU,IAAIA,GAAS,oBAAI,IAAA,CAAK,GAEvE,KAAK,UAAU,IAAIA,CAAO,EAAG,IAAIH,CAAQ,GAEpC,KAAK,mBAAmB,IAAIG,CAAO,MACtC,KAAK,mBAAmB,IAAIA,CAAO,GACnC,KAAK,UAAU,EAAE,MAAMR,EAAqB,WAAW,UAAU,CAACQ,CAAO,GAAG;AAAA,EAEhF;AAAA,EAEO,YAAYA,GAAiBH,GAAoC;;AACtE,IAAIA,MACFE,IAAA,KAAK,UAAU,IAAIC,CAAO,MAA1B,QAAAD,EAA6B,OAAOF,MAEhCK,IAAA,KAAK,UAAU,IAAIF,CAAO,MAA1B,gBAAAE,EAA6B,UAAS,MACxC,KAAK,UAAU,OAAOF,CAAO,GAC7B,KAAK,mBAAmB,OAAOA,CAAO,GACtC,KAAK,UAAU,EAAE,MAAMR,EAAqB,aAAa,UAAU,CAACQ,CAAO,GAAG,OAGhF,KAAK,UAAU,OAAOA,CAAO,GAC7B,KAAK,mBAAmB,OAAOA,CAAO,GACtC,KAAK,UAAU,EAAE,MAAMR,EAAqB,aAAa,UAAU,CAACQ,CAAO,GAAG;AAAA,EAElF;AAAA,EAEO,OAAOG,GAA2B;AAAE,SAAK,aAAa,KAAKA,CAAO;AAAA,EAAG;AAAA,EAErE,QAAQA,GAA2B;AAAE,SAAK,cAAc,KAAKA,CAAO;AAAA,EAAG;AAAA,EAEvE,QAAQA,GAAuC;AAAE,SAAK,cAAc,KAAKA,CAAO;AAAA,EAAG;AAAA,EAEnF,QAAc;;AACnB,SAAK,QAAQ,KACbJ,IAAA,KAAK,WAAL,QAAAA,EAAa;AAAA,EACf;AACF;"}
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
@@ -1,5 +1,6 @@
1
1
  import { WebsocketsHubConfig } from '@digo-labs/common';
2
2
  export declare class WebsocketHub {
3
+ private httpServer;
3
4
  private wss;
4
5
  private clients;
5
6
  private state;
package/dist/server.js CHANGED
@@ -1,17 +1,25 @@
1
1
  var S = Object.defineProperty;
2
- var E = (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) => E(i, typeof t != "symbol" ? t + "" : 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 o, WebsocketClientEvent as r, WebSocketHubEvent as d, WEBSOCKET_HUB_CHANNEL as c } from "@digo-labs/common";
6
- class w {
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.wss = new b({ port: t.port }), this.heartbeatInterval = setInterval(() => this.heartbeat(), 3e4), this.wss.on("close", () => clearInterval(this.heartbeatInterval)), this.wss.on("connection", (e) => this.handleConnection(e));
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, o.DISCONNECT, ""), this.broadcastStatus();
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 r.IDENTIFY: {
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, o.CONNECT, ""), this.broadcastStatus();
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 r.SUBSCRIBE: {
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, o.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
74
+ }), this.addLog(t.id, r.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
66
75
  break;
67
76
  }
68
- case r.UNSUBSCRIBE: {
69
- a.channels.forEach((s) => t.channels.delete(s)), this.addLog(t.id, o.UNSUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
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 r.MESSAGE: {
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, o.MESSAGE, a.channel, a.data), this.broadcastStatus();
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 r.STATE: {
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, o.STATE, a.channel, a.data), this.broadcastStatus();
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.wss.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();
126
137
  }
127
138
  }
128
139
  export {
129
- w as WebsocketHub
140
+ g as WebsocketHub
130
141
  };
131
142
  //# sourceMappingURL=server.js.map
@@ -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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"}