topsyde-utils 1.0.205 → 1.0.207

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.
Files changed (31) hide show
  1. package/dist/index.d.ts +2 -2
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/server/bun/websocket/Channel.d.ts +25 -3
  5. package/dist/server/bun/websocket/Channel.js +80 -26
  6. package/dist/server/bun/websocket/Channel.js.map +1 -1
  7. package/dist/server/bun/websocket/Client.d.ts +34 -1
  8. package/dist/server/bun/websocket/Client.js +95 -18
  9. package/dist/server/bun/websocket/Client.js.map +1 -1
  10. package/dist/server/bun/websocket/Message.d.ts +6 -10
  11. package/dist/server/bun/websocket/Message.js +31 -32
  12. package/dist/server/bun/websocket/Message.js.map +1 -1
  13. package/dist/server/bun/websocket/Websocket.d.ts +34 -4
  14. package/dist/server/bun/websocket/Websocket.js +70 -12
  15. package/dist/server/bun/websocket/Websocket.js.map +1 -1
  16. package/dist/server/bun/websocket/websocket.enums.d.ts +6 -0
  17. package/dist/server/bun/websocket/websocket.enums.js +7 -0
  18. package/dist/server/bun/websocket/websocket.enums.js.map +1 -1
  19. package/dist/server/bun/websocket/websocket.types.d.ts +60 -3
  20. package/dist/server/bun/websocket/websocket.types.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/__tests__/app.test.ts +1 -1
  23. package/src/__tests__/singleton.test.ts +6 -4
  24. package/src/index.ts +3 -1
  25. package/src/server/bun/websocket/Channel.ts +89 -36
  26. package/src/server/bun/websocket/Client.ts +109 -19
  27. package/src/server/bun/websocket/ISSUES.md +1175 -0
  28. package/src/server/bun/websocket/Message.ts +36 -49
  29. package/src/server/bun/websocket/Websocket.ts +71 -12
  30. package/src/server/bun/websocket/websocket.enums.ts +7 -0
  31. package/src/server/bun/websocket/websocket.types.ts +58 -3
@@ -1,12 +1,11 @@
1
1
  import Guards from "../../../utils/Guards.js";
2
- export default class Message {
3
- constructor() {
4
- this.messageTemplate = { type: "", content: {}, channel: "", timestamp: "" };
5
- }
6
- create(message, options) {
7
- // Clone the template (faster than creating new objects)
8
- const output = Object.assign({}, this.messageTemplate);
9
- // Set the dynamic properties in a single pass
2
+ class Message {
3
+ // Private constructor to prevent instantiation
4
+ constructor() { }
5
+ static Create(message, options) {
6
+ // Clone the template
7
+ const output = Object.assign({}, Message.MESSAGE_TEMPLATE);
8
+ // Set the dynamic properties
10
9
  output.type = message.type;
11
10
  output.channel = message.channel || options?.channel || "N/A";
12
11
  // Process message content based on type
@@ -19,7 +18,7 @@ export default class Message {
19
18
  else {
20
19
  output.content = {};
21
20
  }
22
- // Process options in a single pass if provided
21
+ // Process options if provided
23
22
  if (options) {
24
23
  // Add data if provided
25
24
  if (options.data !== undefined) {
@@ -36,14 +35,14 @@ export default class Message {
36
35
  if (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {
37
36
  output.client = {
38
37
  id: options.client.id,
39
- name: options.client.name,
38
+ name: options.client.name || "Unknown",
40
39
  };
41
- } /* else {
42
- delete output.client;
43
- } */
40
+ }
44
41
  // Include channel metadata if requested
45
- if (options.includeMetadata !== false)
42
+ // Include channel metadata if provided as an object
43
+ if (options.metadata && Guards.IsObject(options.metadata) && !Guards.IsArray(options.metadata)) {
46
44
  output.metadata = options.metadata;
45
+ }
47
46
  // Add timestamp if requested (default: true)
48
47
  if (options.includeTimestamp !== false) {
49
48
  output.timestamp = new Date().toISOString();
@@ -66,8 +65,7 @@ export default class Message {
66
65
  }
67
66
  // Apply custom transformation if provided
68
67
  if (options.transform) {
69
- const transformed = options.transform(output);
70
- return transformed;
68
+ return options.transform(output);
71
69
  }
72
70
  }
73
71
  else {
@@ -75,14 +73,17 @@ export default class Message {
75
73
  }
76
74
  return output;
77
75
  }
78
- createWhisper(message, options) {
79
- return this.create({ ...message, content: message.content, channel: message.channel, type: "whisper" }, options);
76
+ static CreateWhisper(message, options) {
77
+ return Message.Create({ ...message, content: message.content, channel: message.channel, type: "whisper" }, options);
78
+ }
79
+ static Serialize(message, transform) {
80
+ return transform ? transform(message) : JSON.stringify(message);
80
81
  }
81
- send(target, message, options) {
82
- target.send(this.create(message, options));
82
+ static Send(target, message, options) {
83
+ target.send(Message.Create(message, options));
83
84
  }
84
- alert(target, reason, client) {
85
- target.send(this.create({
85
+ static Alert(target, reason, client) {
86
+ target.send(Message.Create({
86
87
  content: {
87
88
  message: reason,
88
89
  },
@@ -90,15 +91,13 @@ export default class Message {
90
91
  type: "message",
91
92
  }, { client }));
92
93
  }
93
- serialize(message, transform) {
94
- return transform ? transform(message) : JSON.stringify(message);
95
- }
96
- static Serialize(message, transform) {
97
- return transform ? transform(message) : JSON.stringify(message);
98
- }
99
- static Create(message, options) {
100
- const msg = new Message();
101
- return msg.create(message, options);
102
- }
103
94
  }
95
+ // Shared template for all messages
96
+ Message.MESSAGE_TEMPLATE = {
97
+ type: "",
98
+ content: {},
99
+ channel: "",
100
+ timestamp: "",
101
+ };
102
+ export default Message;
104
103
  //# sourceMappingURL=Message.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Message.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Message.ts"],"names":[],"mappings":"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","sourcesContent":["import Guards from \"../../../utils/Guards\";\nimport {\n\tWebsocketStructuredMessage,\n\tWebsocketMessage,\n\tWebsocketMessageOptions,\n\tI_WebsocketChannel,\n\tI_WebsocketClient,\n\tWebsocketEntityData,\n} from \"./websocket.types\";\n\nexport default class Message {\n\tprivate messageTemplate: WebsocketStructuredMessage<any>;\n\n\tconstructor() {\n\t\tthis.messageTemplate = { type: \"\", content: {}, channel: \"\", timestamp: \"\" };\n\t}\n\n\tpublic create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage {\n\t\t// Clone the template (faster than creating new objects)\n\t\tconst output = Object.assign({}, this.messageTemplate);\n\t\t// Set the dynamic properties in a single pass\n\t\toutput.type = message.type;\n\t\toutput.channel = message.channel || options?.channel || \"N/A\";\n\n\t\t// Process message content based on type\n\t\tif (typeof message.content === \"string\") {\n\t\t\toutput.content = { message: message.content };\n\t\t} else if (typeof message.content === \"object\" && message.content !== null) {\n\t\t\toutput.content = { ...message.content };\n\t\t} else {\n\t\t\toutput.content = {};\n\t\t}\n\n\t\t// Process options in a single pass if provided\n\t\tif (options) {\n\t\t\t// Add data if provided\n\t\t\tif (options.data !== undefined) {\n\t\t\t\tif (typeof options.data === \"object\" && options.data !== null && !Array.isArray(options.data)) {\n\t\t\t\t\t// Merge object data with content\n\t\t\t\t\tObject.assign(output.content, options.data);\n\t\t\t\t} else {\n\t\t\t\t\t// Set as data property for other types\n\t\t\t\t\toutput.content.data = options.data;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Add client information if provided\n\t\t\tif (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {\n\t\t\t\toutput.client = {\n\t\t\t\t\tid: options.client.id,\n\t\t\t\t\tname: options.client.name,\n\t\t\t\t};\n\t\t\t} /* else {\n\t\t\t\tdelete output.client;\n\t\t\t} */\n\n\t\t\t// Include channel metadata if requested\n\t\t\tif (options.includeMetadata !== false) output.metadata = options.metadata;\n\n\t\t\t// Add timestamp if requested (default: true)\n\t\t\tif (options.includeTimestamp !== false) {\n\t\t\t\toutput.timestamp = new Date().toISOString();\n\t\t\t} else {\n\t\t\t\t// Remove timestamp if explicitly disabled\n\t\t\t\tdelete output.timestamp;\n\t\t\t}\n\n\t\t\t// Add priority if specified\n\t\t\tif (options.priority !== undefined) {\n\t\t\t\toutput.priority = options.priority;\n\t\t\t}\n\n\t\t\t// Add expiration if specified\n\t\t\tif (options.expiresAt !== undefined) {\n\t\t\t\toutput.expiresAt = options.expiresAt;\n\t\t\t}\n\n\t\t\t// Add any custom fields to the root of the message\n\t\t\tif (options.customFields) {\n\t\t\t\tObject.assign(output, options.customFields);\n\t\t\t}\n\n\t\t\t// Apply custom transformation if provided\n\t\t\tif (options.transform) {\n\t\t\t\tconst transformed = options.transform(output);\n\t\t\t\treturn transformed;\n\t\t\t}\n\t\t} else {\n\t\t\toutput.timestamp = new Date().toISOString();\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tpublic createWhisper(message: Omit<WebsocketMessage, \"type\">, options?: WebsocketMessageOptions) {\n\t\treturn this.create({ ...message, content: message.content, channel: message.channel, type: \"whisper\" }, options);\n\t}\n\n\tpublic send(target: I_WebsocketClient, message: WebsocketStructuredMessage): void;\n\tpublic send(target: I_WebsocketClient, message: WebsocketMessage, options?: WebsocketMessageOptions): void;\n\tpublic send(target: I_WebsocketClient, message: WebsocketStructuredMessage | WebsocketMessage, options?: WebsocketMessageOptions): void {\n\t\ttarget.send(this.create(message, options));\n\t}\n\n\tpublic alert(target: I_WebsocketClient, reason: string, client?: WebsocketEntityData) {\n\t\ttarget.send(\n\t\t\tthis.create(\n\t\t\t\t{\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tmessage: reason,\n\t\t\t\t\t},\n\t\t\t\t\tchannel: \"alert\",\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t},\n\t\t\t\t{ client },\n\t\t\t),\n\t\t);\n\t}\n\n\tpublic serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T) {\n\t\treturn transform ? transform(message) : JSON.stringify(message);\n\t}\n\n\tpublic static Serialize<T = string>(message: WebsocketStructuredMessage, transform: (message: WebsocketStructuredMessage) => T): T;\n\tpublic static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T;\n\tpublic static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T {\n\t\treturn transform ? transform(message) : JSON.stringify(message);\n\t}\n\n\tpublic static Create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage{\n\t\tconst msg = new Message();\n\t\treturn msg.create(message, options);\n\t}\n}\n"]}
1
+ {"version":3,"file":"Message.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Message.ts"],"names":[],"mappings":"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","sourcesContent":["import Guards from \"../../../utils/Guards\";\nimport { WebsocketStructuredMessage, WebsocketMessage, WebsocketMessageOptions, I_WebsocketClient, WebsocketEntityData } from \"./websocket.types\";\n\nexport default class Message {\n\t// Shared template for all messages\n\tprivate static readonly MESSAGE_TEMPLATE: WebsocketStructuredMessage<any> = {\n\t\ttype: \"\",\n\t\tcontent: {},\n\t\tchannel: \"\",\n\t\ttimestamp: \"\",\n\t};\n\n\t// Private constructor to prevent instantiation\n\tprivate constructor() {}\n\n\tpublic static Create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage {\n\t\t// Clone the template\n\t\tconst output = Object.assign({}, Message.MESSAGE_TEMPLATE);\n\n\t\t// Set the dynamic properties\n\t\toutput.type = message.type;\n\t\toutput.channel = message.channel || options?.channel || \"N/A\";\n\n\t\t// Process message content based on type\n\t\tif (typeof message.content === \"string\") {\n\t\t\toutput.content = { message: message.content };\n\t\t} else if (typeof message.content === \"object\" && message.content !== null) {\n\t\t\toutput.content = { ...message.content };\n\t\t} else {\n\t\t\toutput.content = {};\n\t\t}\n\n\t\t// Process options if provided\n\t\tif (options) {\n\t\t\t// Add data if provided\n\t\t\tif (options.data !== undefined) {\n\t\t\t\tif (typeof options.data === \"object\" && options.data !== null && !Array.isArray(options.data)) {\n\t\t\t\t\t// Merge object data with content\n\t\t\t\t\tObject.assign(output.content, options.data);\n\t\t\t\t} else {\n\t\t\t\t\t// Set as data property for other types\n\t\t\t\t\toutput.content.data = options.data;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add client information if provided\n\t\t\tif (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {\n\t\t\t\toutput.client = {\n\t\t\t\t\tid: options.client.id,\n\t\t\t\t\tname: options.client.name || \"Unknown\",\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Include channel metadata if requested\n\t\t\t// Include channel metadata if provided as an object\n\t\t\tif (options.metadata && Guards.IsObject(options.metadata) && !Guards.IsArray(options.metadata)) {\n\t\t\t\toutput.metadata = options.metadata;\n\t\t\t}\n\n\t\t\t// Add timestamp if requested (default: true)\n\t\t\tif (options.includeTimestamp !== false) {\n\t\t\t\toutput.timestamp = new Date().toISOString();\n\t\t\t} else {\n\t\t\t\t// Remove timestamp if explicitly disabled\n\t\t\t\tdelete output.timestamp;\n\t\t\t}\n\n\t\t\t// Add priority if specified\n\t\t\tif (options.priority !== undefined) {\n\t\t\t\toutput.priority = options.priority;\n\t\t\t}\n\n\t\t\t// Add expiration if specified\n\t\t\tif (options.expiresAt !== undefined) {\n\t\t\t\toutput.expiresAt = options.expiresAt;\n\t\t\t}\n\n\t\t\t// Add any custom fields to the root of the message\n\t\t\tif (options.customFields) {\n\t\t\t\tObject.assign(output, options.customFields);\n\t\t\t}\n\n\t\t\t// Apply custom transformation if provided\n\t\t\tif (options.transform) {\n\t\t\t\treturn options.transform(output);\n\t\t\t}\n\t\t} else {\n\t\t\toutput.timestamp = new Date().toISOString();\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tpublic static CreateWhisper(message: Omit<WebsocketMessage, \"type\">, options?: WebsocketMessageOptions): WebsocketStructuredMessage {\n\t\treturn Message.Create({ ...message, content: message.content, channel: message.channel, type: \"whisper\" }, options);\n\t}\n\n\tpublic static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T {\n\t\treturn transform ? transform(message) : JSON.stringify(message);\n\t}\n\n\tpublic static Send(target: I_WebsocketClient, message: WebsocketMessage, options?: WebsocketMessageOptions): void {\n\t\ttarget.send(Message.Create(message, options));\n\t}\n\n\tpublic static Alert(target: I_WebsocketClient, reason: string, client?: WebsocketEntityData): void {\n\t\ttarget.send(\n\t\t\tMessage.Create(\n\t\t\t\t{\n\t\t\t\t\tcontent: {\n\t\t\t\t\t\tmessage: reason,\n\t\t\t\t\t},\n\t\t\t\t\tchannel: \"alert\",\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t},\n\t\t\t\t{ client },\n\t\t\t),\n\t\t);\n\t}\n}\n"]}
@@ -13,6 +13,25 @@ export interface I_WebsocketConstructor {
13
13
  channelClass?: typeof Channel;
14
14
  options?: WebsocketConstructorOptions;
15
15
  }
16
+ /**
17
+ * Websocket - Singleton managing clients, channels, and message routing
18
+ *
19
+ * ## API Design: Static vs Instance
20
+ * - **Static methods**: Use in application code (e.g., `Websocket.Broadcast()`, `Websocket.GetClient()`)
21
+ * - **Instance methods**: Use when extending the class (e.g., `protected createClient()`)
22
+ *
23
+ * Static methods are facades that call the singleton instance internally.
24
+ *
25
+ * @example
26
+ * // Application code - use static methods
27
+ * Websocket.Broadcast("lobby", { type: "chat", content: { message: "Hi!" } });
28
+ *
29
+ * // Extension - override instance methods
30
+ * MyWebsocket extends Websocket:
31
+ * protected createClient(entity) {
32
+ * return new MyCustomClient(entity);
33
+ * }
34
+ */
16
35
  export default class Websocket extends Singleton {
17
36
  protected _channels: WebsocketChannel;
18
37
  protected _clients: Map<string, I_WebsocketClient>;
@@ -77,7 +96,7 @@ export default class Websocket extends Singleton {
77
96
  * @param message - The message
78
97
  * @param args - The arguments
79
98
  */
80
- static BraodcastAll(message: WebsocketStructuredMessage, ...args: any[]): void;
99
+ static BroadCastAll(message: WebsocketStructuredMessage, ...args: any[]): void;
81
100
  /**
82
101
  * Join a channel
83
102
  * @param channel - The channel
@@ -131,8 +150,19 @@ export default class Websocket extends Singleton {
131
150
  */
132
151
  static CreateClient(entity: I_WebsocketEntity): I_WebsocketClient;
133
152
  /**
134
- * Generate a message
135
- * @returns The generated message
153
+ * Get all connected clients (excluding connecting/disconnecting)
154
+ * @returns Array of connected clients
136
155
  */
137
- static GenerateMessage(): WebsocketStructuredMessage;
156
+ static GetConnectedClients(): I_WebsocketClient[];
157
+ /**
158
+ * Get client statistics by state
159
+ * @returns Object with counts by state
160
+ */
161
+ static GetClientStats(): {
162
+ total: number;
163
+ connecting: number;
164
+ connected: number;
165
+ disconnecting: number;
166
+ disconnected: number;
167
+ };
138
168
  }
@@ -4,6 +4,25 @@ import { Console } from "../../../utils/Console.js";
4
4
  import Channel from "./Channel.js";
5
5
  import Client from "./Client.js";
6
6
  import { E_WebsocketMessageType } from "./websocket.enums.js";
7
+ /**
8
+ * Websocket - Singleton managing clients, channels, and message routing
9
+ *
10
+ * ## API Design: Static vs Instance
11
+ * - **Static methods**: Use in application code (e.g., `Websocket.Broadcast()`, `Websocket.GetClient()`)
12
+ * - **Instance methods**: Use when extending the class (e.g., `protected createClient()`)
13
+ *
14
+ * Static methods are facades that call the singleton instance internally.
15
+ *
16
+ * @example
17
+ * // Application code - use static methods
18
+ * Websocket.Broadcast("lobby", { type: "chat", content: { message: "Hi!" } });
19
+ *
20
+ * // Extension - override instance methods
21
+ * MyWebsocket extends Websocket:
22
+ * protected createClient(entity) {
23
+ * return new MyCustomClient(entity);
24
+ * }
25
+ */
7
26
  export default class Websocket extends Singleton {
8
27
  constructor(options) {
9
28
  super();
@@ -14,7 +33,7 @@ export default class Websocket extends Singleton {
14
33
  if (this._ws_interface_handlers.message)
15
34
  return this._ws_interface_handlers.message(ws, message);
16
35
  ws.send("This is the message from the server: " + message);
17
- Websocket.BraodcastAll({ type: "client.message.received", content: { message } });
36
+ Websocket.BroadCastAll({ type: "client.message.received", content: { message } });
18
37
  };
19
38
  this.clientConnected = (ws) => {
20
39
  if (this._options.debug)
@@ -25,21 +44,32 @@ export default class Websocket extends Singleton {
25
44
  const client = Websocket.CreateClient({ id: ws.data.id, ws: ws, name: ws.data.name });
26
45
  this._clients.set(client.id, client);
27
46
  client.send({ type: E_WebsocketMessageType.CLIENT_CONNECTED, content: { message: "Welcome to the server", client: client.whoami() } });
28
- global.addMember(client);
47
+ // Client handles its own joining logic with rollback support
48
+ if (!client.joinChannel(global)) {
49
+ Lib.Warn(`Failed to add client ${client.id} to global channel`);
50
+ }
51
+ // Mark as fully connected
52
+ client.markConnected();
29
53
  if (this._ws_interface_handlers.open)
30
54
  this._ws_interface_handlers.open(ws);
31
55
  };
32
56
  this.clientDisconnected = (ws, code, reason) => {
33
57
  if (this._options.debug)
34
58
  Lib.Log("Client disconnected", ws.data);
35
- if (this._ws_interface_handlers.close)
36
- this._ws_interface_handlers.close(ws, code, reason);
37
59
  const client = this._clients.get(ws.data.id);
38
60
  if (!client)
39
61
  return;
62
+ // Mark as disconnecting
63
+ client.markDisconnecting();
64
+ if (this._ws_interface_handlers.close)
65
+ this._ws_interface_handlers.close(ws, code, reason);
66
+ // Remove from all channels
40
67
  this._channels.forEach((channel) => {
41
68
  channel.removeMember(client);
42
69
  });
70
+ // Mark as disconnected
71
+ client.markDisconnected();
72
+ // Remove from registry
43
73
  this._clients.delete(ws.data.id);
44
74
  };
45
75
  this.handleHeartbeat = (ws, message) => {
@@ -147,7 +177,7 @@ export default class Websocket extends Singleton {
147
177
  * @param message - The message
148
178
  * @param args - The arguments
149
179
  */
150
- static BraodcastAll(message, ...args) {
180
+ static BroadCastAll(message, ...args) {
151
181
  const ws = this.GetInstance();
152
182
  ws._channels.forEach((channel) => channel.broadcast(message, ...args));
153
183
  }
@@ -233,15 +263,43 @@ export default class Websocket extends Singleton {
233
263
  return ws.createClient(entity);
234
264
  }
235
265
  /**
236
- * Generate a message
237
- * @returns The generated message
266
+ * Get all connected clients (excluding connecting/disconnecting)
267
+ * @returns Array of connected clients
268
+ */
269
+ static GetConnectedClients() {
270
+ const ws = this.GetInstance();
271
+ return Array.from(ws._clients.values()).filter(client => client.state === "connected");
272
+ }
273
+ /**
274
+ * Get client statistics by state
275
+ * @returns Object with counts by state
238
276
  */
239
- static GenerateMessage() {
240
- const msg = {
241
- type: "",
242
- content: {},
277
+ static GetClientStats() {
278
+ const ws = this.GetInstance();
279
+ const stats = {
280
+ total: ws._clients.size,
281
+ connecting: 0,
282
+ connected: 0,
283
+ disconnecting: 0,
284
+ disconnected: 0,
243
285
  };
244
- return msg;
286
+ for (const client of ws._clients.values()) {
287
+ switch (client.state) {
288
+ case "connecting":
289
+ stats.connecting++;
290
+ break;
291
+ case "connected":
292
+ stats.connected++;
293
+ break;
294
+ case "disconnecting":
295
+ stats.disconnecting++;
296
+ break;
297
+ case "disconnected":
298
+ stats.disconnected++;
299
+ break;
300
+ }
301
+ }
302
+ return stats;
245
303
  }
246
304
  }
247
305
  //# sourceMappingURL=Websocket.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Websocket.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Websocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server, ServerWebSocket, WebSocketHandler } from \"bun\";\nimport Singleton from \"../../../singleton\";\nimport { Lib } from \"../../../utils\";\nimport { Console } from \"../../../utils/Console\";\nimport Channel from \"./Channel\";\nimport Client from \"./Client\";\nimport type {\n\tI_WebsocketChannel,\n\tI_WebsocketClient,\n\tI_WebsocketEntity,\n\tI_WebsocketInterface,\n\tWebsocketChannel,\n\tWebsocketEntityData,\n\tBunWebsocketMessage,\n\tWebsocketStructuredMessage,\n} from \"./websocket.types\";\nimport { E_WebsocketMessageType } from \"./websocket.enums\";\n\nexport type WebsocketConstructorOptions = {\n\tdebug?: boolean;\n};\n\nexport interface I_WebsocketConstructor {\n\tws_interface?: I_WebsocketInterface;\n\tchannels?: WebsocketChannel;\n\tclientClass?: typeof Client;\n\tchannelClass?: typeof Channel;\n\toptions?: WebsocketConstructorOptions;\n}\n\nexport default class Websocket extends Singleton {\n\tprotected _channels: WebsocketChannel;\n\tprotected _clients: Map<string, I_WebsocketClient> = new Map();\n\tprotected _server!: Server;\n\tprotected _channelClass: typeof Channel;\n\tprotected _clientClass: typeof Client;\n\tprotected _ws_interface?: I_WebsocketInterface;\n\tprotected _options: WebsocketConstructorOptions;\n\tprotected _ws_interface_handlers: Partial<WebSocketHandler<WebsocketEntityData>>;\n\n\tprotected constructor(options?: I_WebsocketConstructor) {\n\t\tsuper();\n\t\tthis._ws_interface = options?.ws_interface;\n\t\tthis._channels = options?.channels ?? new Map<string, Channel>();\n\t\tthis._clientClass = options?.clientClass ?? Client;\n\t\tthis._channelClass = options?.channelClass ?? Channel.GetChannelType(options?.channels);\n\t\tthis._options = options?.options ?? { debug: false };\n\t\tthis.createChannel(\"global\", \"Global\", 1000);\n\t\tthis._ws_interface_handlers = this._ws_interface?.handlers(this._channels, this._clients) ?? {};\n\t}\n\n\tprotected set server(value: Server) {\n\t\tthis._server = value;\n\t}\n\n\tpublic get server(): Server {\n\t\treturn this._server;\n\t}\n\n\tpublic set(server: Server) {\n\t\tthis.server = server;\n\t\tConsole.blank();\n\t\tConsole.success(\"Websocket server set\");\n\t}\n\n\t/**\n\t * Create a new channel\n\t * @param id - The id of the channel\n\t * @param name - The name of the channel\n\t * @param limit - The limit of the channel\n\t * @returns The created channel\n\t */\n\tpublic createChannel(id: string, name: string, limit?: number): I_WebsocketChannel {\n\t\tif (this._channels.has(id)) return this._channels.get(id) as I_WebsocketChannel;\n\t\tconst channel = new this._channelClass(id, name, this, limit);\n\t\tthis._channels.set(id, channel);\n\t\treturn channel;\n\t}\n\n\t/**\n\t * Remove a channel\n\t * @param id - The id of the channel\n\t */\n\tpublic removeChannel(id: string) {\n\t\tthis._channels.delete(id);\n\t}\n\n\t/**\n\t * Create a new channel\n\t * @param id - The id of the channel\n\t * @param name - The name of the channel\n\t * @param limit - The limit of the channel\n\t * @returns The created channel\n\t */\n\tpublic static CreateChannel(id: string, name: string, limit?: number) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws.createChannel(id, name, limit);\n\t}\n\n\tpublic handlers(): WebSocketHandler<WebsocketEntityData> {\n\t\treturn {\n\t\t\topen: this.clientConnected,\n\t\t\tmessage: this.clientMessageReceived,\n\t\t\tclose: this.clientDisconnected,\n\t\t};\n\t}\n\n\tprivate clientMessageReceived = (ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) => {\n\t\tif (Websocket.Heartbeat(ws, message)) return;\n\n\t\tif (this._ws_interface_handlers.message) return this._ws_interface_handlers.message(ws, message);\n\n\t\tws.send(\"This is the message from the server: \" + message);\n\t\tWebsocket.BraodcastAll({ type: \"client.message.received\", content: { message } });\n\t};\n\n\tprivate clientConnected = (ws: ServerWebSocket<WebsocketEntityData>) => {\n\t\tif (this._options.debug) Lib.Log(\"[debug] Client connected\", ws.data);\n\n\t\tconst global = this._channels.get(\"global\");\n\t\tif (!global) throw new Error(\"Global channel not found\");\n\n\t\tconst client = Websocket.CreateClient({ id: ws.data.id, ws: ws, name: ws.data.name });\n\t\tthis._clients.set(client.id, client);\n\n\t\tclient.send({ type: E_WebsocketMessageType.CLIENT_CONNECTED, content: { message: \"Welcome to the server\", client: client.whoami() } });\n\t\tglobal.addMember(client);\n\n\t\tif (this._ws_interface_handlers.open) this._ws_interface_handlers.open(ws);\n\t};\n\n\tprivate clientDisconnected = (ws: ServerWebSocket<WebsocketEntityData>, code: number, reason: string) => {\n\t\tif (this._options.debug) Lib.Log(\"Client disconnected\", ws.data);\n\n\t\tif (this._ws_interface_handlers.close) this._ws_interface_handlers.close(ws, code, reason);\n\n\t\tconst client = this._clients.get(ws.data.id);\n\t\tif (!client) return;\n\n\t\tthis._channels.forEach((channel) => {\n\t\t\tchannel.removeMember(client);\n\t\t});\n\n\t\tthis._clients.delete(ws.data.id);\n\t};\n\n\tprivate handleHeartbeat = (ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) => {\n\t\tif (message === \"ping\") {\n\t\t\tconst pong: WebsocketStructuredMessage = { type: \"pong\", content: { message: \"pong\" } };\n\t\t\tws.send(JSON.stringify(pong));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t};\n\n\tprotected createClient(entity: I_WebsocketEntity): I_WebsocketClient {\n\t\treturn new this._clientClass(entity);\n\t}\n\n\t/**\n\t * Handle the heartbeat\n\t * @param ws - The websocket\n\t * @param message - The message\n\t * @returns True if the heartbeat was handled, false otherwise\n\t */\n\tpublic static Heartbeat(ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) {\n\t\tconst self = this.GetInstance<Websocket>();\n\t\treturn self.handleHeartbeat(ws, message);\n\t}\n\n\t/**\n\t * Get the server\n\t * @returns The server\n\t */\n\tpublic static Server() {\n\t\treturn this.GetInstance<Websocket>().server;\n\t}\n\n\t/**\n\t * Broadcast a message to a channel\n\t * @param channel - The channel\n\t * @param message - The message\n\t * @param args - The arguments\n\t */\n\tpublic static Broadcast(channel: string, message: WebsocketStructuredMessage, ...args: any[]) {\n\t\t// Get the server from the singleton instance\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tif (!ws.server) {\n\t\t\tthrow new Error(\"Websocket server not set\");\n\t\t}\n\t\tws.server.publish(channel, JSON.stringify({ message, args }));\n\t}\n\n\t/**\n\t * Broadcast a message to all channels\n\t * @param message - The message\n\t * @param args - The arguments\n\t */\n\tpublic static BraodcastAll(message: WebsocketStructuredMessage, ...args: any[]) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tws._channels.forEach((channel) => channel.broadcast(message, ...args));\n\t}\n\n\t/**\n\t * Join a channel\n\t * @param channel - The channel\n\t * @param entity - The entity\n\t */\n\tpublic static Join(channel: string, entity: I_WebsocketEntity) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst client = ws._clients.get(entity.id);\n\t\tif (!client) return;\n\t\tws._channels.get(channel)?.addMember(client);\n\t}\n\n\t/**\n\t * Leave a channel\n\t * @param channel - The channel\n\t * @param entity - The entity\n\t */\n\tpublic static Leave(channel: string, entity: I_WebsocketEntity) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst client = ws._clients.get(entity.id);\n\t\tif (!client) return;\n\t\tws._channels.get(channel)?.removeMember(client);\n\t}\n\n\t/**\n\t * Get a client\n\t * @param id - The id of the client\n\t * @param throw_if_nil - Whether to throw an error if the client is not found\n\t * @returns The client\n\t */\n\tpublic static GetClient(id: string, throw_if_nil?: true): I_WebsocketClient;\n\tpublic static GetClient(id: string, throw_if_nil?: false): I_WebsocketClient | undefined;\n\tpublic static GetClient(id: string, throw_if_nil: boolean = true): I_WebsocketClient | undefined {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst client = ws._clients.get(id);\n\t\tif (!client && throw_if_nil) throw new Error(`Client with id ${id} not found`);\n\t\treturn client;\n\t}\n\n\t/**\n\t * Get a channel\n\t * @param id - The id of the channel\n\t * @returns The channel\n\t */\n\tpublic static GetChannel(id: string) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._channels.get(id);\n\t}\n\n\t/**\n\t * Get all channels\n\t * @returns The channels\n\t */\n\tpublic static GetChannels() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn Array.from(ws._channels.values());\n\t}\n\n\t/**\n\t * Get all clients\n\t * @returns The clients\n\t */\n\tpublic static GetClients() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn Array.from(ws._clients.values());\n\t}\n\n\t/**\n\t * Get the number of clients\n\t * @returns The number of clients\n\t */\n\tpublic static GetClientCount() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._clients.size;\n\t}\n\n\t/**\n\t * Get the number of channels\n\t * @returns The number of channels\n\t */\n\tpublic static GetChannelCount() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._channels.size;\n\t}\n\n\t/**\n\t * Create a client\n\t * @param entity - The entity\n\t * @returns The created client\n\t */\n\tpublic static CreateClient(entity: I_WebsocketEntity): I_WebsocketClient {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws.createClient(entity);\n\t}\n\n\t/**\n\t * Generate a message\n\t * @returns The generated message\n\t */\n\tpublic static GenerateMessage(): WebsocketStructuredMessage {\n\t\tconst msg: WebsocketStructuredMessage = {\n\t\t\ttype: \"\",\n\t\t\tcontent: {},\n\t\t};\n\t\treturn msg;\n\t}\n}\n"]}
1
+ {"version":3,"file":"Websocket.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Websocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server, ServerWebSocket, WebSocketHandler } from \"bun\";\nimport Singleton from \"../../../singleton\";\nimport { Lib } from \"../../../utils\";\nimport { Console } from \"../../../utils/Console\";\nimport Channel from \"./Channel\";\nimport Client from \"./Client\";\nimport type {\n\tI_WebsocketChannel,\n\tI_WebsocketClient,\n\tI_WebsocketEntity,\n\tI_WebsocketInterface,\n\tWebsocketChannel,\n\tWebsocketEntityData,\n\tBunWebsocketMessage,\n\tWebsocketStructuredMessage,\n} from \"./websocket.types\";\nimport { E_WebsocketMessageType } from \"./websocket.enums\";\n\nexport type WebsocketConstructorOptions = {\n\tdebug?: boolean;\n};\n\nexport interface I_WebsocketConstructor {\n\tws_interface?: I_WebsocketInterface;\n\tchannels?: WebsocketChannel;\n\tclientClass?: typeof Client;\n\tchannelClass?: typeof Channel;\n\toptions?: WebsocketConstructorOptions;\n}\n\n/**\n * Websocket - Singleton managing clients, channels, and message routing\n *\n * ## API Design: Static vs Instance\n * - **Static methods**: Use in application code (e.g., `Websocket.Broadcast()`, `Websocket.GetClient()`)\n * - **Instance methods**: Use when extending the class (e.g., `protected createClient()`)\n *\n * Static methods are facades that call the singleton instance internally.\n *\n * @example\n * // Application code - use static methods\n * Websocket.Broadcast(\"lobby\", { type: \"chat\", content: { message: \"Hi!\" } });\n *\n * // Extension - override instance methods\n * MyWebsocket extends Websocket:\n * protected createClient(entity) {\n * return new MyCustomClient(entity);\n * }\n */\nexport default class Websocket extends Singleton {\n\tprotected _channels: WebsocketChannel;\n\tprotected _clients: Map<string, I_WebsocketClient> = new Map();\n\tprotected _server!: Server;\n\tprotected _channelClass: typeof Channel;\n\tprotected _clientClass: typeof Client;\n\tprotected _ws_interface?: I_WebsocketInterface;\n\tprotected _options: WebsocketConstructorOptions;\n\tprotected _ws_interface_handlers: Partial<WebSocketHandler<WebsocketEntityData>>;\n\n\tprotected constructor(options?: I_WebsocketConstructor) {\n\t\tsuper();\n\t\tthis._ws_interface = options?.ws_interface;\n\t\tthis._channels = options?.channels ?? new Map<string, Channel>();\n\t\tthis._clientClass = options?.clientClass ?? Client;\n\t\tthis._channelClass = options?.channelClass ?? Channel.GetChannelType(options?.channels);\n\t\tthis._options = options?.options ?? { debug: false };\n\t\tthis.createChannel(\"global\", \"Global\", 1000);\n\t\tthis._ws_interface_handlers = this._ws_interface?.handlers(this._channels, this._clients) ?? {};\n\t}\n\n\tprotected set server(value: Server) {\n\t\tthis._server = value;\n\t}\n\n\tpublic get server(): Server {\n\t\treturn this._server;\n\t}\n\n\tpublic set(server: Server) {\n\t\tthis.server = server;\n\t\tConsole.blank();\n\t\tConsole.success(\"Websocket server set\");\n\t}\n\n\t/**\n\t * Create a new channel\n\t * @param id - The id of the channel\n\t * @param name - The name of the channel\n\t * @param limit - The limit of the channel\n\t * @returns The created channel\n\t */\n\tpublic createChannel(id: string, name: string, limit?: number): I_WebsocketChannel {\n\t\tif (this._channels.has(id)) return this._channels.get(id) as I_WebsocketChannel;\n\t\tconst channel = new this._channelClass(id, name, this, limit);\n\t\tthis._channels.set(id, channel);\n\t\treturn channel;\n\t}\n\n\t/**\n\t * Remove a channel\n\t * @param id - The id of the channel\n\t */\n\tpublic removeChannel(id: string) {\n\t\tthis._channels.delete(id);\n\t}\n\n\t/**\n\t * Create a new channel\n\t * @param id - The id of the channel\n\t * @param name - The name of the channel\n\t * @param limit - The limit of the channel\n\t * @returns The created channel\n\t */\n\tpublic static CreateChannel(id: string, name: string, limit?: number) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws.createChannel(id, name, limit);\n\t}\n\n\tpublic handlers(): WebSocketHandler<WebsocketEntityData> {\n\t\treturn {\n\t\t\topen: this.clientConnected,\n\t\t\tmessage: this.clientMessageReceived,\n\t\t\tclose: this.clientDisconnected,\n\t\t};\n\t}\n\n\tprivate clientMessageReceived = (ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) => {\n\t\tif (Websocket.Heartbeat(ws, message)) return;\n\n\t\tif (this._ws_interface_handlers.message) return this._ws_interface_handlers.message(ws, message);\n\n\t\tws.send(\"This is the message from the server: \" + message);\n\t\tWebsocket.BroadCastAll({ type: \"client.message.received\", content: { message } });\n\t};\n\n\tprivate clientConnected = (ws: ServerWebSocket<WebsocketEntityData>) => {\n\t\tif (this._options.debug) Lib.Log(\"[debug] Client connected\", ws.data);\n\n\t\tconst global = this._channels.get(\"global\");\n\t\tif (!global) throw new Error(\"Global channel not found\");\n\n\t\tconst client = Websocket.CreateClient({ id: ws.data.id, ws: ws, name: ws.data.name });\n\t\tthis._clients.set(client.id, client);\n\n\t\tclient.send({ type: E_WebsocketMessageType.CLIENT_CONNECTED, content: { message: \"Welcome to the server\", client: client.whoami() } });\n\n\t\t// Client handles its own joining logic with rollback support\n\t\tif (!client.joinChannel(global)) {\n\t\t\tLib.Warn(`Failed to add client ${client.id} to global channel`);\n\t\t}\n\n\t\t// Mark as fully connected\n\t\tclient.markConnected();\n\n\t\tif (this._ws_interface_handlers.open) this._ws_interface_handlers.open(ws);\n\t};\n\n\tprivate clientDisconnected = (ws: ServerWebSocket<WebsocketEntityData>, code: number, reason: string) => {\n\t\tif (this._options.debug) Lib.Log(\"Client disconnected\", ws.data);\n\n\t\tconst client = this._clients.get(ws.data.id);\n\t\tif (!client) return;\n\n\t\t// Mark as disconnecting\n\t\tclient.markDisconnecting();\n\n\t\tif (this._ws_interface_handlers.close) this._ws_interface_handlers.close(ws, code, reason);\n\n\t\t// Remove from all channels\n\t\tthis._channels.forEach((channel) => {\n\t\t\tchannel.removeMember(client);\n\t\t});\n\n\t\t// Mark as disconnected\n\t\tclient.markDisconnected();\n\n\t\t// Remove from registry\n\t\tthis._clients.delete(ws.data.id);\n\t};\n\n\tprivate handleHeartbeat = (ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) => {\n\t\tif (message === \"ping\") {\n\t\t\tconst pong: WebsocketStructuredMessage = { type: \"pong\", content: { message: \"pong\" } };\n\t\t\tws.send(JSON.stringify(pong));\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t};\n\n\tprotected createClient(entity: I_WebsocketEntity): I_WebsocketClient {\n\t\treturn new this._clientClass(entity);\n\t}\n\n\t/**\n\t * Handle the heartbeat\n\t * @param ws - The websocket\n\t * @param message - The message\n\t * @returns True if the heartbeat was handled, false otherwise\n\t */\n\tpublic static Heartbeat(ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) {\n\t\tconst self = this.GetInstance<Websocket>();\n\t\treturn self.handleHeartbeat(ws, message);\n\t}\n\n\t/**\n\t * Get the server\n\t * @returns The server\n\t */\n\tpublic static Server() {\n\t\treturn this.GetInstance<Websocket>().server;\n\t}\n\n\t/**\n\t * Broadcast a message to a channel\n\t * @param channel - The channel\n\t * @param message - The message\n\t * @param args - The arguments\n\t */\n\tpublic static Broadcast(channel: string, message: WebsocketStructuredMessage, ...args: any[]) {\n\t\t// Get the server from the singleton instance\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tif (!ws.server) {\n\t\t\tthrow new Error(\"Websocket server not set\");\n\t\t}\n\t\tws.server.publish(channel, JSON.stringify({ message, args }));\n\t}\n\n\t/**\n\t * Broadcast a message to all channels\n\t * @param message - The message\n\t * @param args - The arguments\n\t */\n\tpublic static BroadCastAll(message: WebsocketStructuredMessage, ...args: any[]) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tws._channels.forEach((channel) => channel.broadcast(message, ...args));\n\t}\n\n\t/**\n\t * Join a channel\n\t * @param channel - The channel\n\t * @param entity - The entity\n\t */\n\tpublic static Join(channel: string, entity: I_WebsocketEntity) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst client = ws._clients.get(entity.id);\n\t\tif (!client) return;\n\t\tws._channels.get(channel)?.addMember(client);\n\t}\n\n\t/**\n\t * Leave a channel\n\t * @param channel - The channel\n\t * @param entity - The entity\n\t */\n\tpublic static Leave(channel: string, entity: I_WebsocketEntity) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst client = ws._clients.get(entity.id);\n\t\tif (!client) return;\n\t\tws._channels.get(channel)?.removeMember(client);\n\t}\n\n\t/**\n\t * Get a client\n\t * @param id - The id of the client\n\t * @param throw_if_nil - Whether to throw an error if the client is not found\n\t * @returns The client\n\t */\n\tpublic static GetClient(id: string, throw_if_nil?: true): I_WebsocketClient;\n\tpublic static GetClient(id: string, throw_if_nil?: false): I_WebsocketClient | undefined;\n\tpublic static GetClient(id: string, throw_if_nil: boolean = true): I_WebsocketClient | undefined {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst client = ws._clients.get(id);\n\t\tif (!client && throw_if_nil) throw new Error(`Client with id ${id} not found`);\n\t\treturn client;\n\t}\n\n\t/**\n\t * Get a channel\n\t * @param id - The id of the channel\n\t * @returns The channel\n\t */\n\tpublic static GetChannel(id: string) {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._channels.get(id);\n\t}\n\n\t/**\n\t * Get all channels\n\t * @returns The channels\n\t */\n\tpublic static GetChannels() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn Array.from(ws._channels.values());\n\t}\n\n\t/**\n\t * Get all clients\n\t * @returns The clients\n\t */\n\tpublic static GetClients() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn Array.from(ws._clients.values());\n\t}\n\n\t/**\n\t * Get the number of clients\n\t * @returns The number of clients\n\t */\n\tpublic static GetClientCount() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._clients.size;\n\t}\n\n\t/**\n\t * Get the number of channels\n\t * @returns The number of channels\n\t */\n\tpublic static GetChannelCount() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._channels.size;\n\t}\n\n\t/**\n\t * Create a client\n\t * @param entity - The entity\n\t * @returns The created client\n\t */\n\tpublic static CreateClient(entity: I_WebsocketEntity): I_WebsocketClient {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws.createClient(entity);\n\t}\n\n\t/**\n\t * Get all connected clients (excluding connecting/disconnecting)\n\t * @returns Array of connected clients\n\t */\n\tpublic static GetConnectedClients(): I_WebsocketClient[] {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn Array.from(ws._clients.values()).filter(\n\t\t\tclient => client.state === \"connected\"\n\t\t);\n\t}\n\n\t/**\n\t * Get client statistics by state\n\t * @returns Object with counts by state\n\t */\n\tpublic static GetClientStats() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\tconst stats = {\n\t\t\ttotal: ws._clients.size,\n\t\t\tconnecting: 0,\n\t\t\tconnected: 0,\n\t\t\tdisconnecting: 0,\n\t\t\tdisconnected: 0,\n\t\t};\n\n\t\tfor (const client of ws._clients.values()) {\n\t\t\tswitch (client.state) {\n\t\t\t\tcase \"connecting\": stats.connecting++; break;\n\t\t\t\tcase \"connected\": stats.connected++; break;\n\t\t\t\tcase \"disconnecting\": stats.disconnecting++; break;\n\t\t\t\tcase \"disconnected\": stats.disconnected++; break;\n\t\t\t}\n\t\t}\n\n\t\treturn stats;\n\t}\n}\n"]}
@@ -19,3 +19,9 @@ export declare enum E_WebsocketMessagePriority {
19
19
  MEDIUM = 1,
20
20
  HIGH = 2
21
21
  }
22
+ export declare enum E_ClientState {
23
+ CONNECTING = "connecting",
24
+ CONNECTED = "connected",
25
+ DISCONNECTING = "disconnecting",
26
+ DISCONNECTED = "disconnected"
27
+ }
@@ -21,4 +21,11 @@ export var E_WebsocketMessagePriority;
21
21
  E_WebsocketMessagePriority[E_WebsocketMessagePriority["MEDIUM"] = 1] = "MEDIUM";
22
22
  E_WebsocketMessagePriority[E_WebsocketMessagePriority["HIGH"] = 2] = "HIGH";
23
23
  })(E_WebsocketMessagePriority || (E_WebsocketMessagePriority = {}));
24
+ export var E_ClientState;
25
+ (function (E_ClientState) {
26
+ E_ClientState["CONNECTING"] = "connecting";
27
+ E_ClientState["CONNECTED"] = "connected";
28
+ E_ClientState["DISCONNECTING"] = "disconnecting";
29
+ E_ClientState["DISCONNECTED"] = "disconnected";
30
+ })(E_ClientState || (E_ClientState = {}));
24
31
  //# sourceMappingURL=websocket.enums.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.enums.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/websocket.enums.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,sBAeX;AAfD,WAAY,sBAAsB;IACjC,+DAAqC,CAAA;IACrC,qEAA2C,CAAA;IAC3C,qEAA2C,CAAA;IAC3C,uEAA6C,CAAA;IAC7C,uEAA6C,CAAA;IAC7C,yEAA+C,CAAA;IAC/C,uCAAa,CAAA;IACb,uCAAa,CAAA;IACb,6CAAmB,CAAA;IACnB,6CAAmB,CAAA;IACnB,iDAAuB,CAAA;IACvB,2CAAiB,CAAA;IACjB,yCAAe,CAAA;IACf,2CAAiB,CAAA;AAClB,CAAC,EAfW,sBAAsB,KAAtB,sBAAsB,QAejC;AAED,MAAM,CAAN,IAAY,0BAIX;AAJD,WAAY,0BAA0B;IACrC,yEAAO,CAAA;IACP,+EAAU,CAAA;IACV,2EAAQ,CAAA;AACT,CAAC,EAJW,0BAA0B,KAA1B,0BAA0B,QAIrC","sourcesContent":["export enum E_WebsocketMessageType {\n\tCLIENT_CONNECTED = \"client.connected\",\n\tCLIENT_DISCONNECTED = \"client.disconnected\",\n\tCLIENT_JOIN_CHANNEL = \"client.join.channel\",\n\tCLIENT_LEAVE_CHANNEL = \"client.leave.channel\",\n\tCLIENT_JOIN_CHANNELS = \"client.join.channels\",\n\tCLIENT_LEAVE_CHANNELS = \"client.leave.channels\",\n\tPING = \"ping\",\n\tPONG = \"pong\",\n\tMESSAGE = \"message\",\n\tWHISPER = \"whisper\",\n\tBROADCAST = \"broadcast\",\n\tPROMPT = \"prompt\",\n\tERROR = \"error\",\n\tSYSTEM = \"system\",\n}\n\nexport enum E_WebsocketMessagePriority {\n\tLOW = 0,\n\tMEDIUM = 1,\n\tHIGH = 2,\n}\n"]}
1
+ {"version":3,"file":"websocket.enums.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/websocket.enums.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,sBAeX;AAfD,WAAY,sBAAsB;IACjC,+DAAqC,CAAA;IACrC,qEAA2C,CAAA;IAC3C,qEAA2C,CAAA;IAC3C,uEAA6C,CAAA;IAC7C,uEAA6C,CAAA;IAC7C,yEAA+C,CAAA;IAC/C,uCAAa,CAAA;IACb,uCAAa,CAAA;IACb,6CAAmB,CAAA;IACnB,6CAAmB,CAAA;IACnB,iDAAuB,CAAA;IACvB,2CAAiB,CAAA;IACjB,yCAAe,CAAA;IACf,2CAAiB,CAAA;AAClB,CAAC,EAfW,sBAAsB,KAAtB,sBAAsB,QAejC;AAED,MAAM,CAAN,IAAY,0BAIX;AAJD,WAAY,0BAA0B;IACrC,yEAAO,CAAA;IACP,+EAAU,CAAA;IACV,2EAAQ,CAAA;AACT,CAAC,EAJW,0BAA0B,KAA1B,0BAA0B,QAIrC;AAED,MAAM,CAAN,IAAY,aAKX;AALD,WAAY,aAAa;IACxB,0CAAyB,CAAA;IACzB,wCAAuB,CAAA;IACvB,gDAA+B,CAAA;IAC/B,8CAA6B,CAAA;AAC9B,CAAC,EALW,aAAa,KAAb,aAAa,QAKxB","sourcesContent":["export enum E_WebsocketMessageType {\n\tCLIENT_CONNECTED = \"client.connected\",\n\tCLIENT_DISCONNECTED = \"client.disconnected\",\n\tCLIENT_JOIN_CHANNEL = \"client.join.channel\",\n\tCLIENT_LEAVE_CHANNEL = \"client.leave.channel\",\n\tCLIENT_JOIN_CHANNELS = \"client.join.channels\",\n\tCLIENT_LEAVE_CHANNELS = \"client.leave.channels\",\n\tPING = \"ping\",\n\tPONG = \"pong\",\n\tMESSAGE = \"message\",\n\tWHISPER = \"whisper\",\n\tBROADCAST = \"broadcast\",\n\tPROMPT = \"prompt\",\n\tERROR = \"error\",\n\tSYSTEM = \"system\",\n}\n\nexport enum E_WebsocketMessagePriority {\n\tLOW = 0,\n\tMEDIUM = 1,\n\tHIGH = 2,\n}\n\nexport enum E_ClientState {\n\tCONNECTING = \"connecting\",\n\tCONNECTED = \"connected\",\n\tDISCONNECTING = \"disconnecting\",\n\tDISCONNECTED = \"disconnected\",\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { ServerWebSocket, WebSocketHandler } from "bun";
2
2
  import Channel from "./Channel";
3
3
  import Websocket from "./Websocket";
4
+ import { E_ClientState } from "./websocket.enums";
4
5
  export type BunWebsocketMessage = string | Buffer<ArrayBufferLike>;
5
6
  export type WebsocketChannel<T extends I_WebsocketChannel = Channel> = Map<string, T>;
6
7
  export type WebsocketClients = Map<string, I_WebsocketClient>;
@@ -88,7 +89,36 @@ export type WebsocketMessage<T extends Record<string, any> = Record<string, any>
88
89
  */
89
90
  [key: string]: any;
90
91
  };
91
- export type WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = WebsocketMessage<T> & WebsocketMessageOptions;
92
+ /**
93
+ * Message structure sent over the wire to clients.
94
+ * This is the actual WebSocket payload format - transport options are NOT included.
95
+ */
96
+ export type WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = {
97
+ /** Message type identifier for client-side routing */
98
+ type: string;
99
+ /** Message payload */
100
+ content: T;
101
+ /** Channel ID where message originated */
102
+ channel?: string;
103
+ /** ISO timestamp when message was created */
104
+ timestamp?: string;
105
+ /** Client information (who sent this) */
106
+ client?: WebsocketEntityData;
107
+ /** Channel metadata (if included) */
108
+ metadata?: Record<string, string>;
109
+ /** Message priority for client-side processing */
110
+ priority?: number;
111
+ /** Expiration timestamp (milliseconds since epoch) */
112
+ expiresAt?: number;
113
+ /** Any additional custom fields */
114
+ [key: string]: any;
115
+ };
116
+ /**
117
+ * @deprecated This type incorrectly mixed transport options with wire format.
118
+ * Use WebsocketStructuredMessage for wire format and WebsocketMessageOptions for options.
119
+ * This will be removed in a future version.
120
+ */
121
+ export type deprecated_WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = WebsocketMessage<T> & WebsocketMessageOptions;
92
122
  export type WebsocketEntityId = string;
93
123
  export type WebsocketEntityName = string;
94
124
  export type WebsocketEntityData = {
@@ -100,15 +130,29 @@ export interface I_WebsocketEntity extends WebsocketEntityData {
100
130
  }
101
131
  export interface I_WebsocketClient extends I_WebsocketEntity {
102
132
  channels: WebsocketChannel<I_WebsocketChannel>;
133
+ state: E_ClientState;
103
134
  send(message: string, options?: WebsocketMessageOptions): void;
104
135
  send(message: WebsocketStructuredMessage): void;
105
136
  subscribe(channel: string): any;
106
- joinChannel(channel: I_WebsocketChannel, send?: boolean): void;
137
+ joinChannel(channel: I_WebsocketChannel, send?: boolean): boolean;
107
138
  leaveChannel(channel: I_WebsocketChannel, send?: boolean): void;
108
139
  joinChannels(channels: I_WebsocketChannel[], send?: boolean): void;
109
140
  leaveChannels(channels?: I_WebsocketChannel[], send?: boolean): void;
110
141
  unsubscribe(channel: string): any;
111
142
  whoami(): WebsocketEntityData;
143
+ canReceiveMessages(): boolean;
144
+ markConnected(): void;
145
+ markDisconnecting(): void;
146
+ markDisconnected(): void;
147
+ getConnectionInfo(): {
148
+ id: string;
149
+ name: string;
150
+ state: E_ClientState;
151
+ connectedAt?: Date;
152
+ disconnectedAt?: Date;
153
+ uptime: number;
154
+ channelCount: number;
155
+ };
112
156
  }
113
157
  export interface I_WebsocketChannelEntity<T extends Websocket = Websocket> extends WebsocketEntityData {
114
158
  ws: T;
@@ -116,6 +160,18 @@ export interface I_WebsocketChannelEntity<T extends Websocket = Websocket> exten
116
160
  export type BroadcastOptions = WebsocketMessageOptions & {
117
161
  debug?: boolean;
118
162
  };
163
+ export type AddMemberResult = {
164
+ success: true;
165
+ client: I_WebsocketClient;
166
+ } | {
167
+ success: false;
168
+ reason: "full" | "already_member" | "error";
169
+ error?: Error;
170
+ };
171
+ export type AddMemberOptions = {
172
+ /** Whether to notify client when channel is full (default: false) */
173
+ notify_when_full?: boolean;
174
+ };
119
175
  export interface I_WebsocketChannel<T extends Websocket = Websocket> extends I_WebsocketChannelEntity<T> {
120
176
  limit: number;
121
177
  members: Map<string, I_WebsocketClient>;
@@ -123,7 +179,8 @@ export interface I_WebsocketChannel<T extends Websocket = Websocket> extends I_W
123
179
  createdAt: Date;
124
180
  broadcast(message: WebsocketStructuredMessage | string, options?: BroadcastOptions): void;
125
181
  hasMember(client: I_WebsocketEntity | string): boolean;
126
- addMember(entity: I_WebsocketClient): I_WebsocketClient | false;
182
+ addMember(entity: I_WebsocketClient, options?: AddMemberOptions): AddMemberResult;
183
+ removeMemberInternal(entity: I_WebsocketClient): void;
127
184
  removeMember(entity: I_WebsocketEntity): I_WebsocketClient | false;
128
185
  getMember(client: I_WebsocketEntity | string): I_WebsocketClient | undefined;
129
186
  getMembers(clients?: string[] | I_WebsocketEntity[]): I_WebsocketClient[];
@@ -1 +1 @@
1
- {"version":3,"file":"websocket.types.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/websocket.types.ts"],"names":[],"mappings":"","sourcesContent":["import { ServerWebSocket, WebSocketHandler } from \"bun\";\nimport Channel from \"./Channel\";\nimport Websocket from \"./Websocket\";\n\nexport type BunWebsocketMessage = string | Buffer<ArrayBufferLike>;\n\nexport type WebsocketChannel<T extends I_WebsocketChannel = Channel> = Map<string, T>;\nexport type WebsocketClients = Map<string, I_WebsocketClient>;\nexport type WebsocketMessageOptions = {\n\t/**\n\t * Additional data to include in the message content\n\t * If an object is provided, it will be merged with the content\n\t * If a primitive value is provided, it will be added as content.data\n\t */\n\tdata?: any;\n\n\t/**\n\t * Client information to include in the message\n\t * Will be added as content.client\n\t */\n\tclient?: Partial<WebsocketEntityData> & {\n\t\t[key: string]: any;\n\t};\n\n\t/**\n\t * Channel metadata to include in the message\n\t * If true, all metadata will be included\n\t * If an array of strings, only the specified keys will be included\n\t */\n\tincludeMetadata?: boolean | string[];\n\n\t/**\n\t * Client IDs to exclude from receiving the broadcast\n\t * Useful for sending messages to all clients except the sender\n\t */\n\texcludeClients?: string[];\n\n\t/**\n\t * Channel to include in the message\n\t * Defaults to the channel of the message\n\t */\n\tchannel?: string;\n\n\t/**\n\t * Whether to include timestamp in the message\n\t * Defaults to true\n\t */\n\tincludeTimestamp?: boolean;\n\n\t/**\n\t * Custom fields to add to the root of the message\n\t * These will be merged with the message object\n\t */\n\tcustomFields?: Record<string, any>;\n\n\t/**\n\t * Transform function to modify the final message before sending\n\t * This is applied after all other processing\n\t */\n\ttransform?: (message: any) => any;\n\n\t/**\n\t * Priority of the message (higher numbers = higher priority)\n\t * Can be used by clients to determine processing order\n\t */\n\tpriority?: number;\n\n\t/**\n\t * Message expiration time in milliseconds since epoch\n\t * Can be used by clients to ignore outdated messages\n\t */\n\texpiresAt?: number;\n\n\t/**\n\t * Metadata to include in the message\n\t * If an array of strings, only the specified keys will be included\n\t */\n\tmetadata?: boolean | string[] | Record<string, string>;\n};\n\nexport type WebsocketMessage<T extends Record<string, any> = Record<string, any>> = {\n\t/**\n\t * Message type identifier used for client-side routing\n\t */\n\ttype: string;\n\t/**\n\t * Message content - can be any data structure\n\t * If a string is provided, it will be wrapped in {message: string}\n\t */\n\tcontent: T;\n\t/**\n\t * Channel ID\n\t */\n\tchannel?: string;\n\t/**\n\t * Timestamp of the message\n\t */\n\ttimestamp?: string;\n\t/**\n\t * Any additional custom fields\n\t */\n\t[key: string]: any;\n};\n\nexport type WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = WebsocketMessage<T> & WebsocketMessageOptions;\n\nexport type WebsocketEntityId = string;\nexport type WebsocketEntityName = string;\nexport type WebsocketEntityData = { id: WebsocketEntityId; name: WebsocketEntityName };\n\nexport interface I_WebsocketEntity extends WebsocketEntityData {\n\tws: ServerWebSocket<WebsocketEntityData>;\n}\n\nexport interface I_WebsocketClient extends I_WebsocketEntity {\n\tchannels: WebsocketChannel<I_WebsocketChannel>;\n\tsend(message: string, options?: WebsocketMessageOptions): void;\n\tsend(message: WebsocketStructuredMessage): void;\n\tsubscribe(channel: string): any;\n\tjoinChannel(channel: I_WebsocketChannel, send?: boolean): void;\n\tleaveChannel(channel: I_WebsocketChannel, send?: boolean): void;\n\tjoinChannels(channels: I_WebsocketChannel[], send?: boolean): void;\n\tleaveChannels(channels?: I_WebsocketChannel[], send?: boolean): void;\n\tunsubscribe(channel: string): any;\n\twhoami(): WebsocketEntityData;\n}\n\nexport interface I_WebsocketChannelEntity<T extends Websocket = Websocket> extends WebsocketEntityData {\n\tws: T;\n}\n\n// New types for the broadcast method\nexport type BroadcastOptions = WebsocketMessageOptions & {\n\tdebug?: boolean;\n};\nexport interface I_WebsocketChannel<T extends Websocket = Websocket> extends I_WebsocketChannelEntity<T> {\n\tlimit: number;\n\tmembers: Map<string, I_WebsocketClient>;\n\tmetadata: Record<string, string>;\n\tcreatedAt: Date;\n\tbroadcast(message: WebsocketStructuredMessage | string, options?: BroadcastOptions): void;\n\thasMember(client: I_WebsocketEntity | string): boolean;\n\taddMember(entity: I_WebsocketClient): I_WebsocketClient | false;\n\tremoveMember(entity: I_WebsocketEntity): I_WebsocketClient | false;\n\tgetMember(client: I_WebsocketEntity | string): I_WebsocketClient | undefined;\n\tgetMembers(clients?: string[] | I_WebsocketEntity[]): I_WebsocketClient[];\n\tgetMetadata(): Record<string, string>;\n\tgetCreatedAt(): Date;\n\tgetId(): string;\n\tgetSize(): number;\n\tgetLimit(): number;\n\tgetName(): string;\n\tcanAddMember(): boolean;\n}\n\n/**\n * Interface for implementing custom WebSocket behavior.\n *\n * @interface I_WebsocketInterface\n *\n * @property {Function} setup - Initializes the WebSocket handler with channels and clients\n *\n * The interface supports three optional handler methods:\n *\n * - `message`: Custom message handler that replaces the default handler\n * - `open`: Connection handler that runs after the default open handler\n * - `close`: Disconnection handler that runs before the default close handler\n */\nexport type WebsocketInterfaceHandlers = Partial<WebSocketHandler<WebsocketEntityData>>;\n\n/**\n * Interface for implementing custom WebSocket behavior.\n *\n * @interface I_WebsocketInterface\n *\n * @property {Function} setup - Initializes the WebSocket handler with channels and clients\n *\n * The interface supports three optional handler methods:\n *\n * - `message`: Custom message handler that replaces the default handler\n * - `open`: Connection handler that runs after the default open handler\n * - `close`: Disconnection handler that runs before the default close handler\n */\nexport interface I_WebsocketInterface {\n\thandlers: (channels: WebsocketChannel, clients: WebsocketClients) => WebsocketInterfaceHandlers;\n}\n"]}
1
+ {"version":3,"file":"websocket.types.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/websocket.types.ts"],"names":[],"mappings":"","sourcesContent":["import { ServerWebSocket, WebSocketHandler } from \"bun\";\nimport Channel from \"./Channel\";\nimport Websocket from \"./Websocket\";\nimport { E_ClientState } from \"./websocket.enums\";\n\nexport type BunWebsocketMessage = string | Buffer<ArrayBufferLike>;\n\nexport type WebsocketChannel<T extends I_WebsocketChannel = Channel> = Map<string, T>;\nexport type WebsocketClients = Map<string, I_WebsocketClient>;\nexport type WebsocketMessageOptions = {\n\t/**\n\t * Additional data to include in the message content\n\t * If an object is provided, it will be merged with the content\n\t * If a primitive value is provided, it will be added as content.data\n\t */\n\tdata?: any;\n\n\t/**\n\t * Client information to include in the message\n\t * Will be added as content.client\n\t */\n\tclient?: Partial<WebsocketEntityData> & {\n\t\t[key: string]: any;\n\t};\n\n\t/**\n\t * Channel metadata to include in the message\n\t * If true, all metadata will be included\n\t * If an array of strings, only the specified keys will be included\n\t */\n\tincludeMetadata?: boolean | string[];\n\n\t/**\n\t * Client IDs to exclude from receiving the broadcast\n\t * Useful for sending messages to all clients except the sender\n\t */\n\texcludeClients?: string[];\n\n\t/**\n\t * Channel to include in the message\n\t * Defaults to the channel of the message\n\t */\n\tchannel?: string;\n\n\t/**\n\t * Whether to include timestamp in the message\n\t * Defaults to true\n\t */\n\tincludeTimestamp?: boolean;\n\n\t/**\n\t * Custom fields to add to the root of the message\n\t * These will be merged with the message object\n\t */\n\tcustomFields?: Record<string, any>;\n\n\t/**\n\t * Transform function to modify the final message before sending\n\t * This is applied after all other processing\n\t */\n\ttransform?: (message: any) => any;\n\n\t/**\n\t * Priority of the message (higher numbers = higher priority)\n\t * Can be used by clients to determine processing order\n\t */\n\tpriority?: number;\n\n\t/**\n\t * Message expiration time in milliseconds since epoch\n\t * Can be used by clients to ignore outdated messages\n\t */\n\texpiresAt?: number;\n\n\t/**\n\t * Metadata to include in the message\n\t * If an array of strings, only the specified keys will be included\n\t */\n\tmetadata?: boolean | string[] | Record<string, string>;\n};\n\nexport type WebsocketMessage<T extends Record<string, any> = Record<string, any>> = {\n\t/**\n\t * Message type identifier used for client-side routing\n\t */\n\ttype: string;\n\t/**\n\t * Message content - can be any data structure\n\t * If a string is provided, it will be wrapped in {message: string}\n\t */\n\tcontent: T;\n\t/**\n\t * Channel ID\n\t */\n\tchannel?: string;\n\t/**\n\t * Timestamp of the message\n\t */\n\ttimestamp?: string;\n\t/**\n\t * Any additional custom fields\n\t */\n\t[key: string]: any;\n};\n\n/**\n * Message structure sent over the wire to clients.\n * This is the actual WebSocket payload format - transport options are NOT included.\n */\nexport type WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = {\n\t/** Message type identifier for client-side routing */\n\ttype: string;\n\n\t/** Message payload */\n\tcontent: T;\n\n\t/** Channel ID where message originated */\n\tchannel?: string;\n\n\t/** ISO timestamp when message was created */\n\ttimestamp?: string;\n\n\t/** Client information (who sent this) */\n\tclient?: WebsocketEntityData;\n\n\t/** Channel metadata (if included) */\n\tmetadata?: Record<string, string>;\n\n\t/** Message priority for client-side processing */\n\tpriority?: number;\n\n\t/** Expiration timestamp (milliseconds since epoch) */\n\texpiresAt?: number;\n\n\t/** Any additional custom fields */\n\t[key: string]: any;\n};\n\n/**\n * @deprecated This type incorrectly mixed transport options with wire format.\n * Use WebsocketStructuredMessage for wire format and WebsocketMessageOptions for options.\n * This will be removed in a future version.\n */\nexport type deprecated_WebsocketStructuredMessage<T extends Record<string, any> = Record<string, any>> = WebsocketMessage<T> & WebsocketMessageOptions;\n\nexport type WebsocketEntityId = string;\nexport type WebsocketEntityName = string;\nexport type WebsocketEntityData = { id: WebsocketEntityId; name: WebsocketEntityName };\n\nexport interface I_WebsocketEntity extends WebsocketEntityData {\n\tws: ServerWebSocket<WebsocketEntityData>;\n}\n\nexport interface I_WebsocketClient extends I_WebsocketEntity {\n\tchannels: WebsocketChannel<I_WebsocketChannel>;\n\tstate: E_ClientState;\n\tsend(message: string, options?: WebsocketMessageOptions): void;\n\tsend(message: WebsocketStructuredMessage): void;\n\tsubscribe(channel: string): any;\n\tjoinChannel(channel: I_WebsocketChannel, send?: boolean): boolean;\n\tleaveChannel(channel: I_WebsocketChannel, send?: boolean): void;\n\tjoinChannels(channels: I_WebsocketChannel[], send?: boolean): void;\n\tleaveChannels(channels?: I_WebsocketChannel[], send?: boolean): void;\n\tunsubscribe(channel: string): any;\n\twhoami(): WebsocketEntityData;\n\tcanReceiveMessages(): boolean;\n\tmarkConnected(): void;\n\tmarkDisconnecting(): void;\n\tmarkDisconnected(): void;\n\tgetConnectionInfo(): { id: string; name: string; state: E_ClientState; connectedAt?: Date; disconnectedAt?: Date; uptime: number; channelCount: number };\n}\n\nexport interface I_WebsocketChannelEntity<T extends Websocket = Websocket> extends WebsocketEntityData {\n\tws: T;\n}\n\n// New types for the broadcast method\nexport type BroadcastOptions = WebsocketMessageOptions & {\n\tdebug?: boolean;\n};\n\n// Result type for addMember operations\nexport type AddMemberResult = { success: true; client: I_WebsocketClient } | { success: false; reason: \"full\" | \"already_member\" | \"error\"; error?: Error };\n\n// Options for addMember operations\nexport type AddMemberOptions = {\n\t/** Whether to notify client when channel is full (default: false) */\n\tnotify_when_full?: boolean;\n};\nexport interface I_WebsocketChannel<T extends Websocket = Websocket> extends I_WebsocketChannelEntity<T> {\n\tlimit: number;\n\tmembers: Map<string, I_WebsocketClient>;\n\tmetadata: Record<string, string>;\n\tcreatedAt: Date;\n\tbroadcast(message: WebsocketStructuredMessage | string, options?: BroadcastOptions): void;\n\thasMember(client: I_WebsocketEntity | string): boolean;\n\taddMember(entity: I_WebsocketClient, options?: AddMemberOptions): AddMemberResult;\n\tremoveMemberInternal(entity: I_WebsocketClient): void;\n\tremoveMember(entity: I_WebsocketEntity): I_WebsocketClient | false;\n\tgetMember(client: I_WebsocketEntity | string): I_WebsocketClient | undefined;\n\tgetMembers(clients?: string[] | I_WebsocketEntity[]): I_WebsocketClient[];\n\tgetMetadata(): Record<string, string>;\n\tgetCreatedAt(): Date;\n\tgetId(): string;\n\tgetSize(): number;\n\tgetLimit(): number;\n\tgetName(): string;\n\tcanAddMember(): boolean;\n}\n\n/**\n * Interface for implementing custom WebSocket behavior.\n *\n * @interface I_WebsocketInterface\n *\n * @property {Function} setup - Initializes the WebSocket handler with channels and clients\n *\n * The interface supports three optional handler methods:\n *\n * - `message`: Custom message handler that replaces the default handler\n * - `open`: Connection handler that runs after the default open handler\n * - `close`: Disconnection handler that runs before the default close handler\n */\nexport type WebsocketInterfaceHandlers = Partial<WebSocketHandler<WebsocketEntityData>>;\n\n/**\n * Interface for implementing custom WebSocket behavior.\n *\n * @interface I_WebsocketInterface\n *\n * @property {Function} setup - Initializes the WebSocket handler with channels and clients\n *\n * The interface supports three optional handler methods:\n *\n * - `message`: Custom message handler that replaces the default handler\n * - `open`: Connection handler that runs after the default open handler\n * - `close`: Disconnection handler that runs before the default close handler\n */\nexport interface I_WebsocketInterface {\n\thandlers: (channels: WebsocketChannel, clients: WebsocketClients) => WebsocketInterfaceHandlers;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "topsyde-utils",
3
- "version": "1.0.205",
3
+ "version": "1.0.207",
4
4
  "description": "A bundle of TypeScript utility classes and functions",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -83,7 +83,7 @@ describe("Websocket Tests", () => {
83
83
  content: { text: "Broadcast to all channels" },
84
84
  };
85
85
 
86
- _Websocket.BraodcastAll(message);
86
+ _Websocket.BroadCastAll(message);
87
87
  // No assertion needed, just checking it doesn't throw
88
88
  });
89
89