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.
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/server/bun/websocket/Channel.d.ts +25 -3
- package/dist/server/bun/websocket/Channel.js +80 -26
- package/dist/server/bun/websocket/Channel.js.map +1 -1
- package/dist/server/bun/websocket/Client.d.ts +34 -1
- package/dist/server/bun/websocket/Client.js +95 -18
- package/dist/server/bun/websocket/Client.js.map +1 -1
- package/dist/server/bun/websocket/Message.d.ts +6 -10
- package/dist/server/bun/websocket/Message.js +31 -32
- package/dist/server/bun/websocket/Message.js.map +1 -1
- package/dist/server/bun/websocket/Websocket.d.ts +34 -4
- package/dist/server/bun/websocket/Websocket.js +70 -12
- package/dist/server/bun/websocket/Websocket.js.map +1 -1
- package/dist/server/bun/websocket/websocket.enums.d.ts +6 -0
- package/dist/server/bun/websocket/websocket.enums.js +7 -0
- package/dist/server/bun/websocket/websocket.enums.js.map +1 -1
- package/dist/server/bun/websocket/websocket.types.d.ts +60 -3
- package/dist/server/bun/websocket/websocket.types.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/app.test.ts +1 -1
- package/src/__tests__/singleton.test.ts +6 -4
- package/src/index.ts +3 -1
- package/src/server/bun/websocket/Channel.ts +89 -36
- package/src/server/bun/websocket/Client.ts +109 -19
- package/src/server/bun/websocket/ISSUES.md +1175 -0
- package/src/server/bun/websocket/Message.ts +36 -49
- package/src/server/bun/websocket/Websocket.ts +71 -12
- package/src/server/bun/websocket/websocket.enums.ts +7 -0
- package/src/server/bun/websocket/websocket.types.ts +58 -3
@@ -1,12 +1,11 @@
|
|
1
1
|
import Guards from "../../../utils/Guards.js";
|
2
|
-
|
3
|
-
constructor
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
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
|
-
}
|
42
|
-
delete output.client;
|
43
|
-
} */
|
40
|
+
}
|
44
41
|
// Include channel metadata if requested
|
45
|
-
if
|
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
|
-
|
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
|
-
|
79
|
-
return
|
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
|
-
|
82
|
-
target.send(
|
82
|
+
static Send(target, message, options) {
|
83
|
+
target.send(Message.Create(message, options));
|
83
84
|
}
|
84
|
-
|
85
|
-
target.send(
|
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":"AAAA,OAAO,MAAM,MAAM,uBAAuB,CAAC;
|
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
|
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
|
-
*
|
135
|
-
* @returns
|
153
|
+
* Get all connected clients (excluding connecting/disconnecting)
|
154
|
+
* @returns Array of connected clients
|
136
155
|
*/
|
137
|
-
static
|
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.
|
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
|
-
|
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
|
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
|
-
*
|
237
|
-
* @returns
|
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
|
240
|
-
const
|
241
|
-
|
242
|
-
|
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
|
-
|
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"]}
|
@@ -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
|
-
|
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):
|
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):
|
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):
|
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