topsyde-utils 1.3.2 → 2.0.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/index.d.ts +2 -43
- package/dist/index.js +1 -38
- 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/utils/index.d.ts +0 -3
- package/dist/utils/index.js +0 -3
- package/dist/utils/index.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 +12 -66
- package/src/__tests__/singleton.test.ts +0 -143
- package/src/index.ts +2 -83
- package/src/utils/Lib.ts +0 -77
- package/src/utils/index.ts +0 -3
- 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/client/api/base.api.d.ts +0 -63
- package/dist/client/api/base.api.js +0 -61
- package/dist/client/api/base.api.js.map +0 -1
- package/dist/client/api/index.d.ts +0 -2
- package/dist/client/api/index.js +0 -5
- package/dist/client/api/index.js.map +0 -1
- package/dist/client/rxjs/index.d.ts +0 -1
- package/dist/client/rxjs/index.js +0 -4
- package/dist/client/rxjs/index.js.map +0 -1
- package/dist/client/rxjs/useRxjs.d.ts +0 -17
- package/dist/client/rxjs/useRxjs.js +0 -87
- package/dist/client/rxjs/useRxjs.js.map +0 -1
- package/dist/client/vite/plugins/index.d.ts +0 -2
- package/dist/client/vite/plugins/index.js +0 -5
- package/dist/client/vite/plugins/index.js.map +0 -1
- package/dist/client/vite/plugins/topsydeUtilsVitePlugin.d.ts +0 -9
- package/dist/client/vite/plugins/topsydeUtilsVitePlugin.js +0 -74
- package/dist/client/vite/plugins/topsydeUtilsVitePlugin.js.map +0 -1
- package/dist/external/index.d.ts +0 -1
- package/dist/external/index.js +0 -4
- package/dist/external/index.js.map +0 -1
- package/dist/external/re-exports.d.ts +0 -16
- package/dist/external/re-exports.js +0 -24
- package/dist/external/re-exports.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/dist/utils/BaseDto.d.ts +0 -33
- package/dist/utils/BaseDto.js +0 -69
- package/dist/utils/BaseDto.js.map +0 -1
- package/dist/utils/BaseEntity.d.ts +0 -31
- package/dist/utils/BaseEntity.js +0 -37
- package/dist/utils/BaseEntity.js.map +0 -1
- package/dist/utils/dto_validators/IsNumberOrRangeConstraint.d.ts +0 -9
- package/dist/utils/dto_validators/IsNumberOrRangeConstraint.js +0 -85
- package/dist/utils/dto_validators/IsNumberOrRangeConstraint.js.map +0 -1
- package/dist/utils/dto_validators/index.d.ts +0 -1
- package/dist/utils/dto_validators/index.js +0 -4
- package/dist/utils/dto_validators/index.js.map +0 -1
- package/src/__tests__/app.test.ts +0 -206
- package/src/application.ts +0 -73
- package/src/client/api/base.api.ts +0 -111
- package/src/client/api/index.ts +0 -5
- package/src/client/rxjs/index.ts +0 -4
- package/src/client/rxjs/useRxjs.ts +0 -113
- package/src/client/vite/plugins/index.ts +0 -5
- package/src/client/vite/plugins/topsydeUtilsVitePlugin.ts +0 -80
- package/src/external/index.ts +0 -4
- package/src/external/re-exports.ts +0 -54
- 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
- package/src/utils/BaseDto.ts +0 -77
- package/src/utils/BaseEntity.ts +0 -49
- package/src/utils/dto_validators/IsNumberOrRangeConstraint.ts +0 -32
- package/src/utils/dto_validators/index.ts +0 -4
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import Guards from "../../../utils/Guards";
|
|
2
|
-
import { WebsocketStructuredMessage, WebsocketMessage, WebsocketMessageOptions, I_WebsocketClient, WebsocketEntityData } from "./websocket.types";
|
|
3
|
-
|
|
4
|
-
export default class Message {
|
|
5
|
-
// Shared template for all messages
|
|
6
|
-
private static readonly MESSAGE_TEMPLATE: WebsocketStructuredMessage<any> = {
|
|
7
|
-
type: "",
|
|
8
|
-
content: {},
|
|
9
|
-
channel: "",
|
|
10
|
-
timestamp: "",
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
// Private constructor to prevent instantiation
|
|
14
|
-
private constructor() {}
|
|
15
|
-
|
|
16
|
-
public static Create(message: WebsocketMessage, options?: WebsocketMessageOptions): WebsocketStructuredMessage {
|
|
17
|
-
// Clone the template
|
|
18
|
-
const output = Object.assign({}, Message.MESSAGE_TEMPLATE);
|
|
19
|
-
|
|
20
|
-
// Set the dynamic properties
|
|
21
|
-
output.type = message.type;
|
|
22
|
-
output.channel = message.channel || options?.channel || "N/A";
|
|
23
|
-
|
|
24
|
-
// Process message content based on type
|
|
25
|
-
if (typeof message.content === "string") {
|
|
26
|
-
output.content = { message: message.content };
|
|
27
|
-
} else if (typeof message.content === "object" && message.content !== null) {
|
|
28
|
-
output.content = { ...message.content };
|
|
29
|
-
} else {
|
|
30
|
-
output.content = {};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Process options if provided
|
|
34
|
-
if (options) {
|
|
35
|
-
// Add data if provided
|
|
36
|
-
if (options.data !== undefined) {
|
|
37
|
-
if (typeof options.data === "object" && options.data !== null && !Array.isArray(options.data)) {
|
|
38
|
-
// Merge object data with content
|
|
39
|
-
Object.assign(output.content, options.data);
|
|
40
|
-
} else {
|
|
41
|
-
// Set as data property for other types
|
|
42
|
-
output.content.data = options.data;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Add client information if provided
|
|
47
|
-
if (options.client && Guards.IsObject(options.client) && Guards.IsString(options.client.id, true)) {
|
|
48
|
-
output.client = {
|
|
49
|
-
id: options.client.id,
|
|
50
|
-
name: options.client.name || "Unknown",
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Include channel metadata if requested
|
|
55
|
-
// Include channel metadata if provided as an object
|
|
56
|
-
if (options.metadata && Guards.IsObject(options.metadata) && !Guards.IsArray(options.metadata)) {
|
|
57
|
-
output.metadata = options.metadata;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Add timestamp if requested (default: true)
|
|
61
|
-
if (options.includeTimestamp !== false) {
|
|
62
|
-
output.timestamp = new Date().toISOString();
|
|
63
|
-
} else {
|
|
64
|
-
// Remove timestamp if explicitly disabled
|
|
65
|
-
delete output.timestamp;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Add priority if specified
|
|
69
|
-
if (options.priority !== undefined) {
|
|
70
|
-
output.priority = options.priority;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Add expiration if specified
|
|
74
|
-
if (options.expiresAt !== undefined) {
|
|
75
|
-
output.expiresAt = options.expiresAt;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Add any custom fields to the root of the message
|
|
79
|
-
if (options.customFields) {
|
|
80
|
-
Object.assign(output, options.customFields);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Apply custom transformation if provided
|
|
84
|
-
if (options.transform) {
|
|
85
|
-
return options.transform(output);
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
output.timestamp = new Date().toISOString();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return output;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
public static CreateWhisper(message: Omit<WebsocketMessage, "type">, options?: WebsocketMessageOptions): WebsocketStructuredMessage {
|
|
95
|
-
return Message.Create({ ...message, content: message.content, channel: message.channel, type: "whisper" }, options);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
public static Serialize<T = string>(message: WebsocketStructuredMessage, transform?: (message: WebsocketStructuredMessage) => T): string | T {
|
|
99
|
-
return transform ? transform(message) : JSON.stringify(message);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
public static Send(target: I_WebsocketClient, message: WebsocketMessage, options?: WebsocketMessageOptions): void {
|
|
103
|
-
target.send(Message.Create(message, options));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
public static Alert(target: I_WebsocketClient, reason: string, client?: WebsocketEntityData): void {
|
|
107
|
-
target.send(
|
|
108
|
-
Message.Create(
|
|
109
|
-
{
|
|
110
|
-
content: {
|
|
111
|
-
message: reason,
|
|
112
|
-
},
|
|
113
|
-
channel: "alert",
|
|
114
|
-
type: "message",
|
|
115
|
-
},
|
|
116
|
-
{ client },
|
|
117
|
-
),
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
import { Server, ServerWebSocket, WebSocketHandler } from "bun";
|
|
2
|
-
import Singleton from "../../../singleton";
|
|
3
|
-
import { Lib } from "../../../utils";
|
|
4
|
-
import { Console } from "../../../utils/Console";
|
|
5
|
-
import Channel from "./Channel";
|
|
6
|
-
import Client from "./Client";
|
|
7
|
-
import type {
|
|
8
|
-
I_WebsocketChannel,
|
|
9
|
-
I_WebsocketClient,
|
|
10
|
-
I_WebsocketEntity,
|
|
11
|
-
I_WebsocketInterface,
|
|
12
|
-
WebsocketChannel,
|
|
13
|
-
WebsocketEntityData,
|
|
14
|
-
BunWebsocketMessage,
|
|
15
|
-
WebsocketStructuredMessage,
|
|
16
|
-
} from "./websocket.types";
|
|
17
|
-
import { E_WebsocketMessageType } from "./websocket.enums";
|
|
18
|
-
|
|
19
|
-
export type WebsocketConstructorOptions = {
|
|
20
|
-
debug?: boolean;
|
|
21
|
-
global_channel_limit?: number;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export interface I_WebsocketConstructor {
|
|
25
|
-
ws_interface?: I_WebsocketInterface;
|
|
26
|
-
channels?: WebsocketChannel;
|
|
27
|
-
clientClass?: typeof Client;
|
|
28
|
-
channelClass?: typeof Channel;
|
|
29
|
-
options?: WebsocketConstructorOptions;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Websocket - Singleton managing clients, channels, and message routing
|
|
34
|
-
*
|
|
35
|
-
* ## API Design: Static vs Instance
|
|
36
|
-
* - **Static methods**: Use in application code (e.g., `Websocket.Broadcast()`, `Websocket.GetClient()`)
|
|
37
|
-
* - **Instance methods**: Use when extending the class (e.g., `protected createClient()`)
|
|
38
|
-
*
|
|
39
|
-
* Static methods are facades that call the singleton instance internally.
|
|
40
|
-
*
|
|
41
|
-
* @example
|
|
42
|
-
* // Application code - use static methods
|
|
43
|
-
* Websocket.Broadcast("lobby", { type: "chat", content: { message: "Hi!" } });
|
|
44
|
-
*
|
|
45
|
-
* // Extension - override instance methods
|
|
46
|
-
* MyWebsocket extends Websocket:
|
|
47
|
-
* protected createClient(entity) {
|
|
48
|
-
* return new MyCustomClient(entity);
|
|
49
|
-
* }
|
|
50
|
-
*/
|
|
51
|
-
export default class Websocket extends Singleton {
|
|
52
|
-
protected _channels: WebsocketChannel;
|
|
53
|
-
protected _clients: Map<string, I_WebsocketClient> = new Map();
|
|
54
|
-
protected _server!: Server;
|
|
55
|
-
protected _channelClass: typeof Channel;
|
|
56
|
-
protected _clientClass: typeof Client;
|
|
57
|
-
protected _ws_interface?: I_WebsocketInterface;
|
|
58
|
-
protected _options: WebsocketConstructorOptions;
|
|
59
|
-
protected _ws_interface_handlers: Partial<WebSocketHandler<WebsocketEntityData>>;
|
|
60
|
-
protected _lastId = 1;
|
|
61
|
-
|
|
62
|
-
protected constructor(options?: I_WebsocketConstructor) {
|
|
63
|
-
super();
|
|
64
|
-
this._ws_interface = options?.ws_interface;
|
|
65
|
-
this._channels = options?.channels ?? new Map<string, Channel>();
|
|
66
|
-
this._clientClass = options?.clientClass ?? Client;
|
|
67
|
-
this._channelClass = options?.channelClass ?? Channel.GetChannelType(options?.channels);
|
|
68
|
-
this._options = options?.options ?? { debug: false };
|
|
69
|
-
this.createChannel("global", "Global", this._options.global_channel_limit ?? 1000);
|
|
70
|
-
this._ws_interface_handlers = this._ws_interface?.handlers(this._channels, this._clients) ?? {};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
protected set server(value: Server) {
|
|
74
|
-
this._server = value;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
public get server(): Server {
|
|
78
|
-
return this._server;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
public set(server: Server) {
|
|
82
|
-
this.server = server;
|
|
83
|
-
Console.blank();
|
|
84
|
-
Console.success("Websocket server set");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Create a new channel
|
|
89
|
-
* @param id - The id of the channel
|
|
90
|
-
* @param name - The name of the channel
|
|
91
|
-
* @param limit - The limit of the channel
|
|
92
|
-
* @returns The created channel
|
|
93
|
-
*/
|
|
94
|
-
public createChannel(id: string, name: string, limit?: number): Channel {
|
|
95
|
-
if (this._channels.has(id)) return this._channels.get(id) as Channel;
|
|
96
|
-
const channel = new this._channelClass(id, name, this, limit);
|
|
97
|
-
this._channels.set(id, channel);
|
|
98
|
-
return channel;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Remove a channel
|
|
103
|
-
* @param id - The id of the channel
|
|
104
|
-
*/
|
|
105
|
-
public removeChannel(id: string) {
|
|
106
|
-
const channel = this._channels.get(id);
|
|
107
|
-
if (!channel) return;
|
|
108
|
-
channel.delete();
|
|
109
|
-
this._channels.delete(id);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Create a new channel
|
|
114
|
-
* @param id - The id of the channel
|
|
115
|
-
* @param name - The name of the channel
|
|
116
|
-
* @param limit - The limit of the channel
|
|
117
|
-
* @returns The created channel
|
|
118
|
-
*/
|
|
119
|
-
public static CreateChannel(id: string, name: string, limit?: number) {
|
|
120
|
-
const ws = this.GetInstance<Websocket>();
|
|
121
|
-
return ws.createChannel(id, name, limit);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
public handlers(): WebSocketHandler<WebsocketEntityData> {
|
|
125
|
-
return {
|
|
126
|
-
open: this.clientConnected,
|
|
127
|
-
message: this.clientMessageReceived,
|
|
128
|
-
close: this.clientDisconnected,
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
private clientMessageReceived = (ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) => {
|
|
133
|
-
try {
|
|
134
|
-
if (Websocket.Heartbeat(ws, message)) return;
|
|
135
|
-
|
|
136
|
-
if (this._ws_interface_handlers.message) return this._ws_interface_handlers.message(ws, message);
|
|
137
|
-
|
|
138
|
-
ws.send("This is the message from the server: " + message);
|
|
139
|
-
Websocket.BroadCastAll({ type: "client.message.received", content: { message } });
|
|
140
|
-
} catch (error) {
|
|
141
|
-
console.error(error instanceof Error ? error.message : error);
|
|
142
|
-
ws.close(1011, "Internal server error during message handling: " + (error instanceof Error ? error.message : error));
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
private clientConnected = (ws: ServerWebSocket<WebsocketEntityData>) => {
|
|
147
|
-
try {
|
|
148
|
-
if (this._options.debug) Lib.Log("[debug] Client connected", ws.data);
|
|
149
|
-
|
|
150
|
-
const global = this._channels.get("global");
|
|
151
|
-
if (!global) throw new Error("Global channel not found");
|
|
152
|
-
|
|
153
|
-
const client = Websocket.CreateClient({ id: ws.data.id, ws: ws, name: ws.data.name });
|
|
154
|
-
this._clients.set(client.id, client);
|
|
155
|
-
this._lastId++;
|
|
156
|
-
if ((Number(client.id) || 0) >= this._lastId) this._lastId = Number(client.id) + 1;
|
|
157
|
-
|
|
158
|
-
// Mark as fully connected
|
|
159
|
-
client.markConnected();
|
|
160
|
-
|
|
161
|
-
client.send({ type: E_WebsocketMessageType.CLIENT_CONNECTED, content: { message: "Welcome to the server", client: client.whoami() } });
|
|
162
|
-
|
|
163
|
-
// Client handles its own joining logic with rollback support
|
|
164
|
-
const joinResult = client.joinChannel(global);
|
|
165
|
-
if (!joinResult.success) {
|
|
166
|
-
throw new Error("Failed to join global channel: " + joinResult.reason);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (this._ws_interface_handlers.open) this._ws_interface_handlers.open(ws);
|
|
170
|
-
} catch (error) {
|
|
171
|
-
console.error(error instanceof Error ? error.message : error);
|
|
172
|
-
ws.close(1011, "Internal server error during connection setup: " + (error instanceof Error ? error.message : error));
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
private clientDisconnected = (ws: ServerWebSocket<WebsocketEntityData>, code: number, reason: string) => {
|
|
177
|
-
try {
|
|
178
|
-
if (this._options.debug) Lib.Log("Client disconnected", ws.data);
|
|
179
|
-
|
|
180
|
-
const client = this._clients.get(ws.data.id);
|
|
181
|
-
if (!client) return;
|
|
182
|
-
|
|
183
|
-
// Mark as disconnecting
|
|
184
|
-
client.markDisconnecting();
|
|
185
|
-
|
|
186
|
-
if (this._ws_interface_handlers.close) this._ws_interface_handlers.close(ws, code, reason);
|
|
187
|
-
|
|
188
|
-
// Remove from all channels
|
|
189
|
-
this._channels.forEach((channel) => {
|
|
190
|
-
channel.removeMember(client);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Remove from registry
|
|
194
|
-
this._clients.delete(ws.data.id);
|
|
195
|
-
// Mark as disconnected
|
|
196
|
-
client.markDisconnected();
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error(error instanceof Error ? error.message : error);
|
|
199
|
-
ws.close(1011, "Internal server error during disconnection: " + (error instanceof Error ? error.message : error));
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
private handleHeartbeat = (ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) => {
|
|
204
|
-
if (message === "ping") {
|
|
205
|
-
const pong: WebsocketStructuredMessage = { type: "pong", content: { message: "pong" } };
|
|
206
|
-
ws.send(JSON.stringify(pong));
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
return false;
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
protected createClient(entity: I_WebsocketEntity): I_WebsocketClient {
|
|
213
|
-
return new this._clientClass(entity);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Handle the heartbeat
|
|
218
|
-
* @param ws - The websocket
|
|
219
|
-
* @param message - The message
|
|
220
|
-
* @returns True if the heartbeat was handled, false otherwise
|
|
221
|
-
*/
|
|
222
|
-
public static Heartbeat(ws: ServerWebSocket<WebsocketEntityData>, message: BunWebsocketMessage) {
|
|
223
|
-
const self = this.GetInstance<Websocket>();
|
|
224
|
-
return self.handleHeartbeat(ws, message);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Get the server
|
|
229
|
-
* @returns The server
|
|
230
|
-
*/
|
|
231
|
-
public static Server() {
|
|
232
|
-
return this.GetInstance<Websocket>().server;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Broadcast a message to a channel
|
|
237
|
-
* @param channel - The channel
|
|
238
|
-
* @param message - The message
|
|
239
|
-
* @param args - The arguments
|
|
240
|
-
*/
|
|
241
|
-
public static Broadcast(channel: string, message: WebsocketStructuredMessage, ...args: any[]) {
|
|
242
|
-
// Get the server from the singleton instance
|
|
243
|
-
const ws = this.GetInstance<Websocket>();
|
|
244
|
-
if (!ws.server) {
|
|
245
|
-
throw new Error("Websocket server not set");
|
|
246
|
-
}
|
|
247
|
-
ws.server.publish(channel, JSON.stringify({ message, args }));
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Broadcast a message to all channels
|
|
252
|
-
* @param message - The message
|
|
253
|
-
* @param args - The arguments
|
|
254
|
-
*/
|
|
255
|
-
public static BroadCastAll(message: WebsocketStructuredMessage, ...args: any[]) {
|
|
256
|
-
const ws = this.GetInstance<Websocket>();
|
|
257
|
-
ws._channels.forEach((channel) => channel.broadcast(message, ...args));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Join a channel
|
|
262
|
-
* @param channel - The channel
|
|
263
|
-
* @param entity - The entity
|
|
264
|
-
*/
|
|
265
|
-
public static Join(channel: string, entity: I_WebsocketEntity) {
|
|
266
|
-
const ws = this.GetInstance<Websocket>();
|
|
267
|
-
const client = ws._clients.get(entity.id);
|
|
268
|
-
if (!client) return;
|
|
269
|
-
ws._channels.get(channel)?.addMember(client);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Leave a channel
|
|
274
|
-
* @param channel - The channel
|
|
275
|
-
* @param entity - The entity
|
|
276
|
-
*/
|
|
277
|
-
public static Leave(channel: string, entity: I_WebsocketEntity) {
|
|
278
|
-
const ws = this.GetInstance<Websocket>();
|
|
279
|
-
const client = ws._clients.get(entity.id);
|
|
280
|
-
if (!client) return;
|
|
281
|
-
ws._channels.get(channel)?.removeMember(client);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Get a client
|
|
286
|
-
* @param id - The id of the client
|
|
287
|
-
* @param throw_if_nil - Whether to throw an error if the client is not found
|
|
288
|
-
* @returns The client
|
|
289
|
-
*/
|
|
290
|
-
public static GetClient(id: string, throw_if_nil?: true): I_WebsocketClient;
|
|
291
|
-
public static GetClient(id: string, throw_if_nil?: false): I_WebsocketClient | undefined;
|
|
292
|
-
public static GetClient(id: string, throw_if_nil: boolean = true): I_WebsocketClient | undefined {
|
|
293
|
-
const ws = this.GetInstance<Websocket>();
|
|
294
|
-
const client = ws._clients.get(id);
|
|
295
|
-
if (!client && throw_if_nil) throw new Error(`Client with id ${id} not found`);
|
|
296
|
-
return client;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Get a channel
|
|
301
|
-
* @param id - The id of the channel
|
|
302
|
-
* @returns The channel
|
|
303
|
-
*/
|
|
304
|
-
public static GetChannel(id: string) {
|
|
305
|
-
const ws = this.GetInstance<Websocket>();
|
|
306
|
-
return ws._channels.get(id);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Get all channels
|
|
311
|
-
* @returns The channels
|
|
312
|
-
*/
|
|
313
|
-
public static GetChannels() {
|
|
314
|
-
const ws = this.GetInstance<Websocket>();
|
|
315
|
-
return Array.from(ws._channels.values());
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* Get all clients
|
|
320
|
-
* @returns The clients
|
|
321
|
-
*/
|
|
322
|
-
public static GetClients() {
|
|
323
|
-
const ws = this.GetInstance<Websocket>();
|
|
324
|
-
return Array.from(ws._clients.values());
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
public static GetLastId() {
|
|
328
|
-
const ws = this.GetInstance<Websocket>();
|
|
329
|
-
return ws._lastId;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* Get the number of clients
|
|
334
|
-
* @returns The number of clients
|
|
335
|
-
*/
|
|
336
|
-
public static GetClientCount() {
|
|
337
|
-
const ws = this.GetInstance<Websocket>();
|
|
338
|
-
return ws._clients.size;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Get the number of channels
|
|
343
|
-
* @returns The number of channels
|
|
344
|
-
*/
|
|
345
|
-
public static GetChannelCount() {
|
|
346
|
-
const ws = this.GetInstance<Websocket>();
|
|
347
|
-
return ws._channels.size;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Create a client
|
|
352
|
-
* @param entity - The entity
|
|
353
|
-
* @returns The created client
|
|
354
|
-
*/
|
|
355
|
-
public static CreateClient(entity: I_WebsocketEntity): I_WebsocketClient {
|
|
356
|
-
const ws = this.GetInstance<Websocket>();
|
|
357
|
-
return ws.createClient(entity);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Get all connected clients (excluding connecting/disconnecting)
|
|
362
|
-
* @returns Array of connected clients
|
|
363
|
-
*/
|
|
364
|
-
public static GetConnectedClients(): I_WebsocketClient[] {
|
|
365
|
-
const ws = this.GetInstance<Websocket>();
|
|
366
|
-
return Array.from(ws._clients.values()).filter((client) => client.state === "connected");
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Get client statistics by state
|
|
371
|
-
* @returns Object with counts by state
|
|
372
|
-
*/
|
|
373
|
-
public static GetClientStats() {
|
|
374
|
-
const ws = this.GetInstance<Websocket>();
|
|
375
|
-
const stats = {
|
|
376
|
-
total: ws._clients.size,
|
|
377
|
-
connecting: 0,
|
|
378
|
-
connected: 0,
|
|
379
|
-
disconnecting: 0,
|
|
380
|
-
disconnected: 0,
|
|
381
|
-
};
|
|
382
|
-
|
|
383
|
-
for (const client of ws._clients.values()) {
|
|
384
|
-
switch (client.state) {
|
|
385
|
-
case "connecting":
|
|
386
|
-
stats.connecting++;
|
|
387
|
-
break;
|
|
388
|
-
case "connected":
|
|
389
|
-
stats.connected++;
|
|
390
|
-
break;
|
|
391
|
-
case "disconnecting":
|
|
392
|
-
stats.disconnecting++;
|
|
393
|
-
break;
|
|
394
|
-
case "disconnected":
|
|
395
|
-
stats.disconnected++;
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
return stats;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
// This file is auto-generated by scripts/generate-indexes.ts
|
|
2
|
-
// Do not edit this file directly
|
|
3
|
-
|
|
4
|
-
export * from './Websocket';
|
|
5
|
-
export * from './websocket.guards';
|
|
6
|
-
export * from './Message';
|
|
7
|
-
export * from './Channel';
|
|
8
|
-
export * from './Client';
|
|
9
|
-
export * from './websocket.enums';
|
|
10
|
-
export * from './websocket.types';
|
|
11
|
-
export { default as Websocket } from './Websocket';
|
|
12
|
-
export { default as Message } from './Message';
|
|
13
|
-
export { default as Channel } from './Channel';
|
|
14
|
-
export { default as Client } from './Client';
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
export enum E_WebsocketMessageType {
|
|
2
|
-
CLIENT_CONNECTED = "client.connected",
|
|
3
|
-
CLIENT_DISCONNECTED = "client.disconnected",
|
|
4
|
-
CLIENT_JOIN_CHANNEL = "client.join.channel",
|
|
5
|
-
CLIENT_LEAVE_CHANNEL = "client.leave.channel",
|
|
6
|
-
CLIENT_JOIN_CHANNELS = "client.join.channels",
|
|
7
|
-
CLIENT_LEAVE_CHANNELS = "client.leave.channels",
|
|
8
|
-
PING = "ping",
|
|
9
|
-
PONG = "pong",
|
|
10
|
-
MESSAGE = "message",
|
|
11
|
-
WHISPER = "whisper",
|
|
12
|
-
BROADCAST = "broadcast",
|
|
13
|
-
PROMPT = "prompt",
|
|
14
|
-
ERROR = "error",
|
|
15
|
-
SYSTEM = "system",
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export enum E_WebsocketMessagePriority {
|
|
19
|
-
LOW = 0,
|
|
20
|
-
MEDIUM = 1,
|
|
21
|
-
HIGH = 2,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export enum E_ClientState {
|
|
25
|
-
CONNECTING = "connecting",
|
|
26
|
-
CONNECTED = "connected",
|
|
27
|
-
DISCONNECTING = "disconnecting",
|
|
28
|
-
DISCONNECTED = "disconnected",
|
|
29
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { I_WebsocketClient, I_WebsocketEntity, WebsocketStructuredMessage } from "./websocket.types";
|
|
2
|
-
import { type ServerWebSocket } from "bun";
|
|
3
|
-
|
|
4
|
-
export function IsWebsocketStructuredMessage(message: any): message is WebsocketStructuredMessage {
|
|
5
|
-
return typeof message === "object" && message !== null && "type" in message && "content" in message;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function IsWebsocketEntity(entity: any): entity is I_WebsocketEntity {
|
|
9
|
-
return (
|
|
10
|
-
typeof entity === "object" &&
|
|
11
|
-
entity !== null &&
|
|
12
|
-
"id" in entity &&
|
|
13
|
-
"name" in entity &&
|
|
14
|
-
"ws" in entity &&
|
|
15
|
-
typeof entity.ws === "object" &&
|
|
16
|
-
entity.ws !== null &&
|
|
17
|
-
"send" in entity.ws &&
|
|
18
|
-
typeof entity.ws.send === "function" &&
|
|
19
|
-
"close" in entity.ws &&
|
|
20
|
-
typeof entity.ws.close === "function"
|
|
21
|
-
);
|
|
22
|
-
}
|