topsyde-utils 1.1.1 → 1.1.2
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.
|
@@ -52,7 +52,7 @@ export default class Client {
|
|
|
52
52
|
this._state = E_ClientState.CONNECTING;
|
|
53
53
|
}
|
|
54
54
|
canReceiveMessages() {
|
|
55
|
-
return this._state === E_ClientState.CONNECTED;
|
|
55
|
+
return this._state === E_ClientState.CONNECTED || this._state === E_ClientState.DISCONNECTING;
|
|
56
56
|
}
|
|
57
57
|
markConnected() {
|
|
58
58
|
this._state = E_ClientState.CONNECTED;
|
|
@@ -1 +1 @@
|
|
|
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;\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\tpublic joinChannel(channel: I_WebsocketChannel, send: boolean = true): boolean {\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 false;\n\t\t}\n\n\t\t// Try to add to channel first\n\t\tconst result = channel.addMember(this);\n\t\tif (!result.success) {\n\t\t\treturn false; // Channel full, already member, or other issue\n\t\t}\n\n\t\ttry {\n\t\t\t// Subscribe to channel's pub/sub topic\n\t\t\tthis.subscribe(channel_id);\n\t\t\tthis.channels.set(channel_id, channel);\n\n\t\t\t// Send join notification\n\t\t\tif (send) {\n\t\t\t\tthis.send({\n\t\t\t\t\ttype: E_WebsocketMessageType.CLIENT_JOIN_CHANNEL,\n\t\t\t\t\tcontent: { message: \"Welcome to the channel\" },\n\t\t\t\t\tchannel: channel_id,\n\t\t\t\t\tclient: this.whoami(),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\t// Rollback channel membership on failure\n\t\t\tchannel.removeMemberInternal(this);\n\t\t\tthis.channels.delete(channel_id);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpublic leaveChannel(channel: I_WebsocketChannel, send: boolean = true) {\n\t\tconst channel_id = channel.getId();\n\t\tthis.channels.delete(channel_id);\n\t\tthis.unsubscribe(channel_id);\n\t\tif (send)\n\t\t\tthis.send({\n\t\t\t\ttype: E_WebsocketMessageType.CLIENT_LEAVE_CHANNEL,\n\t\t\t\tcontent: { message: \"Left the channel\" },\n\t\t\t\tchannel: channel_id,\n\t\t\t\tclient: this.whoami(),\n\t\t\t});\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
|
+
{"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\tpublic joinChannel(channel: I_WebsocketChannel, send: boolean = true): boolean {\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 false;\n\t\t}\n\n\t\t// Try to add to channel first\n\t\tconst result = channel.addMember(this);\n\t\tif (!result.success) {\n\t\t\treturn false; // Channel full, already member, or other issue\n\t\t}\n\n\t\ttry {\n\t\t\t// Subscribe to channel's pub/sub topic\n\t\t\tthis.subscribe(channel_id);\n\t\t\tthis.channels.set(channel_id, channel);\n\n\t\t\t// Send join notification\n\t\t\tif (send) {\n\t\t\t\tthis.send({\n\t\t\t\t\ttype: E_WebsocketMessageType.CLIENT_JOIN_CHANNEL,\n\t\t\t\t\tcontent: { message: \"Welcome to the channel\" },\n\t\t\t\t\tchannel: channel_id,\n\t\t\t\t\tclient: this.whoami(),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\t// Rollback channel membership on failure\n\t\t\tchannel.removeMemberInternal(this);\n\t\t\tthis.channels.delete(channel_id);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpublic leaveChannel(channel: I_WebsocketChannel, send: boolean = true) {\n\t\tconst channel_id = channel.getId();\n\t\tthis.channels.delete(channel_id);\n\t\tthis.unsubscribe(channel_id);\n\t\tif (send)\n\t\t\tthis.send({\n\t\t\t\ttype: E_WebsocketMessageType.CLIENT_LEAVE_CHANNEL,\n\t\t\t\tcontent: { message: \"Left the channel\" },\n\t\t\t\tchannel: channel_id,\n\t\t\t\tclient: this.whoami(),\n\t\t\t});\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"]}
|
package/package.json
CHANGED
|
@@ -82,7 +82,7 @@ export default class Client implements I_WebsocketClient {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
public canReceiveMessages(): boolean {
|
|
85
|
-
return this._state === E_ClientState.CONNECTED;
|
|
85
|
+
return this._state === E_ClientState.CONNECTED || this._state === E_ClientState.DISCONNECTING;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
public markConnected(): void {
|