topsyde-utils 1.3.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -31
- package/dist/index.js +1 -27
- package/dist/index.js.map +1 -1
- package/dist/utils/Lib.d.ts +0 -12
- package/dist/utils/Lib.js +0 -65
- package/dist/utils/Lib.js.map +1 -1
- package/dist/websocket.shared.types.d.ts +25 -0
- package/dist/websocket.shared.types.js +4 -0
- package/dist/websocket.shared.types.js.map +1 -0
- package/package.json +1 -22
- package/src/index.ts +2 -51
- package/src/utils/Lib.ts +0 -77
- package/src/websocket.shared.types.ts +27 -0
- package/dist/application.d.ts +0 -18
- package/dist/application.js +0 -60
- package/dist/application.js.map +0 -1
- package/dist/server/base/base.database.d.ts +0 -10
- package/dist/server/base/base.database.js +0 -23
- package/dist/server/base/base.database.js.map +0 -1
- package/dist/server/base/index.d.ts +0 -2
- package/dist/server/base/index.js +0 -5
- package/dist/server/base/index.js.map +0 -1
- package/dist/server/bun/index.d.ts +0 -3
- package/dist/server/bun/index.js +0 -6
- package/dist/server/bun/index.js.map +0 -1
- package/dist/server/bun/router/controller-discovery.d.ts +0 -13
- package/dist/server/bun/router/controller-discovery.js +0 -83
- package/dist/server/bun/router/controller-discovery.js.map +0 -1
- package/dist/server/bun/router/index.d.ts +0 -6
- package/dist/server/bun/router/index.js +0 -9
- package/dist/server/bun/router/index.js.map +0 -1
- package/dist/server/bun/router/router.d.ts +0 -12
- package/dist/server/bun/router/router.internal.d.ts +0 -15
- package/dist/server/bun/router/router.internal.js +0 -51
- package/dist/server/bun/router/router.internal.js.map +0 -1
- package/dist/server/bun/router/router.js +0 -38
- package/dist/server/bun/router/router.js.map +0 -1
- package/dist/server/bun/router/routes.d.ts +0 -5
- package/dist/server/bun/router/routes.js +0 -2
- package/dist/server/bun/router/routes.js.map +0 -1
- package/dist/server/bun/websocket/Channel.d.ts +0 -68
- package/dist/server/bun/websocket/Channel.js +0 -263
- package/dist/server/bun/websocket/Channel.js.map +0 -1
- package/dist/server/bun/websocket/Client.d.ts +0 -87
- package/dist/server/bun/websocket/Client.js +0 -193
- package/dist/server/bun/websocket/Client.js.map +0 -1
- package/dist/server/bun/websocket/Message.d.ts +0 -10
- package/dist/server/bun/websocket/Message.js +0 -103
- package/dist/server/bun/websocket/Message.js.map +0 -1
- package/dist/server/bun/websocket/Websocket.d.ts +0 -171
- package/dist/server/bun/websocket/Websocket.js +0 -336
- package/dist/server/bun/websocket/Websocket.js.map +0 -1
- package/dist/server/bun/websocket/index.d.ts +0 -11
- package/dist/server/bun/websocket/index.js +0 -14
- package/dist/server/bun/websocket/index.js.map +0 -1
- package/dist/server/bun/websocket/websocket.enums.d.ts +0 -27
- package/dist/server/bun/websocket/websocket.enums.js +0 -31
- package/dist/server/bun/websocket/websocket.enums.js.map +0 -1
- package/dist/server/bun/websocket/websocket.guards.d.ts +0 -3
- package/dist/server/bun/websocket/websocket.guards.js +0 -17
- package/dist/server/bun/websocket/websocket.guards.js.map +0 -1
- package/dist/server/bun/websocket/websocket.types.d.ts +0 -235
- package/dist/server/bun/websocket/websocket.types.js +0 -2
- package/dist/server/bun/websocket/websocket.types.js.map +0 -1
- package/dist/server/controller.d.ts +0 -62
- package/dist/server/controller.js +0 -55
- package/dist/server/controller.js.map +0 -1
- package/dist/server/index.d.ts +0 -4
- package/dist/server/index.js +0 -7
- package/dist/server/index.js.map +0 -1
- package/dist/server/service.d.ts +0 -5
- package/dist/server/service.js +0 -38
- package/dist/server/service.js.map +0 -1
- package/src/application.ts +0 -73
- package/src/server/base/base.database.ts +0 -31
- package/src/server/base/index.ts +0 -5
- package/src/server/bun/index.ts +0 -6
- package/src/server/bun/router/controller-discovery.ts +0 -94
- package/src/server/bun/router/index.ts +0 -9
- package/src/server/bun/router/router.internal.ts +0 -64
- package/src/server/bun/router/router.ts +0 -51
- package/src/server/bun/router/routes.ts +0 -7
- package/src/server/bun/websocket/Channel.ts +0 -310
- package/src/server/bun/websocket/Client.ts +0 -243
- package/src/server/bun/websocket/ISSUES.md +0 -1175
- package/src/server/bun/websocket/Message.ts +0 -120
- package/src/server/bun/websocket/Websocket.ts +0 -402
- package/src/server/bun/websocket/index.ts +0 -14
- package/src/server/bun/websocket/websocket.enums.ts +0 -29
- package/src/server/bun/websocket/websocket.guards.ts +0 -22
- package/src/server/bun/websocket/websocket.types.ts +0 -252
- package/src/server/controller.ts +0 -121
- package/src/server/index.ts +0 -7
- package/src/server/service.ts +0 -36
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { E_WebsocketMessageType, E_ClientState } from "./websocket.enums.js";
|
|
2
|
-
import { Guards, Lib } from "../../../utils/index.js";
|
|
3
|
-
import Message from "./Message.js";
|
|
4
|
-
/**
|
|
5
|
-
* Client - Connected WebSocket client with channel membership
|
|
6
|
-
*
|
|
7
|
-
* ## Channel Membership
|
|
8
|
-
* - Maintains own channel list and handles Bun pub/sub subscriptions
|
|
9
|
-
* - `joinChannel()` adds to channel, subscribes, and handles rollback on failure
|
|
10
|
-
* - Always use `channel.addMember(client)` in application code, not `client.joinChannel()` directly
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* // ✅ Correct
|
|
14
|
-
* channel.addMember(client);
|
|
15
|
-
*
|
|
16
|
-
* // ❌ Incorrect - internal use only
|
|
17
|
-
* client.joinChannel(channel);
|
|
18
|
-
*/
|
|
19
|
-
export default class Client {
|
|
20
|
-
set ws(value) {
|
|
21
|
-
this._ws = value;
|
|
22
|
-
}
|
|
23
|
-
get ws() {
|
|
24
|
-
return this._ws;
|
|
25
|
-
}
|
|
26
|
-
set id(value) {
|
|
27
|
-
this._id = value;
|
|
28
|
-
}
|
|
29
|
-
get id() {
|
|
30
|
-
return this._id;
|
|
31
|
-
}
|
|
32
|
-
get name() {
|
|
33
|
-
return this._name;
|
|
34
|
-
}
|
|
35
|
-
set name(value) {
|
|
36
|
-
this._name = value;
|
|
37
|
-
}
|
|
38
|
-
set channels(value) {
|
|
39
|
-
this._channels = value;
|
|
40
|
-
}
|
|
41
|
-
get channels() {
|
|
42
|
-
return this._channels;
|
|
43
|
-
}
|
|
44
|
-
get state() {
|
|
45
|
-
return this._state;
|
|
46
|
-
}
|
|
47
|
-
constructor(entity) {
|
|
48
|
-
this._id = entity.id;
|
|
49
|
-
this._name = entity.name;
|
|
50
|
-
this._ws = entity.ws;
|
|
51
|
-
this._channels = new Map();
|
|
52
|
-
this._state = E_ClientState.CONNECTING;
|
|
53
|
-
}
|
|
54
|
-
canReceiveMessages() {
|
|
55
|
-
return this._state === E_ClientState.CONNECTED || this._state === E_ClientState.DISCONNECTING;
|
|
56
|
-
}
|
|
57
|
-
markConnected() {
|
|
58
|
-
this._state = E_ClientState.CONNECTED;
|
|
59
|
-
this._connectedAt = new Date();
|
|
60
|
-
}
|
|
61
|
-
markDisconnecting() {
|
|
62
|
-
this._state = E_ClientState.DISCONNECTING;
|
|
63
|
-
}
|
|
64
|
-
markDisconnected() {
|
|
65
|
-
this._state = E_ClientState.DISCONNECTED;
|
|
66
|
-
this._disconnectedAt = new Date();
|
|
67
|
-
}
|
|
68
|
-
getConnectionInfo() {
|
|
69
|
-
return {
|
|
70
|
-
id: this.id,
|
|
71
|
-
name: this.name,
|
|
72
|
-
state: this._state,
|
|
73
|
-
connectedAt: this._connectedAt,
|
|
74
|
-
disconnectedAt: this._disconnectedAt,
|
|
75
|
-
uptime: this._connectedAt ? Date.now() - this._connectedAt.getTime() : 0,
|
|
76
|
-
channelCount: this._channels.size,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* HELPER: Track channel on client side (for channel.addMember coordination)
|
|
81
|
-
* Allows channel to update client's internal channel map
|
|
82
|
-
* @internal Used by channel.addMember()
|
|
83
|
-
*/
|
|
84
|
-
trackChannel(channel) {
|
|
85
|
-
this.channels.set(channel.getId(), channel);
|
|
86
|
-
}
|
|
87
|
-
/**
|
|
88
|
-
* HELPER: Untrack channel on client side (for channel.addMember rollback)
|
|
89
|
-
* Allows channel to remove from client's internal channel map during rollback
|
|
90
|
-
* @internal Used by channel.addMember() error handling
|
|
91
|
-
*/
|
|
92
|
-
untrackChannel(channel) {
|
|
93
|
-
this.channels.delete(channel.getId());
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Join a channel (thin wrapper that delegates to channel.addMember)
|
|
97
|
-
* channel.addMember() handles all coordination: membership + subscription + tracking + notification
|
|
98
|
-
*/
|
|
99
|
-
joinChannel(channel, send = true) {
|
|
100
|
-
const channel_id = channel.getId();
|
|
101
|
-
// Check if already joined
|
|
102
|
-
if (this.channels.has(channel_id)) {
|
|
103
|
-
return { success: false, reason: "already_member" };
|
|
104
|
-
}
|
|
105
|
-
// Delegate to channel (which now handles full coordination)
|
|
106
|
-
const result = channel.addMember(this, { notify: send });
|
|
107
|
-
if (!result.success) {
|
|
108
|
-
return { success: false, reason: result.reason };
|
|
109
|
-
}
|
|
110
|
-
return { success: true, reason: "" };
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Leave a channel (thin wrapper that delegates to channel.removeMember)
|
|
114
|
-
* channel.removeMember() handles all coordination: membership removal + unsubscription + tracking removal + notification
|
|
115
|
-
*/
|
|
116
|
-
leaveChannel(channel, send = true) {
|
|
117
|
-
const channel_id = channel.getId();
|
|
118
|
-
// Check if we're in the channel
|
|
119
|
-
if (!this.channels.has(channel_id)) {
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
// Delegate to channel (which now handles full coordination)
|
|
123
|
-
channel.removeMember(this, { notify: send });
|
|
124
|
-
}
|
|
125
|
-
joinChannels(channels, send = true) {
|
|
126
|
-
channels.forEach((channel) => {
|
|
127
|
-
this.joinChannel(channel, false);
|
|
128
|
-
});
|
|
129
|
-
if (send)
|
|
130
|
-
this.send({ type: E_WebsocketMessageType.CLIENT_JOIN_CHANNELS, content: { channels }, client: this.whoami() });
|
|
131
|
-
}
|
|
132
|
-
leaveChannels(channels, send = true) {
|
|
133
|
-
if (!channels)
|
|
134
|
-
channels = Array.from(this.channels.values());
|
|
135
|
-
channels.forEach((channel) => {
|
|
136
|
-
this.leaveChannel(channel, false);
|
|
137
|
-
});
|
|
138
|
-
if (send)
|
|
139
|
-
this.send({ type: E_WebsocketMessageType.CLIENT_LEAVE_CHANNELS, content: { channels }, client: this.whoami() });
|
|
140
|
-
}
|
|
141
|
-
whoami() {
|
|
142
|
-
return { id: this.id, name: this.name };
|
|
143
|
-
}
|
|
144
|
-
send(message, options) {
|
|
145
|
-
// Check state before sending
|
|
146
|
-
if (!this.canReceiveMessages()) {
|
|
147
|
-
Lib.Warn(`Cannot send to client ${this.id} in state ${this._state}`);
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
try {
|
|
151
|
-
if (Guards.IsString(message)) {
|
|
152
|
-
const msg = {
|
|
153
|
-
type: "message",
|
|
154
|
-
content: { message },
|
|
155
|
-
};
|
|
156
|
-
message = Message.Create(msg, options);
|
|
157
|
-
}
|
|
158
|
-
this.ws.send(JSON.stringify({ client: this.whoami(), ...message }));
|
|
159
|
-
}
|
|
160
|
-
catch (error) {
|
|
161
|
-
Lib.Warn(`Failed to send message to client ${this.id}:`, error);
|
|
162
|
-
if (error instanceof Error && error.message.includes("closed")) {
|
|
163
|
-
this.markDisconnected();
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
subscribe(channel) {
|
|
168
|
-
this.ws.subscribe(channel);
|
|
169
|
-
}
|
|
170
|
-
unsubscribe(channel) {
|
|
171
|
-
this.ws.unsubscribe(channel);
|
|
172
|
-
}
|
|
173
|
-
static GetClientType(clients) {
|
|
174
|
-
if (!clients)
|
|
175
|
-
return Client;
|
|
176
|
-
if (clients.size > 0) {
|
|
177
|
-
const firstClient = clients.values().next().value;
|
|
178
|
-
if (firstClient) {
|
|
179
|
-
return firstClient.constructor;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
// Fallback to default Client class
|
|
183
|
-
Lib.Warn("Clients map is empty, using default client class");
|
|
184
|
-
return Client;
|
|
185
|
-
}
|
|
186
|
-
static System() {
|
|
187
|
-
return {
|
|
188
|
-
id: "system",
|
|
189
|
-
name: "System",
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
//# sourceMappingURL=Client.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Client.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Client.ts"],"names":[],"mappings":"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","sourcesContent":["import { ServerWebSocket } from \"bun\";\nimport type {\n\tI_WebsocketClient,\n\tWebsocketEntityData,\n\tWebsocketChannel,\n\tWebsocketStructuredMessage,\n\tI_WebsocketEntity,\n\tI_WebsocketChannel,\n\tWebsocketMessageOptions,\n\tWebsocketMessage,\n} from \"./websocket.types\";\nimport { E_WebsocketMessageType, E_ClientState } from \"./websocket.enums\";\nimport { Guards, Lib } from \"../../../utils\";\nimport Message from \"./Message\";\n\n/**\n * Client - Connected WebSocket client with channel membership\n *\n * ## Channel Membership\n * - Maintains own channel list and handles Bun pub/sub subscriptions\n * - `joinChannel()` adds to channel, subscribes, and handles rollback on failure\n * - Always use `channel.addMember(client)` in application code, not `client.joinChannel()` directly\n *\n * @example\n * // ✅ Correct\n * channel.addMember(client);\n *\n * // ❌ Incorrect - internal use only\n * client.joinChannel(channel);\n */\nexport default class Client implements I_WebsocketClient {\n\tprivate _id: string;\n\tprivate _name: string;\n\tprivate _ws: ServerWebSocket<WebsocketEntityData>;\n\tprivate _channels: WebsocketChannel<I_WebsocketChannel>;\n\tprivate _state: E_ClientState;\n\tprivate _connectedAt?: Date;\n\tprivate _disconnectedAt?: Date;\n\n\tprivate set ws(value: ServerWebSocket<WebsocketEntityData>) {\n\t\tthis._ws = value;\n\t}\n\n\tpublic get ws(): ServerWebSocket<WebsocketEntityData> {\n\t\treturn this._ws;\n\t}\n\n\tprivate set id(value: string) {\n\t\tthis._id = value;\n\t}\n\n\tpublic get id(): string {\n\t\treturn this._id;\n\t}\n\n\tpublic get name(): string {\n\t\treturn this._name;\n\t}\n\n\tprivate set name(value: string) {\n\t\tthis._name = value;\n\t}\n\n\tprivate set channels(value: WebsocketChannel<I_WebsocketChannel>) {\n\t\tthis._channels = value;\n\t}\n\n\tpublic get channels(): WebsocketChannel<I_WebsocketChannel> {\n\t\treturn this._channels;\n\t}\n\n\tpublic get state(): E_ClientState {\n\t\treturn this._state;\n\t}\n\n\tconstructor(entity: I_WebsocketEntity) {\n\t\tthis._id = entity.id;\n\t\tthis._name = entity.name;\n\t\tthis._ws = entity.ws;\n\t\tthis._channels = new Map();\n\t\tthis._state = E_ClientState.CONNECTING;\n\t}\n\n\tpublic canReceiveMessages(): boolean {\n\t\treturn this._state === E_ClientState.CONNECTED || this._state === E_ClientState.DISCONNECTING;\n\t}\n\n\tpublic markConnected(): void {\n\t\tthis._state = E_ClientState.CONNECTED;\n\t\tthis._connectedAt = new Date();\n\t}\n\n\tpublic markDisconnecting(): void {\n\t\tthis._state = E_ClientState.DISCONNECTING;\n\t}\n\n\tpublic markDisconnected(): void {\n\t\tthis._state = E_ClientState.DISCONNECTED;\n\t\tthis._disconnectedAt = new Date();\n\t}\n\n\tpublic getConnectionInfo() {\n\t\treturn {\n\t\t\tid: this.id,\n\t\t\tname: this.name,\n\t\t\tstate: this._state,\n\t\t\tconnectedAt: this._connectedAt,\n\t\t\tdisconnectedAt: this._disconnectedAt,\n\t\t\tuptime: this._connectedAt ? Date.now() - this._connectedAt.getTime() : 0,\n\t\t\tchannelCount: this._channels.size,\n\t\t};\n\t}\n\n\t/**\n\t * HELPER: Track channel on client side (for channel.addMember coordination)\n\t * Allows channel to update client's internal channel map\n\t * @internal Used by channel.addMember()\n\t */\n\tpublic trackChannel(channel: I_WebsocketChannel): void {\n\t\tthis.channels.set(channel.getId(), channel);\n\t}\n\n\t/**\n\t * HELPER: Untrack channel on client side (for channel.addMember rollback)\n\t * Allows channel to remove from client's internal channel map during rollback\n\t * @internal Used by channel.addMember() error handling\n\t */\n\tpublic untrackChannel(channel: I_WebsocketChannel): void {\n\t\tthis.channels.delete(channel.getId());\n\t}\n\n\t/**\n\t * Join a channel (thin wrapper that delegates to channel.addMember)\n\t * channel.addMember() handles all coordination: membership + subscription + tracking + notification\n\t */\n\tpublic joinChannel(channel: I_WebsocketChannel, send: boolean = true): { success: boolean; reason: string } {\n\t\tconst channel_id = channel.getId();\n\n\t\t// Check if already joined\n\t\tif (this.channels.has(channel_id)) {\n\t\t\treturn { success: false, reason: \"already_member\" };\n\t\t}\n\n\t\t// Delegate to channel (which now handles full coordination)\n\t\tconst result = channel.addMember(this, { notify: send });\n\n\t\tif (!result.success) {\n\t\t\treturn { success: false, reason: result.reason };\n\t\t}\n\n\t\treturn { success: true, reason: \"\" };\n\t}\n\n\t/**\n\t * Leave a channel (thin wrapper that delegates to channel.removeMember)\n\t * channel.removeMember() handles all coordination: membership removal + unsubscription + tracking removal + notification\n\t */\n\tpublic leaveChannel(channel: I_WebsocketChannel, send: boolean = true) {\n\t\tconst channel_id = channel.getId();\n\n\t\t// Check if we're in the channel\n\t\tif (!this.channels.has(channel_id)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Delegate to channel (which now handles full coordination)\n\t\tchannel.removeMember(this, { notify: send });\n\t}\n\n\tpublic joinChannels(channels: I_WebsocketChannel[], send: boolean = true) {\n\t\tchannels.forEach((channel) => {\n\t\t\tthis.joinChannel(channel, false);\n\t\t});\n\t\tif (send) this.send({ type: E_WebsocketMessageType.CLIENT_JOIN_CHANNELS, content: { channels }, client: this.whoami() });\n\t}\n\n\tpublic leaveChannels(channels?: I_WebsocketChannel[], send: boolean = true) {\n\t\tif (!channels) channels = Array.from(this.channels.values());\n\t\tchannels.forEach((channel) => {\n\t\t\tthis.leaveChannel(channel, false);\n\t\t});\n\t\tif (send) this.send({ type: E_WebsocketMessageType.CLIENT_LEAVE_CHANNELS, content: { channels }, client: this.whoami() });\n\t}\n\n\tpublic whoami(): { id: string; name: string } {\n\t\treturn { id: this.id, name: this.name };\n\t}\n\n\tpublic send(message: string, options?: WebsocketMessageOptions): void;\n\tpublic send(message: WebsocketStructuredMessage): void;\n\tpublic send(message: WebsocketStructuredMessage | string, options?: WebsocketMessageOptions): void {\n\t\t// Check state before sending\n\t\tif (!this.canReceiveMessages()) {\n\t\t\tLib.Warn(`Cannot send to client ${this.id} in state ${this._state}`);\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tif (Guards.IsString(message)) {\n\t\t\t\tconst msg: WebsocketMessage = {\n\t\t\t\t\ttype: \"message\",\n\t\t\t\t\tcontent: { message },\n\t\t\t\t};\n\t\t\t\tmessage = Message.Create(msg, options);\n\t\t\t}\n\t\t\tthis.ws.send(JSON.stringify({ client: this.whoami(), ...message }));\n\t\t} catch (error) {\n\t\t\tLib.Warn(`Failed to send message to client ${this.id}:`, error);\n\t\t\tif (error instanceof Error && error.message.includes(\"closed\")) {\n\t\t\t\tthis.markDisconnected();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic subscribe(channel: string): void {\n\t\tthis.ws.subscribe(channel);\n\t}\n\n\tpublic unsubscribe(channel: string): void {\n\t\tthis.ws.unsubscribe(channel);\n\t}\n\n\tpublic static GetClientType(clients: Map<string, I_WebsocketClient> | undefined): typeof Client {\n\t\tif (!clients) return Client;\n\t\tif (clients.size > 0) {\n\t\t\tconst firstClient = clients.values().next().value;\n\t\t\tif (firstClient) {\n\t\t\t\treturn firstClient.constructor as typeof Client;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to default Client class\n\t\tLib.Warn(\"Clients map is empty, using default client class\");\n\t\treturn Client;\n\t}\n\n\tpublic static System() {\n\t\treturn <WebsocketEntityData>{\n\t\t\tid: \"system\",\n\t\t\tname: \"System\",\n\t\t};\n\t}\n}\n"]}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { WebsocketStructuredMessage, WebsocketMessage, WebsocketMessageOptions, I_WebsocketClient, WebsocketEntityData } from "./websocket.types";
|
|
2
|
-
export default class Message {
|
|
3
|
-
private static readonly MESSAGE_TEMPLATE;
|
|
4
|
-
private constructor();
|
|
5
|
-
static Create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage;
|
|
6
|
-
static CreateWhisper(message: Omit<WebsocketMessage, "type">, options?: WebsocketMessageOptions): WebsocketStructuredMessage;
|
|
7
|
-
static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T;
|
|
8
|
-
static Send(target: I_WebsocketClient, message: WebsocketMessage, options?: WebsocketMessageOptions): void;
|
|
9
|
-
static Alert(target: I_WebsocketClient, reason: string, client?: WebsocketEntityData): void;
|
|
10
|
-
}
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import Guards from "../../../utils/Guards.js";
|
|
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
|
|
9
|
-
output.type = message.type;
|
|
10
|
-
output.channel = message.channel || options?.channel || "N/A";
|
|
11
|
-
// Process message content based on type
|
|
12
|
-
if (typeof message.content === "string") {
|
|
13
|
-
output.content = { message: message.content };
|
|
14
|
-
}
|
|
15
|
-
else if (typeof message.content === "object" && message.content !== null) {
|
|
16
|
-
output.content = { ...message.content };
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
output.content = {};
|
|
20
|
-
}
|
|
21
|
-
// Process options if provided
|
|
22
|
-
if (options) {
|
|
23
|
-
// Add data if provided
|
|
24
|
-
if (options.data !== undefined) {
|
|
25
|
-
if (typeof options.data === "object" && options.data !== null && !Array.isArray(options.data)) {
|
|
26
|
-
// Merge object data with content
|
|
27
|
-
Object.assign(output.content, options.data);
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
// Set as data property for other types
|
|
31
|
-
output.content.data = options.data;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Add client information if provided
|
|
35
|
-
if (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {
|
|
36
|
-
output.client = {
|
|
37
|
-
id: options.client.id,
|
|
38
|
-
name: options.client.name || "Unknown",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
// Include channel metadata if requested
|
|
42
|
-
// Include channel metadata if provided as an object
|
|
43
|
-
if (options.metadata && Guards.IsObject(options.metadata) && !Guards.IsArray(options.metadata)) {
|
|
44
|
-
output.metadata = options.metadata;
|
|
45
|
-
}
|
|
46
|
-
// Add timestamp if requested (default: true)
|
|
47
|
-
if (options.includeTimestamp !== false) {
|
|
48
|
-
output.timestamp = new Date().toISOString();
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
// Remove timestamp if explicitly disabled
|
|
52
|
-
delete output.timestamp;
|
|
53
|
-
}
|
|
54
|
-
// Add priority if specified
|
|
55
|
-
if (options.priority !== undefined) {
|
|
56
|
-
output.priority = options.priority;
|
|
57
|
-
}
|
|
58
|
-
// Add expiration if specified
|
|
59
|
-
if (options.expiresAt !== undefined) {
|
|
60
|
-
output.expiresAt = options.expiresAt;
|
|
61
|
-
}
|
|
62
|
-
// Add any custom fields to the root of the message
|
|
63
|
-
if (options.customFields) {
|
|
64
|
-
Object.assign(output, options.customFields);
|
|
65
|
-
}
|
|
66
|
-
// Apply custom transformation if provided
|
|
67
|
-
if (options.transform) {
|
|
68
|
-
return options.transform(output);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
output.timestamp = new Date().toISOString();
|
|
73
|
-
}
|
|
74
|
-
return output;
|
|
75
|
-
}
|
|
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);
|
|
81
|
-
}
|
|
82
|
-
static Send(target, message, options) {
|
|
83
|
-
target.send(Message.Create(message, options));
|
|
84
|
-
}
|
|
85
|
-
static Alert(target, reason, client) {
|
|
86
|
-
target.send(Message.Create({
|
|
87
|
-
content: {
|
|
88
|
-
message: reason,
|
|
89
|
-
},
|
|
90
|
-
channel: "alert",
|
|
91
|
-
type: "message",
|
|
92
|
-
}, { client }));
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
// Shared template for all messages
|
|
96
|
-
Message.MESSAGE_TEMPLATE = {
|
|
97
|
-
type: "",
|
|
98
|
-
content: {},
|
|
99
|
-
channel: "",
|
|
100
|
-
timestamp: "",
|
|
101
|
-
};
|
|
102
|
-
export default Message;
|
|
103
|
-
//# sourceMappingURL=Message.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Message.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Message.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,uBAAuB,CAAC;AAG3C,MAAqB,OAAO;IAS3B,+CAA+C;IAC/C,gBAAuB,CAAC;IAEjB,MAAM,CAAC,MAAM,CAAC,OAAyB,EAAE,OAAiC;QAChF,qBAAqB;QACrB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAE3D,6BAA6B;QAC7B,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC3B,MAAM,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC;QAE9D,wCAAwC;QACxC,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,CAAC,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;QAC/C,CAAC;aAAM,IAAI,OAAO,OAAO,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;YAC5E,MAAM,CAAC,OAAO,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QACzC,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,OAAO,GAAG,EAAE,CAAC;QACrB,CAAC;QAED,8BAA8B;QAC9B,IAAI,OAAO,EAAE,CAAC;YACb,uBAAuB;YACvB,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAChC,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/F,iCAAiC;oBACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAC7C,CAAC;qBAAM,CAAC;oBACP,uCAAuC;oBACvC,MAAM,CAAC,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;gBACpC,CAAC;YACF,CAAC;YAED,qCAAqC;YACrC,IAAI,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;gBACnG,MAAM,CAAC,MAAM,GAAG;oBACf,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE;oBACrB,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,IAAI,SAAS;iBACtC,CAAC;YACH,CAAC;YAED,wCAAwC;YACxC,oDAAoD;YACpD,IAAI,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAChG,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YACpC,CAAC;YAED,6CAA6C;YAC7C,IAAI,OAAO,CAAC,gBAAgB,KAAK,KAAK,EAAE,CAAC;gBACxC,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC7C,CAAC;iBAAM,CAAC;gBACP,0CAA0C;gBAC1C,OAAO,MAAM,CAAC,SAAS,CAAC;YACzB,CAAC;YAED,4BAA4B;YAC5B,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACpC,MAAM,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;YACpC,CAAC;YAED,8BAA8B;YAC9B,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;gBACrC,MAAM,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;YACtC,CAAC;YAED,mDAAmD;YACnD,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;gBAC1B,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;YAC7C,CAAC;YAED,0CAA0C;YAC1C,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACvB,OAAO,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;aAAM,CAAC;YACP,MAAM,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,CAAC;QAED,OAAO,MAAM,CAAC;IACf,CAAC;IAEM,MAAM,CAAC,aAAa,CAAC,OAAuC,EAAE,OAAiC;QACrG,OAAO,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;IACrH,CAAC;IAEM,MAAM,CAAC,SAAS,CAAa,OAAmC,EAAE,SAAsD;QAC9H,OAAO,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACjE,CAAC;IAEM,MAAM,CAAC,IAAI,CAAC,MAAyB,EAAE,OAAyB,EAAE,OAAiC;QACzG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;IAEM,MAAM,CAAC,KAAK,CAAC,MAAyB,EAAE,MAAc,EAAE,MAA4B;QAC1F,MAAM,CAAC,IAAI,CACV,OAAO,CAAC,MAAM,CACb;YACC,OAAO,EAAE;gBACR,OAAO,EAAE,MAAM;aACf;YACD,OAAO,EAAE,OAAO;YAChB,IAAI,EAAE,SAAS;SACf,EACD,EAAE,MAAM,EAAE,CACV,CACD,CAAC;IACH,CAAC;;AAlHD,mCAAmC;AACX,wBAAgB,GAAoC;IAC3E,IAAI,EAAE,EAAE;IACR,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,EAAE;IACX,SAAS,EAAE,EAAE;CACb,CAAC;eAPkB,OAAO","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"]}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { Server, ServerWebSocket, WebSocketHandler } from "bun";
|
|
2
|
-
import Singleton from "../../../singleton";
|
|
3
|
-
import Channel from "./Channel";
|
|
4
|
-
import Client from "./Client";
|
|
5
|
-
import type { I_WebsocketClient, I_WebsocketEntity, I_WebsocketInterface, WebsocketChannel, WebsocketEntityData, BunWebsocketMessage, WebsocketStructuredMessage } from "./websocket.types";
|
|
6
|
-
export type WebsocketConstructorOptions = {
|
|
7
|
-
debug?: boolean;
|
|
8
|
-
global_channel_limit?: number;
|
|
9
|
-
};
|
|
10
|
-
export interface I_WebsocketConstructor {
|
|
11
|
-
ws_interface?: I_WebsocketInterface;
|
|
12
|
-
channels?: WebsocketChannel;
|
|
13
|
-
clientClass?: typeof Client;
|
|
14
|
-
channelClass?: typeof Channel;
|
|
15
|
-
options?: WebsocketConstructorOptions;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Websocket - Singleton managing clients, channels, and message routing
|
|
19
|
-
*
|
|
20
|
-
* ## API Design: Static vs Instance
|
|
21
|
-
* - **Static methods**: Use in application code (e.g., `Websocket.Broadcast()`, `Websocket.GetClient()`)
|
|
22
|
-
* - **Instance methods**: Use when extending the class (e.g., `protected createClient()`)
|
|
23
|
-
*
|
|
24
|
-
* Static methods are facades that call the singleton instance internally.
|
|
25
|
-
*
|
|
26
|
-
* @example
|
|
27
|
-
* // Application code - use static methods
|
|
28
|
-
* Websocket.Broadcast("lobby", { type: "chat", content: { message: "Hi!" } });
|
|
29
|
-
*
|
|
30
|
-
* // Extension - override instance methods
|
|
31
|
-
* MyWebsocket extends Websocket:
|
|
32
|
-
* protected createClient(entity) {
|
|
33
|
-
* return new MyCustomClient(entity);
|
|
34
|
-
* }
|
|
35
|
-
*/
|
|
36
|
-
export default class Websocket extends Singleton {
|
|
37
|
-
protected _channels: WebsocketChannel;
|
|
38
|
-
protected _clients: Map<string, I_WebsocketClient>;
|
|
39
|
-
protected _server: Server;
|
|
40
|
-
protected _channelClass: typeof Channel;
|
|
41
|
-
protected _clientClass: typeof Client;
|
|
42
|
-
protected _ws_interface?: I_WebsocketInterface;
|
|
43
|
-
protected _options: WebsocketConstructorOptions;
|
|
44
|
-
protected _ws_interface_handlers: Partial<WebSocketHandler<WebsocketEntityData>>;
|
|
45
|
-
protected _lastId: number;
|
|
46
|
-
protected constructor(options?: I_WebsocketConstructor);
|
|
47
|
-
protected set server(value: Server);
|
|
48
|
-
get server(): Server;
|
|
49
|
-
set(server: Server): void;
|
|
50
|
-
/**
|
|
51
|
-
* Create a new channel
|
|
52
|
-
* @param id - The id of the channel
|
|
53
|
-
* @param name - The name of the channel
|
|
54
|
-
* @param limit - The limit of the channel
|
|
55
|
-
* @returns The created channel
|
|
56
|
-
*/
|
|
57
|
-
createChannel(id: string, name: string, limit?: number): Channel;
|
|
58
|
-
/**
|
|
59
|
-
* Remove a channel
|
|
60
|
-
* @param id - The id of the channel
|
|
61
|
-
*/
|
|
62
|
-
removeChannel(id: string): void;
|
|
63
|
-
/**
|
|
64
|
-
* Create a new channel
|
|
65
|
-
* @param id - The id of the channel
|
|
66
|
-
* @param name - The name of the channel
|
|
67
|
-
* @param limit - The limit of the channel
|
|
68
|
-
* @returns The created channel
|
|
69
|
-
*/
|
|
70
|
-
static CreateChannel(id: string, name: string, limit?: number): Channel<Websocket>;
|
|
71
|
-
handlers(): WebSocketHandler<WebsocketEntityData>;
|
|
72
|
-
private clientMessageReceived;
|
|
73
|
-
private clientConnected;
|
|
74
|
-
private clientDisconnected;
|
|
75
|
-
private handleHeartbeat;
|
|
76
|
-
protected createClient(entity: I_WebsocketEntity): I_WebsocketClient;
|
|
77
|
-
/**
|
|
78
|
-
* Handle the heartbeat
|
|
79
|
-
* @param ws - The websocket
|
|
80
|
-
* @param message - The message
|
|
81
|
-
* @returns True if the heartbeat was handled, false otherwise
|
|
82
|
-
*/
|
|
83
|
-
static Heartbeat(ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage): boolean;
|
|
84
|
-
/**
|
|
85
|
-
* Get the server
|
|
86
|
-
* @returns The server
|
|
87
|
-
*/
|
|
88
|
-
static Server(): Server;
|
|
89
|
-
/**
|
|
90
|
-
* Broadcast a message to a channel
|
|
91
|
-
* @param channel - The channel
|
|
92
|
-
* @param message - The message
|
|
93
|
-
* @param args - The arguments
|
|
94
|
-
*/
|
|
95
|
-
static Broadcast(channel: string, message: WebsocketStructuredMessage, ...args: any[]): void;
|
|
96
|
-
/**
|
|
97
|
-
* Broadcast a message to all channels
|
|
98
|
-
* @param message - The message
|
|
99
|
-
* @param args - The arguments
|
|
100
|
-
*/
|
|
101
|
-
static BroadCastAll(message: WebsocketStructuredMessage, ...args: any[]): void;
|
|
102
|
-
/**
|
|
103
|
-
* Join a channel
|
|
104
|
-
* @param channel - The channel
|
|
105
|
-
* @param entity - The entity
|
|
106
|
-
*/
|
|
107
|
-
static Join(channel: string, entity: I_WebsocketEntity): void;
|
|
108
|
-
/**
|
|
109
|
-
* Leave a channel
|
|
110
|
-
* @param channel - The channel
|
|
111
|
-
* @param entity - The entity
|
|
112
|
-
*/
|
|
113
|
-
static Leave(channel: string, entity: I_WebsocketEntity): void;
|
|
114
|
-
/**
|
|
115
|
-
* Get a client
|
|
116
|
-
* @param id - The id of the client
|
|
117
|
-
* @param throw_if_nil - Whether to throw an error if the client is not found
|
|
118
|
-
* @returns The client
|
|
119
|
-
*/
|
|
120
|
-
static GetClient(id: string, throw_if_nil?: true): I_WebsocketClient;
|
|
121
|
-
static GetClient(id: string, throw_if_nil?: false): I_WebsocketClient | undefined;
|
|
122
|
-
/**
|
|
123
|
-
* Get a channel
|
|
124
|
-
* @param id - The id of the channel
|
|
125
|
-
* @returns The channel
|
|
126
|
-
*/
|
|
127
|
-
static GetChannel(id: string): Channel<Websocket> | undefined;
|
|
128
|
-
/**
|
|
129
|
-
* Get all channels
|
|
130
|
-
* @returns The channels
|
|
131
|
-
*/
|
|
132
|
-
static GetChannels(): Channel<Websocket>[];
|
|
133
|
-
/**
|
|
134
|
-
* Get all clients
|
|
135
|
-
* @returns The clients
|
|
136
|
-
*/
|
|
137
|
-
static GetClients(): I_WebsocketClient[];
|
|
138
|
-
static GetLastId(): number;
|
|
139
|
-
/**
|
|
140
|
-
* Get the number of clients
|
|
141
|
-
* @returns The number of clients
|
|
142
|
-
*/
|
|
143
|
-
static GetClientCount(): number;
|
|
144
|
-
/**
|
|
145
|
-
* Get the number of channels
|
|
146
|
-
* @returns The number of channels
|
|
147
|
-
*/
|
|
148
|
-
static GetChannelCount(): number;
|
|
149
|
-
/**
|
|
150
|
-
* Create a client
|
|
151
|
-
* @param entity - The entity
|
|
152
|
-
* @returns The created client
|
|
153
|
-
*/
|
|
154
|
-
static CreateClient(entity: I_WebsocketEntity): I_WebsocketClient;
|
|
155
|
-
/**
|
|
156
|
-
* Get all connected clients (excluding connecting/disconnecting)
|
|
157
|
-
* @returns Array of connected clients
|
|
158
|
-
*/
|
|
159
|
-
static GetConnectedClients(): I_WebsocketClient[];
|
|
160
|
-
/**
|
|
161
|
-
* Get client statistics by state
|
|
162
|
-
* @returns Object with counts by state
|
|
163
|
-
*/
|
|
164
|
-
static GetClientStats(): {
|
|
165
|
-
total: number;
|
|
166
|
-
connecting: number;
|
|
167
|
-
connected: number;
|
|
168
|
-
disconnecting: number;
|
|
169
|
-
disconnected: number;
|
|
170
|
-
};
|
|
171
|
-
}
|