@digo-labs/ws 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -7,21 +7,35 @@ export declare class WebsocketClient {
7
7
  private reconnectInterval;
8
8
  private maxReconnectInterval;
9
9
  private reconnectDelay;
10
+ private getToken;
10
11
  private socket;
11
12
  private alive;
13
+ private isIdentified;
14
+ private refreshTimer;
12
15
  private listeners;
13
16
  private subscribedChannels;
17
+ private deniedChannels;
18
+ private pending;
14
19
  private openHandlers;
20
+ private readyHandlers;
15
21
  private closeHandlers;
16
22
  private errorHandlers;
17
23
  constructor(properties: WebsocketConnectionConfig);
18
24
  private open;
25
+ private identify;
26
+ private handleIdentified;
27
+ private stopRefresh;
28
+ private scheduleReconnect;
29
+ private sendRaw;
19
30
  private sendToHub;
20
31
  send(channel: string, data?: unknown): void;
21
32
  setState(channel: string, data?: unknown): void;
22
33
  subscribe(channel: string, listener: WebsocketListener): void;
23
34
  unsubscribe(channel: string, listener?: WebsocketListener): void;
35
+ isLive(channel: string): boolean;
24
36
  onOpen(handler: () => void): void;
37
+ onReady(handler: () => void): void;
38
+ offReady(handler: () => void): void;
25
39
  onClose(handler: () => void): void;
26
40
  onError(handler: (error: Event) => void): void;
27
41
  close(): void;
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
- import { WS_URL as e, WebsocketClientEvent as t } from "@digo-labs/common";
1
+ import { WS_URL as e, WebSocketHubEvent as t, WebsocketClientEvent as n } from "@digo-labs/common";
2
2
  //#region src/client.ts
3
- var n = class {
3
+ var r = 4e3, i = 4401, a = 6e5, o = class {
4
4
  url;
5
5
  app;
6
6
  id;
@@ -8,80 +8,134 @@ var n = class {
8
8
  reconnectInterval;
9
9
  maxReconnectInterval;
10
10
  reconnectDelay;
11
+ getToken;
11
12
  socket = null;
12
13
  alive = !0;
14
+ isIdentified = !1;
15
+ refreshTimer = null;
13
16
  listeners = /* @__PURE__ */ new Map();
14
17
  subscribedChannels = /* @__PURE__ */ new Set();
18
+ deniedChannels = /* @__PURE__ */ new Set();
19
+ pending = [];
15
20
  openHandlers = [];
21
+ readyHandlers = [];
16
22
  closeHandlers = [];
17
23
  errorHandlers = [];
18
24
  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();
25
+ 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.getToken = t.getToken ?? null, this.open();
20
26
  }
21
27
  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());
28
+ this.socket = new WebSocket(this.url), this.isIdentified = !1, this.socket.onopen = () => {
29
+ this.reconnectDelay = this.reconnectInterval, this.identify(), this.openHandlers.forEach((e) => e());
31
30
  }, this.socket.onmessage = (e) => {
32
- let t;
31
+ let n;
33
32
  try {
34
- t = JSON.parse(e.data);
33
+ n = JSON.parse(e.data);
35
34
  } catch {
36
35
  return;
37
36
  }
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);
37
+ if (n.type === t.IDENTIFIED) {
38
+ this.handleIdentified();
39
+ return;
40
+ }
41
+ if (n.type === t.DENIED) {
42
+ n.channel && this.deniedChannels.add(n.channel);
43
+ return;
45
44
  }
45
+ n.channel && this.listeners.get(n.channel)?.forEach((e) => e(n.data));
46
+ }, this.socket.onclose = (e) => {
47
+ this.isIdentified = !1, this.pending = [], this.stopRefresh(), this.closeHandlers.forEach((e) => e()), e.code !== r && this.alive && this.reconnectEnabled && this.scheduleReconnect(e.code === i);
46
48
  }, this.socket.onerror = (e) => {
47
49
  this.errorHandlers.forEach((t) => t(e));
48
50
  };
49
51
  }
50
- sendToHub(e) {
52
+ async identify() {
53
+ if (!this.getToken) {
54
+ this.sendRaw({
55
+ type: n.IDENTIFY,
56
+ id: this.id,
57
+ app: this.app
58
+ }), this.handleIdentified();
59
+ return;
60
+ }
61
+ let e = await this.getToken().catch(() => null);
62
+ if (!e) {
63
+ this.socket?.close();
64
+ return;
65
+ }
66
+ this.sendRaw({
67
+ type: n.IDENTIFY,
68
+ id: this.id,
69
+ app: this.app,
70
+ token: e
71
+ });
72
+ }
73
+ handleIdentified() {
74
+ this.isIdentified || (this.isIdentified = !0, this.deniedChannels.clear(), this.subscribedChannels.size > 0 && this.sendRaw({
75
+ type: n.SUBSCRIBE,
76
+ channels: [...this.subscribedChannels]
77
+ }), this.pending.splice(0).forEach((e) => this.sendRaw(e)), this.getToken && !this.refreshTimer && (this.refreshTimer = setInterval(() => void this.identify(), a)), this.readyHandlers.forEach((e) => e()));
78
+ }
79
+ stopRefresh() {
80
+ this.refreshTimer && clearInterval(this.refreshTimer), this.refreshTimer = null;
81
+ }
82
+ scheduleReconnect(e) {
83
+ let t = (e ? this.maxReconnectInterval : this.reconnectDelay) * (.5 + Math.random());
84
+ setTimeout(() => {
85
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectInterval), this.open();
86
+ }, t);
87
+ }
88
+ sendRaw(e) {
51
89
  this.socket?.readyState === WebSocket.OPEN && this.socket.send(JSON.stringify(e));
52
90
  }
53
- send(e, n) {
91
+ sendToHub(e) {
92
+ if (this.isIdentified) {
93
+ this.sendRaw(e);
94
+ return;
95
+ }
96
+ (e.type === n.MESSAGE || e.type === n.STATE) && this.socket?.readyState === WebSocket.OPEN && this.pending.push(e);
97
+ }
98
+ send(e, t) {
54
99
  this.sendToHub({
55
- type: t.MESSAGE,
100
+ type: n.MESSAGE,
56
101
  channel: e,
57
- data: n
102
+ data: t
58
103
  });
59
104
  }
60
- setState(e, n) {
105
+ setState(e, t) {
61
106
  this.sendToHub({
62
- type: t.STATE,
107
+ type: n.STATE,
63
108
  channel: e,
64
- data: n
109
+ data: t
65
110
  });
66
111
  }
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,
112
+ subscribe(e, t) {
113
+ this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), this.subscribedChannels.has(e) || (this.subscribedChannels.add(e), this.deniedChannels.delete(e), this.sendToHub({
114
+ type: n.SUBSCRIBE,
70
115
  channels: [e]
71
116
  }));
72
117
  }
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,
118
+ unsubscribe(e, t) {
119
+ t ? (this.listeners.get(e)?.delete(t), this.listeners.get(e)?.size === 0 && (this.listeners.delete(e), this.subscribedChannels.delete(e), this.deniedChannels.delete(e), this.sendToHub({
120
+ type: n.UNSUBSCRIBE,
76
121
  channels: [e]
77
- }))) : (this.listeners.delete(e), this.subscribedChannels.delete(e), this.sendToHub({
78
- type: t.UNSUBSCRIBE,
122
+ }))) : (this.listeners.delete(e), this.subscribedChannels.delete(e), this.deniedChannels.delete(e), this.sendToHub({
123
+ type: n.UNSUBSCRIBE,
79
124
  channels: [e]
80
125
  }));
81
126
  }
127
+ isLive(e) {
128
+ return this.isIdentified && this.subscribedChannels.has(e) && !this.deniedChannels.has(e);
129
+ }
82
130
  onOpen(e) {
83
131
  this.openHandlers.push(e);
84
132
  }
133
+ onReady(e) {
134
+ this.readyHandlers.push(e);
135
+ }
136
+ offReady(e) {
137
+ this.readyHandlers = this.readyHandlers.filter((t) => t !== e);
138
+ }
85
139
  onClose(e) {
86
140
  this.closeHandlers.push(e);
87
141
  }
@@ -89,10 +143,10 @@ var n = class {
89
143
  this.errorHandlers.push(e);
90
144
  }
91
145
  close() {
92
- this.alive = !1, this.socket?.close();
146
+ this.alive = !1, this.isIdentified = !1, this.stopRefresh(), this.socket?.close();
93
147
  }
94
148
  };
95
149
  //#endregion
96
- export { n as WebsocketClient };
150
+ export { o as WebsocketClient };
97
151
 
98
152
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["import { WebsocketConnectionConfig, WebsocketClientEvent, WebsocketClientMessage, WebSocketHubEvent, WebsocketHubMessage, WebsocketListener, WS_URL } from '@digo-labs/common';\n\nconst KICKED_CODE = 4000;\nconst DENIED_CODE = 4401;\nconst REFRESH_INTERVAL = 600_000;\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 private getToken: (() => Promise<string | null>) | null;\n\n private socket: WebSocket | null = null;\n private alive = true;\n private isIdentified = false;\n private refreshTimer: ReturnType<typeof setInterval> | null = null;\n private listeners = new Map<string, Set<WebsocketListener>>();\n private subscribedChannels = new Set<string>();\n private deniedChannels = new Set<string>();\n private pending: WebsocketClientMessage[] = [];\n private openHandlers: (() => void)[] = [];\n private readyHandlers: (() => 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 this.getToken = properties.getToken ?? null;\n\n this.open();\n }\n\n private open(): void {\n this.socket = new WebSocket(this.url);\n this.isIdentified = false;\n\n this.socket.onopen = () => {\n this.reconnectDelay = this.reconnectInterval;\n\n void this.identify();\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 if (message.type === WebSocketHubEvent.IDENTIFIED) {\n this.handleIdentified();\n\n return;\n }\n\n if (message.type === WebSocketHubEvent.DENIED) {\n if (message.channel) this.deniedChannels.add(message.channel);\n\n return;\n }\n\n if (!message.channel) return;\n\n this.listeners.get(message.channel)?.forEach(listener => listener(message.data));\n };\n\n this.socket.onclose = (event) => {\n this.isIdentified = false;\n this.pending = [];\n\n this.stopRefresh();\n\n this.closeHandlers.forEach(h => h());\n\n if (event.code === KICKED_CODE) return;\n\n if (this.alive && this.reconnectEnabled) this.scheduleReconnect(event.code === DENIED_CODE);\n };\n\n this.socket.onerror = (error) => {\n this.errorHandlers.forEach(h => h(error));\n };\n }\n\n private async identify(): Promise<void> {\n if (!this.getToken) {\n this.sendRaw({ type: WebsocketClientEvent.IDENTIFY, id: this.id, app: this.app });\n this.handleIdentified();\n\n return;\n }\n\n const token = await this.getToken().catch(() => null);\n\n if (!token) {\n this.socket?.close();\n\n return;\n }\n\n this.sendRaw({ type: WebsocketClientEvent.IDENTIFY, id: this.id, app: this.app, token: token });\n }\n\n private handleIdentified(): void {\n if (this.isIdentified) return;\n\n this.isIdentified = true;\n this.deniedChannels.clear();\n\n if (this.subscribedChannels.size > 0) this.sendRaw({ type: WebsocketClientEvent.SUBSCRIBE, channels: [...this.subscribedChannels] });\n\n this.pending.splice(0).forEach(message => this.sendRaw(message));\n\n if (this.getToken && !this.refreshTimer) {\n this.refreshTimer = setInterval(() => void this.identify(), REFRESH_INTERVAL);\n }\n\n this.readyHandlers.forEach(h => h());\n }\n\n private stopRefresh(): void {\n if (this.refreshTimer) clearInterval(this.refreshTimer);\n\n this.refreshTimer = null;\n }\n\n private scheduleReconnect(isSlow: boolean): void {\n const jitteredDelay = (isSlow ? this.maxReconnectInterval : 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 private sendRaw(message: WebsocketClientMessage): void {\n if (this.socket?.readyState === WebSocket.OPEN) {\n this.socket.send(JSON.stringify(message));\n }\n }\n\n private sendToHub(message: WebsocketClientMessage): void {\n if (this.isIdentified) {\n this.sendRaw(message);\n\n return;\n }\n\n const isHeld = message.type === WebsocketClientEvent.MESSAGE || message.type === WebsocketClientEvent.STATE;\n\n if (isHeld && this.socket?.readyState === WebSocket.OPEN) this.pending.push(message);\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.deniedChannels.delete(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.deniedChannels.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.deniedChannels.delete(channel);\n this.sendToHub({ type: WebsocketClientEvent.UNSUBSCRIBE, channels: [channel] });\n }\n }\n\n public isLive(channel: string): boolean {\n return this.isIdentified && this.subscribedChannels.has(channel) && !this.deniedChannels.has(channel);\n }\n\n public onOpen(handler: () => void): void { this.openHandlers.push(handler); }\n\n public onReady(handler: () => void): void { this.readyHandlers.push(handler); }\n\n public offReady(handler: () => void): void { this.readyHandlers = this.readyHandlers.filter(h => h !== 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.isIdentified = false;\n\n this.stopRefresh();\n\n this.socket?.close();\n }\n}\n"],"mappings":"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"}
package/dist/server.d.ts CHANGED
@@ -2,18 +2,34 @@ import { WebsocketsHubConfig } from '@digo-labs/common';
2
2
  export declare class WebsocketHub {
3
3
  private httpServer;
4
4
  private wss;
5
+ private config;
5
6
  private clients;
6
7
  private state;
7
8
  private logs;
8
9
  private messageHandlers;
9
10
  private heartbeatInterval;
11
+ private statusTimer;
12
+ private statusDirty;
10
13
  constructor(config: WebsocketsHubConfig);
11
14
  private heartbeat;
12
15
  private handleConnection;
16
+ private parseMessage;
13
17
  private handleMessage;
14
- private addLog;
18
+ private identify;
19
+ private identifyWithToken;
20
+ private canSubscribe;
21
+ private canPublish;
22
+ private channelKey;
23
+ private namespacedKey;
24
+ private storeState;
25
+ private readState;
15
26
  private broadcast;
27
+ private deny;
28
+ private isOverRate;
29
+ private rawSize;
30
+ private addLog;
16
31
  private broadcastStatus;
32
+ private sendStatus;
17
33
  onMessage(handler: (channel: string, data: unknown) => void): void;
18
34
  getState(channel: string): unknown;
19
35
  close(): void;
package/dist/server.js CHANGED
@@ -1,17 +1,20 @@
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";
1
+ import { CHANNEL_NAMESPACE_PREFIX as e, RealtimeHelpers as t, WEBSOCKET_HUB_CHANNEL as n, WebSocketHubEvent as r, WebsocketClientEvent as i, WebsocketLogEvent as a, tryCatch as o, websocketClientMessageSchema as s } from "@digo-labs/common";
2
+ import c from "node:http";
3
+ import { WebSocket as l, WebSocketServer as u } from "ws";
4
4
  //#region src/server.ts
5
- var s = class {
5
+ var d = 1024 * 1024, f = 64 * 1024, p = 1e3, m = 20, h = 1e3, g = 4e3, _ = 4401, v = "/", y = class {
6
6
  httpServer;
7
7
  wss;
8
+ config;
8
9
  clients = /* @__PURE__ */ new Set();
9
10
  state = /* @__PURE__ */ new Map();
10
11
  logs = [];
11
12
  messageHandlers = [];
12
13
  heartbeatInterval;
14
+ statusTimer = null;
15
+ statusDirty = !1;
13
16
  constructor(e) {
14
- this.httpServer = i.createServer((e, t) => {
17
+ this.config = e, this.httpServer = c.createServer((e, t) => {
15
18
  if (e.method === "GET" && e.url === "/health") {
16
19
  t.writeHead(200, { "Content-Type": "application/json" }), t.end(JSON.stringify({
17
20
  ok: !0,
@@ -20,7 +23,10 @@ var s = class {
20
23
  return;
21
24
  }
22
25
  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);
26
+ }), this.wss = new u({
27
+ server: this.httpServer,
28
+ maxPayload: d
29
+ }), 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
30
  }
25
31
  heartbeat() {
26
32
  let e = !1;
@@ -30,6 +36,10 @@ var s = class {
30
36
  return;
31
37
  }
32
38
  t.alive = !1, t.ws.ping();
39
+ });
40
+ let t = Date.now();
41
+ this.state.forEach((e, n) => {
42
+ e.expiresAt !== null && e.expiresAt <= t && this.state.delete(n);
33
43
  }), e && this.broadcastStatus();
34
44
  }
35
45
  handleConnection(e) {
@@ -38,113 +48,215 @@ var s = class {
38
48
  app: "unknown",
39
49
  ws: e,
40
50
  channels: /* @__PURE__ */ new Set(),
41
- alive: !0
51
+ alive: !0,
52
+ token: null,
53
+ windowStart: Date.now(),
54
+ messages: 0
42
55
  };
43
56
  this.clients.add(t), e.on("error", () => {}), e.on("pong", () => {
44
57
  t.alive = !0;
45
58
  }), 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);
59
+ if (t.token && (this.rawSize(n) > f || this.isOverRate(t))) return;
60
+ let r = this.parseMessage(n);
61
+ r && o(this.handleMessage(t, e, r));
53
62
  }), e.on("close", () => {
54
- this.clients.delete(t), this.addLog(t.id, r.DISCONNECT, ""), this.broadcastStatus();
63
+ this.clients.delete(t), this.addLog(t, a.DISCONNECT, ""), this.broadcastStatus();
55
64
  });
56
65
  }
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();
66
+ parseMessage(e) {
67
+ let t;
68
+ try {
69
+ t = JSON.parse(e.toString());
70
+ } catch {
71
+ return null;
72
+ }
73
+ let n = s.safeParse(t);
74
+ return n.success ? n.data : null;
75
+ }
76
+ async handleMessage(e, n, o) {
77
+ switch (o.type) {
78
+ case i.IDENTIFY:
79
+ if (o.token) {
80
+ await this.identifyWithToken(e, n, o.id, o.token);
81
+ break;
82
+ }
83
+ this.identify(e, o.id, o.app);
62
84
  break;
63
- }
64
- case n.SUBSCRIBE:
65
- a.channels.forEach((n) => {
66
- if (e.channels.add(n), this.state.has(n)) {
85
+ case i.SUBSCRIBE: {
86
+ let t = o.channels.filter((t) => this.canSubscribe(e, t));
87
+ t.forEach((t) => {
88
+ let i = this.channelKey(e, t);
89
+ e.channels.add(i);
90
+ let a = this.readState(i);
91
+ if (a !== void 0) {
67
92
  let e = {
68
- type: t.STATE,
69
- channel: n,
70
- data: this.state.get(n)
93
+ type: r.STATE,
94
+ channel: t,
95
+ data: a
71
96
  };
72
- i.send(JSON.stringify(e));
97
+ n.send(JSON.stringify(e));
73
98
  }
74
- }), this.addLog(e.id, r.SUBSCRIBE, a.channels.join(", ")), this.broadcastStatus();
99
+ }), o.channels.filter((e) => !t.includes(e)).forEach((e) => this.deny(n, e)), this.addLog(e, a.SUBSCRIBE, t.join(", ")), this.broadcastStatus();
75
100
  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();
101
+ }
102
+ case i.UNSUBSCRIBE:
103
+ o.channels.forEach((t) => e.channels.delete(this.channelKey(e, t))), this.addLog(e, a.UNSUBSCRIBE, o.channels.join(", ")), this.broadcastStatus();
78
104
  break;
79
- case n.MESSAGE: {
80
- let n = {
81
- type: t.MESSAGE,
82
- channel: a.channel,
83
- data: a.data
105
+ case i.MESSAGE: {
106
+ if (!this.canPublish(e, o.channel)) {
107
+ this.deny(n, o.channel);
108
+ break;
109
+ }
110
+ let t = {
111
+ type: r.MESSAGE,
112
+ channel: o.channel,
113
+ data: o.data
84
114
  };
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();
115
+ this.broadcast(e, this.channelKey(e, o.channel), t), this.messageHandlers.forEach((e) => e(o.channel, o.data)), this.addLog(e, a.MESSAGE, o.channel, o.data), this.broadcastStatus();
86
116
  break;
87
117
  }
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
118
+ case i.STATE: {
119
+ if (!this.canPublish(e, o.channel)) {
120
+ this.deny(n, o.channel);
121
+ break;
122
+ }
123
+ let i = this.channelKey(e, o.channel);
124
+ if (!this.storeState(i, o.data, e.token ? t.getRetainSeconds(e.token, o.channel) : null)) {
125
+ this.deny(n, o.channel);
126
+ break;
127
+ }
128
+ let s = {
129
+ type: r.STATE,
130
+ channel: o.channel,
131
+ data: o.data
94
132
  };
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();
133
+ this.broadcast(e, i, s), this.messageHandlers.forEach((e) => e(o.channel, o.data)), this.addLog(e, a.STATE, o.channel, o.data), this.broadcastStatus();
96
134
  break;
97
135
  }
98
136
  }
99
137
  }
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();
138
+ identify(e, t, n) {
139
+ let r = [...this.clients].find((r) => r.id === t && r.app === n && !r.token == !e.token && r !== e);
140
+ r && (r.ws.close(g), this.clients.delete(r)), e.id = t, e.app = n, this.addLog(e, a.CONNECT, ""), this.broadcastStatus();
141
+ }
142
+ async identifyWithToken(e, n, i, a) {
143
+ let o = this.config.tokenSecret ? await t.verify(a, this.config.tokenSecret) : null;
144
+ if (!o || e.token && e.token.app !== o.app) {
145
+ this.deny(n), n.close(_);
146
+ return;
147
+ }
148
+ let s = !!e.token;
149
+ e.token = o, s || this.identify(e, i, o.app);
150
+ let c = { type: r.IDENTIFIED };
151
+ n.send(JSON.stringify(c));
152
+ }
153
+ canSubscribe(e, n) {
154
+ return e.token ? t.matchesAny(e.token.subscribe, n) : !0;
155
+ }
156
+ canPublish(e, n) {
157
+ return e.token ? t.matchesAny(e.token.publish, n) : !0;
158
+ }
159
+ channelKey(e, t) {
160
+ return e.token ? this.namespacedKey(e.token.app, t) : t;
161
+ }
162
+ namespacedKey(t, n) {
163
+ return `${e}${t}${v}${n}`;
164
+ }
165
+ storeState(t, n, r) {
166
+ if (t.startsWith(e) && !this.state.has(t)) {
167
+ let e = t.slice(0, t.indexOf(v) + 1);
168
+ if ([...this.state.keys()].filter((t) => t.startsWith(e)).length >= h) return !1;
169
+ }
170
+ return this.state.set(t, {
171
+ data: n,
172
+ expiresAt: r === null ? null : Date.now() + r * 1e3
173
+ }), !0;
174
+ }
175
+ readState(e) {
176
+ let t = this.state.get(e);
177
+ if (t) {
178
+ if (t.expiresAt !== null && t.expiresAt <= Date.now()) {
179
+ this.state.delete(e);
180
+ return;
181
+ }
182
+ return t.data;
183
+ }
108
184
  }
109
185
  broadcast(e, t, n) {
110
186
  let r = JSON.stringify(n);
111
187
  this.clients.forEach((n) => {
112
- n !== e && n.channels.has(t) && n.ws.readyState === a.OPEN && n.ws.send(r);
188
+ n !== e && n.channels.has(t) && n.ws.readyState === l.OPEN && n.ws.send(r);
113
189
  });
114
190
  }
115
- broadcastStatus() {
191
+ deny(e, t) {
116
192
  let n = {
117
- clients: [...this.clients].map((e) => ({
193
+ type: r.DENIED,
194
+ channel: t
195
+ };
196
+ e.readyState === l.OPEN && e.send(JSON.stringify(n));
197
+ }
198
+ isOverRate(e) {
199
+ let t = Date.now();
200
+ return t - e.windowStart >= p && (e.windowStart = t, e.messages = 0), e.messages++, e.messages > m;
201
+ }
202
+ rawSize(e) {
203
+ return Array.isArray(e) ? e.reduce((e, t) => e + t.length, 0) : e instanceof ArrayBuffer ? e.byteLength : e.length;
204
+ }
205
+ addLog(e, t, n, r) {
206
+ e.token || (this.logs.push({
207
+ timestamp: Date.now(),
208
+ sender: e.id,
209
+ app: e.app,
210
+ type: t,
211
+ channel: n,
212
+ data: r
213
+ }), this.logs.length > 808 && this.logs.shift());
214
+ }
215
+ broadcastStatus() {
216
+ if (this.statusTimer) {
217
+ this.statusDirty = !0;
218
+ return;
219
+ }
220
+ this.sendStatus(), this.statusTimer = setTimeout(() => {
221
+ this.statusTimer = null, this.statusDirty && (this.statusDirty = !1, this.broadcastStatus());
222
+ }, 200);
223
+ }
224
+ sendStatus() {
225
+ let t = [...this.clients].filter((e) => e.channels.has(n) && e.ws.readyState === l.OPEN);
226
+ if (!t.length) return;
227
+ let i = [...this.clients].filter((e) => !e.token), a = [...this.state].filter(([t]) => t !== n && !t.startsWith(e)), o = {
228
+ clients: i.map((e) => ({
118
229
  id: e.id,
119
230
  app: e.app,
120
231
  channels: [...e.channels]
121
232
  })),
122
- state: Object.fromEntries([...this.state].filter(([t]) => t !== e)),
233
+ state: Object.fromEntries(a.map(([e, t]) => [e, t.data])),
123
234
  logs: this.logs
124
235
  };
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);
236
+ this.state.set(n, {
237
+ data: o,
238
+ expiresAt: null
133
239
  });
240
+ let s = {
241
+ type: r.STATE,
242
+ channel: n,
243
+ data: o
244
+ }, c = JSON.stringify(s);
245
+ t.forEach((e) => e.ws.send(c));
134
246
  }
135
247
  onMessage(e) {
136
248
  this.messageHandlers.push(e);
137
249
  }
138
250
  getState(e) {
139
- return this.state.get(e);
251
+ return this.readState(e);
140
252
  }
141
253
  close() {
142
- clearInterval(this.heartbeatInterval), this.clients.forEach((e) => {
143
- e.ws.readyState === a.OPEN && e.ws.close(1001, "Hub shutting down");
254
+ clearInterval(this.heartbeatInterval), this.statusTimer && clearTimeout(this.statusTimer), this.clients.forEach((e) => {
255
+ e.ws.readyState === l.OPEN && e.ws.close(1001, "Hub shutting down");
144
256
  }), this.wss.close(), this.httpServer.close();
145
257
  }
146
258
  };
147
259
  //#endregion
148
- export { s as WebsocketHub };
260
+ export { y as WebsocketHub };
149
261
 
150
262
  //# sourceMappingURL=server.js.map