topsyde-utils 1.3.0 → 1.3.1
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/server/bun/websocket/Channel.d.ts +1 -0
- package/dist/server/bun/websocket/Channel.js +8 -0
- package/dist/server/bun/websocket/Channel.js.map +1 -1
- package/dist/server/bun/websocket/Websocket.js +4 -0
- package/dist/server/bun/websocket/Websocket.js.map +1 -1
- package/package.json +1 -1
- package/src/server/bun/websocket/Channel.ts +9 -0
- package/src/server/bun/websocket/Websocket.ts +3 -0
|
@@ -63,5 +63,6 @@ export default class Channel<T extends Websocket = Websocket> implements I_Webso
|
|
|
63
63
|
getLimit(): number;
|
|
64
64
|
getSize(): number;
|
|
65
65
|
canAddMember(): boolean;
|
|
66
|
+
delete(): void;
|
|
66
67
|
static GetChannelType(channels: WebsocketChannel<I_WebsocketChannel> | undefined): typeof Channel;
|
|
67
68
|
}
|
|
@@ -234,6 +234,14 @@ export default class Channel {
|
|
|
234
234
|
const size = this.getSize();
|
|
235
235
|
return size < this.limit;
|
|
236
236
|
}
|
|
237
|
+
delete() {
|
|
238
|
+
//first remove all members
|
|
239
|
+
this.members.forEach((member) => {
|
|
240
|
+
this.removeMember(member);
|
|
241
|
+
});
|
|
242
|
+
//then clear members map
|
|
243
|
+
this.members.clear();
|
|
244
|
+
}
|
|
237
245
|
static GetChannelType(channels) {
|
|
238
246
|
if (!channels)
|
|
239
247
|
return Channel;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Channel.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Channel.ts"],"names":[],"mappings":"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","sourcesContent":["import { Guards, Lib } from \"../../../utils\";\nimport Message from \"./Message\";\nimport Websocket from \"./Websocket\";\nimport type {\n\tBroadcastOptions,\n\tI_WebsocketChannel,\n\tI_WebsocketClient,\n\tI_WebsocketEntity,\n\tWebsocketChannel,\n\tWebsocketMessage,\n\tAddMemberResult,\n\tAddMemberOptions,\n\tRemoveMemberOptions,\n} from \"./websocket.types\";\nimport { E_WebsocketMessageType } from \"./websocket.enums\";\n\n/**\n * Channel - Pub/sub topic for WebSocket clients\n *\n * ## Membership Contract\n * - `addMember()` validates capacity and adds to `members` map\n * - Client drives join via `joinChannel()` which subscribes and handles rollback\n * - If subscription fails, membership is automatically rolled back\n * - Member count never exceeds `limit`\n *\n * @example\n * const channel = new Channel(\"game-1\", \"Game Room\", ws, 10);\n * const result = channel.addMember(client);\n * if (result.success) {\n * channel.broadcast({ type: \"player.joined\", content: { player: client.whoami() } });\n * }\n */\nexport default class Channel<T extends Websocket = Websocket> implements I_WebsocketChannel<T> {\n\tpublic createdAt: Date = new Date();\n\tpublic id: string;\n\tpublic name: string;\n\tpublic limit: number;\n\tpublic members: Map<string, I_WebsocketClient>;\n\tpublic metadata: Record<string, string>;\n\tpublic ws: T;\n\n\tconstructor(id: string, name: string, ws: T, limit?: number, members?: Map<string, I_WebsocketClient>, metadata?: Record<string, string>) {\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t\tthis.limit = limit ?? 5;\n\t\tthis.members = members ?? new Map();\n\t\tthis.metadata = metadata ?? {};\n\t\tthis.ws = ws;\n\t}\n\n\tpublic broadcast(message: WebsocketMessage | string, options?: BroadcastOptions) {\n\t\tif (Guards.IsString(message)) {\n\t\t\tconst msg: WebsocketMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\tcontent: { message },\n\t\t\t};\n\t\t\tmessage = msg;\n\t\t}\n\n\t\tconst output = Message.Create(message, { ...options, channel: this.id });\n\n\t\t// Include channel metadata if requested\n\t\tif (options?.includeMetadata) {\n\t\t\toutput.metadata = options.includeMetadata === true ? this.getMetadata() : this.getFilteredMetadata(options.includeMetadata);\n\t\t}\n\n\t\tconst serializedMessage = Message.Serialize(output);\n\n\t\t// If we need to exclude clients, send individually to prevent excluded clients from receiving\n\t\tif (options?.excludeClients && options.excludeClients.length > 0) {\n\t\t\tconst excludeSet = new Set(options.excludeClients); // O(1) lookup\n\n\t\t\tfor (const [clientId, client] of this.members) {\n\t\t\t\tif (!excludeSet.has(clientId)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tclient.ws.send(serializedMessage);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tLib.Warn(`Failed to send to client ${clientId}:`, error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise use pub/sub for everyone\n\t\tthis.ws.server.publish(this.id, serializedMessage);\n\t}\n\n\t// Helper method for filtered metadata\n\tprivate getFilteredMetadata(keys: string[]) {\n\t\tconst metadata = this.getMetadata();\n\t\tconst filtered: Record<string, string> = {};\n\n\t\tfor (const key of keys) {\n\t\t\tif (metadata[key] !== undefined) {\n\t\t\t\tfiltered[key] = metadata[key];\n\t\t\t}\n\t\t}\n\n\t\treturn filtered;\n\t}\n\n\tpublic hasMember(client: I_WebsocketEntity | string) {\n\t\tif (typeof client === \"string\") return this.members.has(client);\n\t\treturn this.members.has(client.id);\n\t}\n\n\t/**\n\t * ATOMIC: Add member to channel (membership only, no side effects)\n\t * Internal method used for rollback-safe operations\n\t * @internal\n\t */\n\tprivate addToMembersMap(client: I_WebsocketClient, options?: AddMemberOptions): AddMemberResult {\n\t\t// Check if already a member\n\t\tif (this.members.has(client.id)) {\n\t\t\treturn { success: false, reason: \"already_member\" };\n\t\t}\n\n\t\t// Check capacity\n\t\tif (!this.canAddMember()) {\n\t\t\t// Optionally notify client why they can't join\n\t\t\tif (options?.notify_when_full) {\n\t\t\t\tthis.notifyChannelFull(client);\n\t\t\t}\n\t\t\treturn { success: false, reason: \"full\" };\n\t\t}\n\n\t\ttry {\n\t\t\tthis.members.set(client.id, client);\n\t\t\treturn { success: true, client };\n\t\t} catch (error) {\n\t\t\t// Rollback\n\t\t\tthis.members.delete(client.id);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\treason: \"error\",\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Add a client to this channel with full coordination\n\t * Handles: membership + WebSocket subscription + client-side tracking + optional notification\n\t * This ensures two-way coordination between channel and client\n\t */\n\tpublic addMember(client: I_WebsocketClient, options?: AddMemberOptions): AddMemberResult {\n\t\t// 1. Atomic membership add\n\t\tconst result = this.addToMembersMap(client, options);\n\t\tif (!result.success) {\n\t\t\treturn result;\n\t\t}\n\n\t\ttry {\n\t\t\t// 2. Subscribe client's WebSocket to channel pub/sub topic\n\t\t\t// CRITICAL: Without this, client won't receive channel.broadcast() messages\n\t\t\tclient.subscribe(this.id);\n\n\t\t\t// 3. Track channel on client side (client's channels map)\n\t\t\tclient.trackChannel(this);\n\n\t\t\t// 4. Optional welcome notification\n\t\t\tif (options?.notify) {\n\t\t\t\tclient.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: this.id,\n\t\t\t\t\tclient: client.whoami(),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Rollback on failure: remove membership + unsubscribe + untrack\n\t\t\tthis.removeFromMembersMap(client);\n\t\t\tclient.unsubscribe(this.id);\n\t\t\tclient.untrackChannel(this);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpublic addMembers(clients: I_WebsocketClient[], options?: AddMemberOptions): AddMemberResult[] {\n\t\tconst results: AddMemberResult[] = [];\n\t\tfor (const client of clients) {\n\t\t\tconst result = this.addMember(client, options);\n\t\t\tresults.push(result);\n\t\t\tif (!result.success) {\n\t\t\t\t// Stop adding further members on failure\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate notifyChannelFull(client: I_WebsocketClient): void {\n\t\tclient.send({\n\t\t\ttype: E_WebsocketMessageType.ERROR,\n\t\t\tcontent: {\n\t\t\t\tmessage: `Channel \"${this.name}\" is full (${this.limit} members)`,\n\t\t\t\tcode: \"CHANNEL_FULL\",\n\t\t\t\tchannel: this.id,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Internal method to remove a member without triggering client-side cleanup.\n\t * Used for rollback operations when joinChannel fails.\n\t * @internal\n\t */\n\tpublic removeFromMembersMap(client: I_WebsocketClient): void {\n\t\tthis.members.delete(client.id);\n\t}\n\n\t/**\n\t * Remove a client from this channel with full coordination\n\t * Handles: membership removal + WebSocket unsubscription + client-side tracking removal + optional notification\n\t * This ensures two-way coordination between channel and client\n\t */\n\tpublic removeMember(entity: I_WebsocketEntity, options?: RemoveMemberOptions) {\n\t\t// 1. Check if member exists\n\t\tif (!this.members.has(entity.id)) return false;\n\t\tconst client = this.members.get(entity.id);\n\t\tif (!client) return false;\n\n\t\t// 2. Remove from channel members (atomic operation)\n\t\tthis.removeFromMembersMap(client);\n\n\t\t// 3. Unsubscribe client's WebSocket from channel pub/sub topic\n\t\tclient.unsubscribe(this.id);\n\n\t\t// 4. Untrack channel on client side (remove from client's channels map)\n\t\tclient.untrackChannel(this);\n\n\t\t// 5. Optional goodbye notification\n\t\tif (options?.notify) {\n\t\t\tclient.send({\n\t\t\t\ttype: E_WebsocketMessageType.CLIENT_LEAVE_CHANNEL,\n\t\t\t\tcontent: { message: \"You left the channel\" },\n\t\t\t\tchannel: this.id,\n\t\t\t\tclient: client.whoami(),\n\t\t\t});\n\t\t}\n\n\t\treturn client;\n\t}\n\n\tpublic getMember(client: I_WebsocketEntity | string) {\n\t\tif (typeof client === \"string\") return this.members.get(client);\n\t\treturn this.members.get(client.id);\n\t}\n\n\tpublic getMembers(clients?: string[] | I_WebsocketEntity[]): I_WebsocketClient[] {\n\t\tif (!clients) return Array.from(this.members.values());\n\t\treturn clients.map((client) => this.getMember(client)).filter((client) => client !== undefined) as I_WebsocketClient[];\n\t}\n\n\tpublic getMetadata() {\n\t\treturn this.metadata;\n\t}\n\n\tpublic getCreatedAt() {\n\t\treturn this.createdAt;\n\t}\n\n\tpublic getId() {\n\t\treturn this.id;\n\t}\n\n\tpublic getName() {\n\t\treturn this.name;\n\t}\n\n\tpublic getLimit() {\n\t\treturn this.limit;\n\t}\n\n\tpublic getSize() {\n\t\treturn this.members.size;\n\t}\n\n\tpublic canAddMember() {\n\t\tconst size = this.getSize();\n\t\treturn size < this.limit;\n\t}\n\n\tpublic static GetChannelType(channels: WebsocketChannel<I_WebsocketChannel> | undefined) {\n\t\tif (!channels) return Channel;\n\t\tif (channels.size > 0) {\n\t\t\tconst firstChannel = channels.values().next().value;\n\t\t\tif (firstChannel) {\n\t\t\t\treturn firstChannel.constructor as typeof Channel;\n\t\t\t} else {\n\t\t\t\treturn Channel;\n\t\t\t}\n\t\t} else {\n\t\t\tLib.Warn(\"Channels are empty, using default channel class\");\n\t\t\treturn Channel;\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Channel.js","sourceRoot":"","sources":["../../../../src/server/bun/websocket/Channel.ts"],"names":[],"mappings":"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","sourcesContent":["import { Guards, Lib } from \"../../../utils\";\nimport Message from \"./Message\";\nimport Websocket from \"./Websocket\";\nimport type {\n\tBroadcastOptions,\n\tI_WebsocketChannel,\n\tI_WebsocketClient,\n\tI_WebsocketEntity,\n\tWebsocketChannel,\n\tWebsocketMessage,\n\tAddMemberResult,\n\tAddMemberOptions,\n\tRemoveMemberOptions,\n} from \"./websocket.types\";\nimport { E_WebsocketMessageType } from \"./websocket.enums\";\n\n/**\n * Channel - Pub/sub topic for WebSocket clients\n *\n * ## Membership Contract\n * - `addMember()` validates capacity and adds to `members` map\n * - Client drives join via `joinChannel()` which subscribes and handles rollback\n * - If subscription fails, membership is automatically rolled back\n * - Member count never exceeds `limit`\n *\n * @example\n * const channel = new Channel(\"game-1\", \"Game Room\", ws, 10);\n * const result = channel.addMember(client);\n * if (result.success) {\n * channel.broadcast({ type: \"player.joined\", content: { player: client.whoami() } });\n * }\n */\nexport default class Channel<T extends Websocket = Websocket> implements I_WebsocketChannel<T> {\n\tpublic createdAt: Date = new Date();\n\tpublic id: string;\n\tpublic name: string;\n\tpublic limit: number;\n\tpublic members: Map<string, I_WebsocketClient>;\n\tpublic metadata: Record<string, string>;\n\tpublic ws: T;\n\n\tconstructor(id: string, name: string, ws: T, limit?: number, members?: Map<string, I_WebsocketClient>, metadata?: Record<string, string>) {\n\t\tthis.id = id;\n\t\tthis.name = name;\n\t\tthis.limit = limit ?? 5;\n\t\tthis.members = members ?? new Map();\n\t\tthis.metadata = metadata ?? {};\n\t\tthis.ws = ws;\n\t}\n\n\tpublic broadcast(message: WebsocketMessage | string, options?: BroadcastOptions) {\n\t\tif (Guards.IsString(message)) {\n\t\t\tconst msg: WebsocketMessage = {\n\t\t\t\ttype: \"message\",\n\t\t\t\tcontent: { message },\n\t\t\t};\n\t\t\tmessage = msg;\n\t\t}\n\n\t\tconst output = Message.Create(message, { ...options, channel: this.id });\n\n\t\t// Include channel metadata if requested\n\t\tif (options?.includeMetadata) {\n\t\t\toutput.metadata = options.includeMetadata === true ? this.getMetadata() : this.getFilteredMetadata(options.includeMetadata);\n\t\t}\n\n\t\tconst serializedMessage = Message.Serialize(output);\n\n\t\t// If we need to exclude clients, send individually to prevent excluded clients from receiving\n\t\tif (options?.excludeClients && options.excludeClients.length > 0) {\n\t\t\tconst excludeSet = new Set(options.excludeClients); // O(1) lookup\n\n\t\t\tfor (const [clientId, client] of this.members) {\n\t\t\t\tif (!excludeSet.has(clientId)) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tclient.ws.send(serializedMessage);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tLib.Warn(`Failed to send to client ${clientId}:`, error);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Otherwise use pub/sub for everyone\n\t\tthis.ws.server.publish(this.id, serializedMessage);\n\t}\n\n\t// Helper method for filtered metadata\n\tprivate getFilteredMetadata(keys: string[]) {\n\t\tconst metadata = this.getMetadata();\n\t\tconst filtered: Record<string, string> = {};\n\n\t\tfor (const key of keys) {\n\t\t\tif (metadata[key] !== undefined) {\n\t\t\t\tfiltered[key] = metadata[key];\n\t\t\t}\n\t\t}\n\n\t\treturn filtered;\n\t}\n\n\tpublic hasMember(client: I_WebsocketEntity | string) {\n\t\tif (typeof client === \"string\") return this.members.has(client);\n\t\treturn this.members.has(client.id);\n\t}\n\n\t/**\n\t * ATOMIC: Add member to channel (membership only, no side effects)\n\t * Internal method used for rollback-safe operations\n\t * @internal\n\t */\n\tprivate addToMembersMap(client: I_WebsocketClient, options?: AddMemberOptions): AddMemberResult {\n\t\t// Check if already a member\n\t\tif (this.members.has(client.id)) {\n\t\t\treturn { success: false, reason: \"already_member\" };\n\t\t}\n\n\t\t// Check capacity\n\t\tif (!this.canAddMember()) {\n\t\t\t// Optionally notify client why they can't join\n\t\t\tif (options?.notify_when_full) {\n\t\t\t\tthis.notifyChannelFull(client);\n\t\t\t}\n\t\t\treturn { success: false, reason: \"full\" };\n\t\t}\n\n\t\ttry {\n\t\t\tthis.members.set(client.id, client);\n\t\t\treturn { success: true, client };\n\t\t} catch (error) {\n\t\t\t// Rollback\n\t\t\tthis.members.delete(client.id);\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\treason: \"error\",\n\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t};\n\t\t}\n\t}\n\n\t/**\n\t * Add a client to this channel with full coordination\n\t * Handles: membership + WebSocket subscription + client-side tracking + optional notification\n\t * This ensures two-way coordination between channel and client\n\t */\n\tpublic addMember(client: I_WebsocketClient, options?: AddMemberOptions): AddMemberResult {\n\t\t// 1. Atomic membership add\n\t\tconst result = this.addToMembersMap(client, options);\n\t\tif (!result.success) {\n\t\t\treturn result;\n\t\t}\n\n\t\ttry {\n\t\t\t// 2. Subscribe client's WebSocket to channel pub/sub topic\n\t\t\t// CRITICAL: Without this, client won't receive channel.broadcast() messages\n\t\t\tclient.subscribe(this.id);\n\n\t\t\t// 3. Track channel on client side (client's channels map)\n\t\t\tclient.trackChannel(this);\n\n\t\t\t// 4. Optional welcome notification\n\t\t\tif (options?.notify) {\n\t\t\t\tclient.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: this.id,\n\t\t\t\t\tclient: client.whoami(),\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Rollback on failure: remove membership + unsubscribe + untrack\n\t\t\tthis.removeFromMembersMap(client);\n\t\t\tclient.unsubscribe(this.id);\n\t\t\tclient.untrackChannel(this);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tpublic addMembers(clients: I_WebsocketClient[], options?: AddMemberOptions): AddMemberResult[] {\n\t\tconst results: AddMemberResult[] = [];\n\t\tfor (const client of clients) {\n\t\t\tconst result = this.addMember(client, options);\n\t\t\tresults.push(result);\n\t\t\tif (!result.success) {\n\t\t\t\t// Stop adding further members on failure\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn results;\n\t}\n\n\tprivate notifyChannelFull(client: I_WebsocketClient): void {\n\t\tclient.send({\n\t\t\ttype: E_WebsocketMessageType.ERROR,\n\t\t\tcontent: {\n\t\t\t\tmessage: `Channel \"${this.name}\" is full (${this.limit} members)`,\n\t\t\t\tcode: \"CHANNEL_FULL\",\n\t\t\t\tchannel: this.id,\n\t\t\t},\n\t\t});\n\t}\n\n\t/**\n\t * Internal method to remove a member without triggering client-side cleanup.\n\t * Used for rollback operations when joinChannel fails.\n\t * @internal\n\t */\n\tpublic removeFromMembersMap(client: I_WebsocketClient): void {\n\t\tthis.members.delete(client.id);\n\t}\n\n\t/**\n\t * Remove a client from this channel with full coordination\n\t * Handles: membership removal + WebSocket unsubscription + client-side tracking removal + optional notification\n\t * This ensures two-way coordination between channel and client\n\t */\n\tpublic removeMember(entity: I_WebsocketEntity, options?: RemoveMemberOptions) {\n\t\t// 1. Check if member exists\n\t\tif (!this.members.has(entity.id)) return false;\n\t\tconst client = this.members.get(entity.id);\n\t\tif (!client) return false;\n\n\t\t// 2. Remove from channel members (atomic operation)\n\t\tthis.removeFromMembersMap(client);\n\n\t\t// 3. Unsubscribe client's WebSocket from channel pub/sub topic\n\t\tclient.unsubscribe(this.id);\n\n\t\t// 4. Untrack channel on client side (remove from client's channels map)\n\t\tclient.untrackChannel(this);\n\n\t\t// 5. Optional goodbye notification\n\t\tif (options?.notify) {\n\t\t\tclient.send({\n\t\t\t\ttype: E_WebsocketMessageType.CLIENT_LEAVE_CHANNEL,\n\t\t\t\tcontent: { message: \"You left the channel\" },\n\t\t\t\tchannel: this.id,\n\t\t\t\tclient: client.whoami(),\n\t\t\t});\n\t\t}\n\n\t\treturn client;\n\t}\n\n\tpublic getMember(client: I_WebsocketEntity | string) {\n\t\tif (typeof client === \"string\") return this.members.get(client);\n\t\treturn this.members.get(client.id);\n\t}\n\n\tpublic getMembers(clients?: string[] | I_WebsocketEntity[]): I_WebsocketClient[] {\n\t\tif (!clients) return Array.from(this.members.values());\n\t\treturn clients.map((client) => this.getMember(client)).filter((client) => client !== undefined) as I_WebsocketClient[];\n\t}\n\n\tpublic getMetadata() {\n\t\treturn this.metadata;\n\t}\n\n\tpublic getCreatedAt() {\n\t\treturn this.createdAt;\n\t}\n\n\tpublic getId() {\n\t\treturn this.id;\n\t}\n\n\tpublic getName() {\n\t\treturn this.name;\n\t}\n\n\tpublic getLimit() {\n\t\treturn this.limit;\n\t}\n\n\tpublic getSize() {\n\t\treturn this.members.size;\n\t}\n\n\tpublic canAddMember() {\n\t\tconst size = this.getSize();\n\t\treturn size < this.limit;\n\t}\n\n\tpublic delete() {\n\t\t//first remove all members\n\t\tthis.members.forEach((member) => {\n\t\t\tthis.removeMember(member);\n\t\t});\n\t\t//then clear members map\n\t\tthis.members.clear();\n\t}\n\n\tpublic static GetChannelType(channels: WebsocketChannel<I_WebsocketChannel> | undefined) {\n\t\tif (!channels) return Channel;\n\t\tif (channels.size > 0) {\n\t\t\tconst firstChannel = channels.values().next().value;\n\t\t\tif (firstChannel) {\n\t\t\t\treturn firstChannel.constructor as typeof Channel;\n\t\t\t} else {\n\t\t\t\treturn Channel;\n\t\t\t}\n\t\t} else {\n\t\t\tLib.Warn(\"Channels are empty, using default channel class\");\n\t\t\treturn Channel;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -141,6 +141,10 @@ export default class Websocket extends Singleton {
|
|
|
141
141
|
* @param id - The id of the channel
|
|
142
142
|
*/
|
|
143
143
|
removeChannel(id) {
|
|
144
|
+
const channel = this._channels.get(id);
|
|
145
|
+
if (!channel)
|
|
146
|
+
return;
|
|
147
|
+
channel.delete();
|
|
144
148
|
this._channels.delete(id);
|
|
145
149
|
}
|
|
146
150
|
/**
|
|
@@ -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\tglobal_channel_limit?: number;\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\tprotected _lastId = 1;\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\", this._options.global_channel_limit ?? 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): Channel {\n\t\tif (this._channels.has(id)) return this._channels.get(id) as Channel;\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\ttry {\n\t\t\tif (Websocket.Heartbeat(ws, message)) return;\n\n\t\t\tif (this._ws_interface_handlers.message) return this._ws_interface_handlers.message(ws, message);\n\n\t\t\tws.send(\"This is the message from the server: \" + message);\n\t\t\tWebsocket.BroadCastAll({ type: \"client.message.received\", content: { message } });\n\t\t} catch (error) {\n\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\tws.close(1011, \"Internal server error during message handling: \" + (error instanceof Error ? error.message : error));\n\t\t}\n\t};\n\n\tprivate clientConnected = (ws: ServerWebSocket<WebsocketEntityData>) => {\n\t\ttry {\n\t\t\tif (this._options.debug) Lib.Log(\"[debug] Client connected\", ws.data);\n\n\t\t\tconst global = this._channels.get(\"global\");\n\t\t\tif (!global) throw new Error(\"Global channel not found\");\n\n\t\t\tconst client = Websocket.CreateClient({ id: ws.data.id, ws: ws, name: ws.data.name });\n\t\t\tthis._clients.set(client.id, client);\n\t\t\tthis._lastId++;\n\t\t\tif ((Number(client.id) || 0) >= this._lastId) this._lastId = Number(client.id) + 1;\n\n\t\t\t// Mark as fully connected\n\t\t\tclient.markConnected();\n\n\t\t\tclient.send({ type: E_WebsocketMessageType.CLIENT_CONNECTED, content: { message: \"Welcome to the server\", client: client.whoami() } });\n\n\t\t\t// Client handles its own joining logic with rollback support\n\t\t\tconst joinResult = client.joinChannel(global);\n\t\t\tif (!joinResult.success) {\n\t\t\t\tthrow new Error(\"Failed to join global channel: \" + joinResult.reason);\n\t\t\t}\n\n\t\t\tif (this._ws_interface_handlers.open) this._ws_interface_handlers.open(ws);\n\t\t} catch (error) {\n\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\tws.close(1011, \"Internal server error during connection setup: \" + (error instanceof Error ? error.message : error));\n\t\t}\n\t};\n\n\tprivate clientDisconnected = (ws: ServerWebSocket<WebsocketEntityData>, code: number, reason: string) => {\n\t\ttry {\n\t\t\tif (this._options.debug) Lib.Log(\"Client disconnected\", ws.data);\n\n\t\t\tconst client = this._clients.get(ws.data.id);\n\t\t\tif (!client) return;\n\n\t\t\t// Mark as disconnecting\n\t\t\tclient.markDisconnecting();\n\n\t\t\tif (this._ws_interface_handlers.close) this._ws_interface_handlers.close(ws, code, reason);\n\n\t\t\t// Remove from all channels\n\t\t\tthis._channels.forEach((channel) => {\n\t\t\t\tchannel.removeMember(client);\n\t\t\t});\n\n\t\t\t// Remove from registry\n\t\t\tthis._clients.delete(ws.data.id);\n\t\t\t// Mark as disconnected\n\t\t\tclient.markDisconnected();\n\t\t} catch (error) {\n\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\tws.close(1011, \"Internal server error during disconnection: \" + (error instanceof Error ? error.message : error));\n\t\t}\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\tpublic static GetLastId() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._lastId;\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((client) => client.state === \"connected\");\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\":\n\t\t\t\t\tstats.connecting++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"connected\":\n\t\t\t\t\tstats.connected++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"disconnecting\":\n\t\t\t\t\tstats.disconnecting++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"disconnected\":\n\t\t\t\t\tstats.disconnected++;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn stats;\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\tglobal_channel_limit?: number;\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\tprotected _lastId = 1;\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\", this._options.global_channel_limit ?? 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): Channel {\n\t\tif (this._channels.has(id)) return this._channels.get(id) as Channel;\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\tconst channel = this._channels.get(id);\n\t\tif (!channel) return;\n\t\tchannel.delete();\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\ttry {\n\t\t\tif (Websocket.Heartbeat(ws, message)) return;\n\n\t\t\tif (this._ws_interface_handlers.message) return this._ws_interface_handlers.message(ws, message);\n\n\t\t\tws.send(\"This is the message from the server: \" + message);\n\t\t\tWebsocket.BroadCastAll({ type: \"client.message.received\", content: { message } });\n\t\t} catch (error) {\n\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\tws.close(1011, \"Internal server error during message handling: \" + (error instanceof Error ? error.message : error));\n\t\t}\n\t};\n\n\tprivate clientConnected = (ws: ServerWebSocket<WebsocketEntityData>) => {\n\t\ttry {\n\t\t\tif (this._options.debug) Lib.Log(\"[debug] Client connected\", ws.data);\n\n\t\t\tconst global = this._channels.get(\"global\");\n\t\t\tif (!global) throw new Error(\"Global channel not found\");\n\n\t\t\tconst client = Websocket.CreateClient({ id: ws.data.id, ws: ws, name: ws.data.name });\n\t\t\tthis._clients.set(client.id, client);\n\t\t\tthis._lastId++;\n\t\t\tif ((Number(client.id) || 0) >= this._lastId) this._lastId = Number(client.id) + 1;\n\n\t\t\t// Mark as fully connected\n\t\t\tclient.markConnected();\n\n\t\t\tclient.send({ type: E_WebsocketMessageType.CLIENT_CONNECTED, content: { message: \"Welcome to the server\", client: client.whoami() } });\n\n\t\t\t// Client handles its own joining logic with rollback support\n\t\t\tconst joinResult = client.joinChannel(global);\n\t\t\tif (!joinResult.success) {\n\t\t\t\tthrow new Error(\"Failed to join global channel: \" + joinResult.reason);\n\t\t\t}\n\n\t\t\tif (this._ws_interface_handlers.open) this._ws_interface_handlers.open(ws);\n\t\t} catch (error) {\n\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\tws.close(1011, \"Internal server error during connection setup: \" + (error instanceof Error ? error.message : error));\n\t\t}\n\t};\n\n\tprivate clientDisconnected = (ws: ServerWebSocket<WebsocketEntityData>, code: number, reason: string) => {\n\t\ttry {\n\t\t\tif (this._options.debug) Lib.Log(\"Client disconnected\", ws.data);\n\n\t\t\tconst client = this._clients.get(ws.data.id);\n\t\t\tif (!client) return;\n\n\t\t\t// Mark as disconnecting\n\t\t\tclient.markDisconnecting();\n\n\t\t\tif (this._ws_interface_handlers.close) this._ws_interface_handlers.close(ws, code, reason);\n\n\t\t\t// Remove from all channels\n\t\t\tthis._channels.forEach((channel) => {\n\t\t\t\tchannel.removeMember(client);\n\t\t\t});\n\n\t\t\t// Remove from registry\n\t\t\tthis._clients.delete(ws.data.id);\n\t\t\t// Mark as disconnected\n\t\t\tclient.markDisconnected();\n\t\t} catch (error) {\n\t\t\tconsole.error(error instanceof Error ? error.message : error);\n\t\t\tws.close(1011, \"Internal server error during disconnection: \" + (error instanceof Error ? error.message : error));\n\t\t}\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\tpublic static GetLastId() {\n\t\tconst ws = this.GetInstance<Websocket>();\n\t\treturn ws._lastId;\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((client) => client.state === \"connected\");\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\":\n\t\t\t\t\tstats.connecting++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"connected\":\n\t\t\t\t\tstats.connected++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"disconnecting\":\n\t\t\t\t\tstats.disconnecting++;\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"disconnected\":\n\t\t\t\t\tstats.disconnected++;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn stats;\n\t}\n}\n"]}
|
package/package.json
CHANGED
|
@@ -284,6 +284,15 @@ export default class Channel<T extends Websocket = Websocket> implements I_Webso
|
|
|
284
284
|
return size < this.limit;
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
public delete() {
|
|
288
|
+
//first remove all members
|
|
289
|
+
this.members.forEach((member) => {
|
|
290
|
+
this.removeMember(member);
|
|
291
|
+
});
|
|
292
|
+
//then clear members map
|
|
293
|
+
this.members.clear();
|
|
294
|
+
}
|
|
295
|
+
|
|
287
296
|
public static GetChannelType(channels: WebsocketChannel<I_WebsocketChannel> | undefined) {
|
|
288
297
|
if (!channels) return Channel;
|
|
289
298
|
if (channels.size > 0) {
|
|
@@ -103,6 +103,9 @@ export default class Websocket extends Singleton {
|
|
|
103
103
|
* @param id - The id of the channel
|
|
104
104
|
*/
|
|
105
105
|
public removeChannel(id: string) {
|
|
106
|
+
const channel = this._channels.get(id);
|
|
107
|
+
if (!channel) return;
|
|
108
|
+
channel.delete();
|
|
106
109
|
this._channels.delete(id);
|
|
107
110
|
}
|
|
108
111
|
|