@vercube/ws 0.0.19

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025-present - Vercube
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ <div align="center">
2
+ <a href="https://vercube.dev/"><img src="https://github.com/OskarLebuda/vue-lazy-hydration/raw/main/.github/assets/logo.png?raw=true" alt="Vite logo" width="200"></a>
3
+ <br>
4
+ <br>
5
+
6
+ # Vercube
7
+
8
+ Next generation HTTP framework
9
+
10
+ <a href="https://www.npmjs.com/package/@vercube/auth">
11
+ <img src="https://img.shields.io/npm/v/%40vercube%2Fauth?style=for-the-badge&logo=npm&color=%23767eff" alt="npm"/>
12
+ </a>
13
+ <a href="https://www.npmjs.com/package/@vercube/auth">
14
+ <img src="https://img.shields.io/npm/dm/%40vercube%2Fauth?style=for-the-badge&logo=npm&color=%23767eff" alt="npm"/>
15
+ </a>
16
+ <a href="https://github.com/vercube/vercube/blob/main/LICENSE" target="_blank">
17
+ <img src="https://img.shields.io/npm/l/%40vercube%2Fauth?style=for-the-badge&color=%23767eff" alt="License"/>
18
+ </a>
19
+ <br/>
20
+ <br/>
21
+ </div>
22
+
23
+ An ultra-efficient JavaScript server framework that runs anywhere - Node.js, Bun, or Deno - with unmatched flexibility and complete configurability for developers who refuse to sacrifice speed or control.
24
+
25
+ ## <a name="module">Websocket module</a>
26
+ The Websocket module enables you to set up websocket connections on your application. It offers a collection of decorators that enable you to listen to incoming messages, emit/broadcast messages and more. It works based on namespaces.
27
+
28
+ ## <a name="documentation">📖 Documentation</a>
29
+ Comprehensive documentation is available at [vercube.dev](https://vercube.dev). There you'll find detailed module descriptions, project information, guides, and everything else you need to know about Vercube.
@@ -0,0 +1,219 @@
1
+ import { App, BasePlugin, ValidationTypes } from "@vercube/core";
2
+ import { Peer } from "crossws";
3
+
4
+ //#region src/Decorators/Namespace.d.ts
5
+
6
+ /**
7
+ * A decorator function for defining a websocket namespace and accepting websocket connections.
8
+ *
9
+ * This function attaches namespace metadata to the decorated class, which is used to group websocket events under a
10
+ * specific namespace.
11
+ *
12
+ * @param {string} path - The namespace path for websocket connections.
13
+ * @returns {Function} - The decorator function.
14
+ */
15
+ declare function Namespace(path: string): Function;
16
+ //#endregion
17
+ //#region src/Decorators/Message.d.ts
18
+ interface MessageDecoratorOptions {
19
+ event: string;
20
+ validationSchema?: ValidationTypes.Schema;
21
+ }
22
+ /**
23
+ * A decorator function for listening to websocket messages under a specific event.
24
+ *
25
+ * This function creates an instance of the MessageDecorator class and registers a handler for websocket messages under
26
+ * the specified event and optional validation schema.
27
+ *
28
+ * @param {MessageDecoratorOptions} params - The options for the message handler, including event and optional validation schema.
29
+ * @returns {Function} - The decorator function.
30
+ */
31
+ declare function Message(params: MessageDecoratorOptions): Function;
32
+ //#endregion
33
+ //#region src/Decorators/Emit.d.ts
34
+ /**
35
+ * A decorator function for emitting websocket messages to the peer.
36
+ *
37
+ * This function creates an instance of the EmitDecorator class and emits the result of the decorated method as a
38
+ * websocket message to the peer under the specified event.
39
+ *
40
+ * @param {string} event - The event name for the emitted websocket message.
41
+ * @returns {Function} - The decorator function.
42
+ */
43
+ declare function Emit(event: string): Function;
44
+ //#endregion
45
+ //#region src/Decorators/Broadcast.d.ts
46
+ /**
47
+ * A decorator function for broadcasting websocket messages to everyone on the namespace (including the peer).
48
+ *
49
+ * This function creates an instance of the BroadcastDecorator class and emits the result of the decorated method as a
50
+ * websocket message to everyone on the namespace under the specified event.
51
+ *
52
+ * @param {string} event - The event name for the broadcasted websocket message.
53
+ * @returns {Function} - The decorator function.
54
+ */
55
+ declare function Broadcast(event: string): Function;
56
+ //#endregion
57
+ //#region src/Decorators/BroadcastOthers.d.ts
58
+ /**
59
+ * A decorator function for broadcasting websocket messages to everyone on the namespace (except the peer).
60
+ *
61
+ * This function creates an instance of the BroadcastOthersDecorator class and emits the result of the decorated method
62
+ * as a websocket message to everyone on the namespace (except the peer) under the specified event.
63
+ *
64
+ * @param {string} event - The event name for the broadcasted websocket message.
65
+ * @returns {Function} - The decorator function.
66
+ */
67
+ declare function BroadcastOthers(event: string): Function;
68
+ //#endregion
69
+ //#region src/Decorators/OnConnectionAttempt.d.ts
70
+ /**
71
+ * A decorator function for handling websocket connection attempts to a namespace.
72
+ *
73
+ * This function creates an instance of the OnConnectionAttemptDecorator class and registers a handler for websocket
74
+ * connection attempts under the namespace. If the decorated function throws or returns false, the connection will not
75
+ * be accepted.
76
+ *
77
+ * The decorated function will receive the following parameters:
78
+ * - params: {Record<string, unknown>} The connection query parameters.
79
+ * - request: {Request} The original HTTP request.
80
+ *
81
+ * @returns {Function} - The decorator function.
82
+ */
83
+ declare function OnConnectionAttempt(): Function;
84
+ //#endregion
85
+ //#region src/Plugins/WebsocketPlugin.d.ts
86
+ /**
87
+ * Websocket Plugin for Vercube framework
88
+ *
89
+ * Enables websocket connections and use of decorators related
90
+ * to the Websocket package.
91
+ *
92
+ * @example
93
+ * ```ts
94
+ * import { createApp } from '@vercube/core';
95
+ * import { WebsocketPlugin } from '@vercube/ws';
96
+ *
97
+ * const app = createApp({
98
+ * setup: async (app) => {
99
+ * app.addPlugin(WebsocketPlugin);
100
+ * }
101
+ * });
102
+ * ```
103
+ *
104
+ * @see {@link https://vercube.dev} for full documentation
105
+ */
106
+ declare class WebsocketPlugin<T = unknown> extends BasePlugin<T> {
107
+ /**
108
+ * The name of the plugin.
109
+ * @override
110
+ */
111
+ name: string;
112
+ /**
113
+ * Method to use the plugin with the given app.
114
+ * @param {App} app - The application instance.
115
+ * @returns {void | Promise<void>}
116
+ * @override
117
+ */
118
+ use(app: App, options: T): void | Promise<void>;
119
+ }
120
+ //#endregion
121
+ //#region src/Types/WebsocketTypes.d.ts
122
+ declare namespace WebsocketTypes {
123
+ enum HandlerAction {
124
+ CONNECTION = "connection",
125
+ MESSAGE = "message",
126
+ }
127
+ type HandlerAttributes = {
128
+ callback: Function;
129
+ event?: string;
130
+ schema?: ValidationTypes.Schema;
131
+ };
132
+ }
133
+ //#endregion
134
+ //#region src/Services/WebsocketService.d.ts
135
+ /**
136
+ * WebsocketService class responsible for dealing with Websocket connections.
137
+ *
138
+ * This class is responsible for:
139
+ * - Registering namespaces and accepting websocket connections for them
140
+ * - Registering event handlers and handling them
141
+ */
142
+ declare class WebsocketService {
143
+ /**
144
+ * Http Server for injecting the server plugin
145
+ */
146
+ private readonly gHttpServer;
147
+ /**
148
+ * Validation provider for running the schema validation
149
+ */
150
+ private readonly gValidationProvider;
151
+ private readonly gLogger;
152
+ /**
153
+ * Internal namespace registry
154
+ */
155
+ private fNamespaces;
156
+ /**
157
+ * Internal handlers registry
158
+ */
159
+ private fHandlers;
160
+ /**
161
+ * Register a new namespace.
162
+ *
163
+ * @param {string} path - The namespace path to register.
164
+ * @returns {void}
165
+ */
166
+ registerNamespace(path: string): void;
167
+ /**
168
+ * Register a new handler for a namespace or event.
169
+ *
170
+ * @param {WebsocketTypes.HandlerAction} action - The handler action type.
171
+ * @param {string} namespace - The namespace to register the handler for.
172
+ * @param {WebsocketTypes.HandlerAttributes} handler - The handler attributes.
173
+ * @returns {void}
174
+ */
175
+ registerHandler(action: WebsocketTypes.HandlerAction, namespace: string, handler: WebsocketTypes.HandlerAttributes): void;
176
+ /**
177
+ * Broadcast a message to all peers in the same namespace (including the sender).
178
+ *
179
+ * @param {Peer} peer - The sender peer (used to determine the namespace).
180
+ * @param {unknown} message - The message to broadcast.
181
+ * @returns {void}
182
+ */
183
+ broadcast(peer: Peer, message: unknown): void;
184
+ /**
185
+ * Emit a message to a single peer.
186
+ *
187
+ * @param {Peer} peer - The peer to send the message to.
188
+ * @param {unknown} message - The message to send.
189
+ * @returns {void}
190
+ */
191
+ emit(peer: Peer, message: unknown): void;
192
+ /**
193
+ * Broadcast a message to all peers in the same namespace except the sender.
194
+ *
195
+ * @param {Peer} peer - The sender peer (used to determine the namespace).
196
+ * @param {unknown} message - The message to broadcast.
197
+ * @returns {void}
198
+ */
199
+ broadcastOthers(peer: Peer, message: unknown): void;
200
+ /**
201
+ * Initialize the websocket service and attach the server plugin.
202
+ *
203
+ * @returns {void}
204
+ */
205
+ initialize(): void;
206
+ /**
207
+ * Handle an incoming websocket message for a peer.
208
+ *
209
+ * @param {Peer} peer - The peer receiving the message.
210
+ * @param {Message} rawMessage - The raw websocket message.
211
+ * @returns {Promise<void>}
212
+ */
213
+ private handleMessage;
214
+ }
215
+ //#endregion
216
+ //#region src/Symbols/WebsocketSymbols.d.ts
217
+ declare const $WebsocketService: symbol;
218
+ //#endregion
219
+ export { $WebsocketService, Broadcast, BroadcastOthers, Emit, Message, Namespace, OnConnectionAttempt, WebsocketPlugin, WebsocketService, WebsocketTypes };
package/dist/index.mjs ADDED
@@ -0,0 +1,554 @@
1
+ import "node:module";
2
+ import { BadRequestError, BasePlugin, HttpServer, ValidationProvider, initializeMetadata, initializeMetadataMethod } from "@vercube/core";
3
+ import { BaseDecorator, Identity, Inject, InjectOptional, createDecorator } from "@vercube/di";
4
+ import { plugin } from "crossws/server";
5
+ import { defineHooks } from "crossws";
6
+ import { Logger } from "@vercube/logger";
7
+
8
+ //#region rolldown:runtime
9
+ var __create = Object.create;
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __getProtoOf = Object.getPrototypeOf;
14
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
15
+ var __commonJS = (cb, mod) => function() {
16
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
20
+ key = keys[i];
21
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
22
+ get: ((k) => from[k]).bind(null, key),
23
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
24
+ });
25
+ }
26
+ return to;
27
+ };
28
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
29
+ value: mod,
30
+ enumerable: true
31
+ }) : target, mod));
32
+
33
+ //#endregion
34
+ //#region src/Decorators/Namespace.ts
35
+ /**
36
+ * A decorator function for defining a websocket namespace and accepting websocket connections.
37
+ *
38
+ * This function attaches namespace metadata to the decorated class, which is used to group websocket events under a
39
+ * specific namespace.
40
+ *
41
+ * @param {string} path - The namespace path for websocket connections.
42
+ * @returns {Function} - The decorator function.
43
+ */
44
+ function Namespace(path) {
45
+ return function internalDecorator(target) {
46
+ const meta = initializeMetadata(target.prototype);
47
+ meta.__meta = {
48
+ ...meta?.__meta,
49
+ namespace: path
50
+ };
51
+ };
52
+ }
53
+
54
+ //#endregion
55
+ //#region src/Types/WebsocketTypes.ts
56
+ let WebsocketTypes;
57
+ (function(_WebsocketTypes) {
58
+ let HandlerAction = /* @__PURE__ */ function(HandlerAction$1) {
59
+ HandlerAction$1["CONNECTION"] = "connection";
60
+ HandlerAction$1["MESSAGE"] = "message";
61
+ return HandlerAction$1;
62
+ }({});
63
+ _WebsocketTypes.HandlerAction = HandlerAction;
64
+ })(WebsocketTypes || (WebsocketTypes = {}));
65
+
66
+ //#endregion
67
+ //#region src/Symbols/WebsocketSymbols.ts
68
+ const $WebsocketService = Identity("WebsocketService");
69
+
70
+ //#endregion
71
+ //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/decorate.js
72
+ var require_decorate = __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.77.3/node_modules/@oxc-project/runtime/src/helpers/decorate.js"(exports, module) {
73
+ function __decorate(decorators, target, key, desc) {
74
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
75
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
76
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
77
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
78
+ }
79
+ module.exports = __decorate, module.exports.__esModule = true, module.exports["default"] = module.exports;
80
+ } });
81
+
82
+ //#endregion
83
+ //#region src/Decorators/Message.ts
84
+ var import_decorate$5 = __toESM(require_decorate(), 1);
85
+ /**
86
+ * A decorator class for listening to websocket messages under
87
+ * a specific event.
88
+ *
89
+ * This class extends the BaseDecorator and is used to listen
90
+ * to websocket messages under a specific event.
91
+ *
92
+ * @extends {BaseDecorator<MessageDecoratorOptions>}
93
+ */
94
+ var MessageDecorator = class extends BaseDecorator {
95
+ gWebsocketService;
96
+ created() {
97
+ if (!this.gWebsocketService) {
98
+ console.warn("MessageDecorator::WebsocketService is not registered");
99
+ return;
100
+ }
101
+ const meta = initializeMetadata(this.prototype);
102
+ const method = initializeMetadataMethod(this.prototype, this.propertyName);
103
+ const namespace = meta?.__meta?.namespace;
104
+ if (!namespace) {
105
+ console.warn("MessageDecorator::Unable to find namespace. Did you use @Namespace()?");
106
+ return;
107
+ }
108
+ method.meta = {
109
+ ...method.meta,
110
+ event: this.options.event
111
+ };
112
+ const originalMethod = this.instance[this.propertyName].bind(this.instance);
113
+ this.gWebsocketService.registerHandler(WebsocketTypes.HandlerAction.MESSAGE, namespace, {
114
+ callback: originalMethod,
115
+ event: this.options.event,
116
+ schema: this.options.validationSchema
117
+ });
118
+ }
119
+ };
120
+ (0, import_decorate$5.default)([InjectOptional($WebsocketService)], MessageDecorator.prototype, "gWebsocketService", void 0);
121
+ /**
122
+ * A decorator function for listening to websocket messages under a specific event.
123
+ *
124
+ * This function creates an instance of the MessageDecorator class and registers a handler for websocket messages under
125
+ * the specified event and optional validation schema.
126
+ *
127
+ * @param {MessageDecoratorOptions} params - The options for the message handler, including event and optional validation schema.
128
+ * @returns {Function} - The decorator function.
129
+ */
130
+ function Message(params) {
131
+ return createDecorator(MessageDecorator, params);
132
+ }
133
+
134
+ //#endregion
135
+ //#region src/Decorators/Emit.ts
136
+ var import_decorate$4 = __toESM(require_decorate(), 1);
137
+ /**
138
+ * A decorator class for emitting websocket messages to the peer.
139
+ *
140
+ * This class extends the BaseDecorator and is used to emit the result of
141
+ * your function as a websocket message to the peer.
142
+ *
143
+ * @extends {BaseDecorator<EmitDecoratorOptions>}
144
+ */
145
+ var EmitDecorator = class extends BaseDecorator {
146
+ gWebsocketService;
147
+ created() {
148
+ if (!this.gWebsocketService) {
149
+ console.warn("EmitDecorator::WebsocketService is not registered");
150
+ return;
151
+ }
152
+ initializeMetadata(this.prototype);
153
+ initializeMetadataMethod(this.prototype, this.propertyName);
154
+ const originalMethod = this.instance[this.propertyName];
155
+ this.instance[this.propertyName] = async (incomingMessage, peer) => {
156
+ const result = await originalMethod.call(this.instance, incomingMessage, peer);
157
+ this.gWebsocketService.emit(peer, {
158
+ event: this.options.event,
159
+ data: result
160
+ });
161
+ return result;
162
+ };
163
+ }
164
+ };
165
+ (0, import_decorate$4.default)([InjectOptional($WebsocketService)], EmitDecorator.prototype, "gWebsocketService", void 0);
166
+ /**
167
+ * A decorator function for emitting websocket messages to the peer.
168
+ *
169
+ * This function creates an instance of the EmitDecorator class and emits the result of the decorated method as a
170
+ * websocket message to the peer under the specified event.
171
+ *
172
+ * @param {string} event - The event name for the emitted websocket message.
173
+ * @returns {Function} - The decorator function.
174
+ */
175
+ function Emit(event) {
176
+ return createDecorator(EmitDecorator, { event });
177
+ }
178
+
179
+ //#endregion
180
+ //#region src/Decorators/Broadcast.ts
181
+ var import_decorate$3 = __toESM(require_decorate(), 1);
182
+ /**
183
+ * A decorator class for broadcasting websocket messages to everyone
184
+ * on the namespace (including the peer).
185
+ *
186
+ * This class extends the BaseDecorator and is used to emit the result of
187
+ * your function as a websocket message to everyone on the namespace.
188
+ *
189
+ * @extends {BaseDecorator<BroadcastDecoratorOptions>}
190
+ */
191
+ var BroadcastDecorator = class extends BaseDecorator {
192
+ gWebsocketService;
193
+ created() {
194
+ if (!this.gWebsocketService) {
195
+ console.warn("BroadcastDecorator::WebsocketService is not registered");
196
+ return;
197
+ }
198
+ initializeMetadata(this.prototype);
199
+ initializeMetadataMethod(this.prototype, this.propertyName);
200
+ const originalMethod = this.instance[this.propertyName];
201
+ this.instance[this.propertyName] = async (incomingMessage, peer) => {
202
+ const result = await originalMethod.call(this.instance, incomingMessage, peer);
203
+ this.gWebsocketService.broadcast(peer, {
204
+ event: this.options.event,
205
+ data: result
206
+ });
207
+ return result;
208
+ };
209
+ }
210
+ };
211
+ (0, import_decorate$3.default)([InjectOptional($WebsocketService)], BroadcastDecorator.prototype, "gWebsocketService", void 0);
212
+ /**
213
+ * A decorator function for broadcasting websocket messages to everyone on the namespace (including the peer).
214
+ *
215
+ * This function creates an instance of the BroadcastDecorator class and emits the result of the decorated method as a
216
+ * websocket message to everyone on the namespace under the specified event.
217
+ *
218
+ * @param {string} event - The event name for the broadcasted websocket message.
219
+ * @returns {Function} - The decorator function.
220
+ */
221
+ function Broadcast(event) {
222
+ return createDecorator(BroadcastDecorator, { event });
223
+ }
224
+
225
+ //#endregion
226
+ //#region src/Decorators/BroadcastOthers.ts
227
+ var import_decorate$2 = __toESM(require_decorate(), 1);
228
+ /**
229
+ * A decorator class for broadcasting websocket messages to everyone
230
+ * on the namespace (except the peer).
231
+ *
232
+ * This class extends the BaseDecorator and is used to emit the result of
233
+ * your function as a websocket message to everyone on the namespace
234
+ * (except the peer).
235
+ *
236
+ * @extends {BaseDecorator<BroadcastOthersDecoratorOptions>}
237
+ */
238
+ var BroadcastOthersDecorator = class extends BaseDecorator {
239
+ gWebsocketService;
240
+ created() {
241
+ if (!this.gWebsocketService) {
242
+ console.warn("BroadcastOthersDecorator::WebsocketService is not registered");
243
+ return;
244
+ }
245
+ initializeMetadata(this.prototype);
246
+ initializeMetadataMethod(this.prototype, this.propertyName);
247
+ const originalMethod = this.instance[this.propertyName];
248
+ this.instance[this.propertyName] = async (incomingMessage, peer) => {
249
+ const result = await originalMethod.call(this.instance, incomingMessage, peer);
250
+ this.gWebsocketService.broadcastOthers(peer, {
251
+ event: this.options.event,
252
+ data: result
253
+ });
254
+ return result;
255
+ };
256
+ }
257
+ };
258
+ (0, import_decorate$2.default)([InjectOptional($WebsocketService)], BroadcastOthersDecorator.prototype, "gWebsocketService", void 0);
259
+ /**
260
+ * A decorator function for broadcasting websocket messages to everyone on the namespace (except the peer).
261
+ *
262
+ * This function creates an instance of the BroadcastOthersDecorator class and emits the result of the decorated method
263
+ * as a websocket message to everyone on the namespace (except the peer) under the specified event.
264
+ *
265
+ * @param {string} event - The event name for the broadcasted websocket message.
266
+ * @returns {Function} - The decorator function.
267
+ */
268
+ function BroadcastOthers(event) {
269
+ return createDecorator(BroadcastOthersDecorator, { event });
270
+ }
271
+
272
+ //#endregion
273
+ //#region src/Decorators/OnConnectionAttempt.ts
274
+ var import_decorate$1 = __toESM(require_decorate(), 1);
275
+ /**
276
+ * A decorator class for handling websocket connection attempts.
277
+ *
278
+ * This class extends the BaseDecorator and is used to handle a
279
+ * websocket connection attempt to a namespace.
280
+ *
281
+ * If your function throws, or returns false, the connection
282
+ * will not be accepted.
283
+ *
284
+ * The decorated function will receive the following parameters:
285
+ *
286
+ * @param {Record<string, unknown>} params - The connection query parameters.
287
+ * @param {Request} request - The original HTTP request.
288
+ *
289
+ * @extends {BaseDecorator}
290
+ */
291
+ var OnConnectionAttemptDecorator = class extends BaseDecorator {
292
+ gWebsocketService;
293
+ created() {
294
+ if (!this.gWebsocketService) {
295
+ console.warn("OnConnectionAttemptDecorator::WebsocketService is not registered");
296
+ return;
297
+ }
298
+ const meta = initializeMetadata(this.prototype);
299
+ initializeMetadataMethod(this.prototype, this.propertyName);
300
+ const namespace = meta?.__meta?.namespace;
301
+ if (!namespace) {
302
+ console.warn("OnConnectionAttemptDecorator::Unable to find namespace. Did you use @Namespace()?");
303
+ return;
304
+ }
305
+ const originalMethod = this.instance[this.propertyName].bind(this.instance);
306
+ this.gWebsocketService.registerHandler(WebsocketTypes.HandlerAction.CONNECTION, namespace, { callback: originalMethod });
307
+ }
308
+ };
309
+ (0, import_decorate$1.default)([InjectOptional($WebsocketService)], OnConnectionAttemptDecorator.prototype, "gWebsocketService", void 0);
310
+ /**
311
+ * A decorator function for handling websocket connection attempts to a namespace.
312
+ *
313
+ * This function creates an instance of the OnConnectionAttemptDecorator class and registers a handler for websocket
314
+ * connection attempts under the namespace. If the decorated function throws or returns false, the connection will not
315
+ * be accepted.
316
+ *
317
+ * The decorated function will receive the following parameters:
318
+ * - params: {Record<string, unknown>} The connection query parameters.
319
+ * - request: {Request} The original HTTP request.
320
+ *
321
+ * @returns {Function} - The decorator function.
322
+ */
323
+ function OnConnectionAttempt() {
324
+ return createDecorator(OnConnectionAttemptDecorator, void 0);
325
+ }
326
+
327
+ //#endregion
328
+ //#region src/Services/WebsocketService.ts
329
+ var import_decorate = __toESM(require_decorate(), 1);
330
+ /**
331
+ * WebsocketService class responsible for dealing with Websocket connections.
332
+ *
333
+ * This class is responsible for:
334
+ * - Registering namespaces and accepting websocket connections for them
335
+ * - Registering event handlers and handling them
336
+ */
337
+ var WebsocketService = class {
338
+ /**
339
+ * Http Server for injecting the server plugin
340
+ */
341
+ gHttpServer;
342
+ /**
343
+ * Validation provider for running the schema validation
344
+ */
345
+ gValidationProvider;
346
+ gLogger;
347
+ /**
348
+ * Internal namespace registry
349
+ */
350
+ fNamespaces = {};
351
+ /**
352
+ * Internal handlers registry
353
+ */
354
+ fHandlers = {
355
+ [WebsocketTypes.HandlerAction.CONNECTION]: {},
356
+ [WebsocketTypes.HandlerAction.MESSAGE]: {}
357
+ };
358
+ /**
359
+ * Register a new namespace.
360
+ *
361
+ * @param {string} path - The namespace path to register.
362
+ * @returns {void}
363
+ */
364
+ registerNamespace(path) {
365
+ if (!this?.fNamespaces?.[path.toLowerCase()]) this.fNamespaces[path.toLowerCase()] = [];
366
+ }
367
+ /**
368
+ * Register a new handler for a namespace or event.
369
+ *
370
+ * @param {WebsocketTypes.HandlerAction} action - The handler action type.
371
+ * @param {string} namespace - The namespace to register the handler for.
372
+ * @param {WebsocketTypes.HandlerAttributes} handler - The handler attributes.
373
+ * @returns {void}
374
+ */
375
+ registerHandler(action, namespace, handler) {
376
+ const normalizedNamespace = namespace.toLowerCase();
377
+ if (action === WebsocketTypes.HandlerAction.CONNECTION) this.fHandlers[action][normalizedNamespace] = handler;
378
+ if (action === WebsocketTypes.HandlerAction.MESSAGE) {
379
+ const event = handler.event;
380
+ if (!event) {
381
+ this.gLogger?.warn("WebsocketService::registerHandler", `Cannot register message handler without an event name for namespace "${normalizedNamespace}".`);
382
+ return;
383
+ }
384
+ if (!this.fHandlers[action][normalizedNamespace]) this.fHandlers[action][normalizedNamespace] = {};
385
+ this.fHandlers[action][normalizedNamespace][event] = handler;
386
+ }
387
+ this.registerNamespace(normalizedNamespace);
388
+ }
389
+ /**
390
+ * Broadcast a message to all peers in the same namespace (including the sender).
391
+ *
392
+ * @param {Peer} peer - The sender peer (used to determine the namespace).
393
+ * @param {unknown} message - The message to broadcast.
394
+ * @returns {void}
395
+ */
396
+ broadcast(peer, message) {
397
+ const namespace = peer.namespace?.toLowerCase();
398
+ if (!namespace) return;
399
+ const peers = this.fNamespaces[namespace];
400
+ if (!peers || peers.length === 0) return;
401
+ for (const p of peers) p.send(message);
402
+ }
403
+ /**
404
+ * Emit a message to a single peer.
405
+ *
406
+ * @param {Peer} peer - The peer to send the message to.
407
+ * @param {unknown} message - The message to send.
408
+ * @returns {void}
409
+ */
410
+ emit(peer, message) {
411
+ peer.send(message);
412
+ }
413
+ /**
414
+ * Broadcast a message to all peers in the same namespace except the sender.
415
+ *
416
+ * @param {Peer} peer - The sender peer (used to determine the namespace).
417
+ * @param {unknown} message - The message to broadcast.
418
+ * @returns {void}
419
+ */
420
+ broadcastOthers(peer, message) {
421
+ const namespace = peer.namespace?.toLowerCase();
422
+ if (!namespace) return;
423
+ const peers = this.fNamespaces[namespace];
424
+ if (!peers || peers.length === 0) return;
425
+ for (const p of peers) if (p.id !== peer.id) p.send(message);
426
+ }
427
+ /**
428
+ * Initialize the websocket service and attach the server plugin.
429
+ *
430
+ * @returns {void}
431
+ */
432
+ initialize() {
433
+ const hooks = defineHooks({
434
+ upgrade: async (request) => {
435
+ const url = new URL(request.url);
436
+ const namespace = url.pathname;
437
+ const parameters = Object.fromEntries(url.searchParams.entries());
438
+ const isNamespaceRegistered = !!this.fNamespaces?.[namespace?.toLowerCase()];
439
+ if (!isNamespaceRegistered) {
440
+ this.gLogger?.warn("WebsocketService::initialize", `Namespace "${namespace}" is not registered. Connection rejected.`);
441
+ return new Response("Namespace not registered", { status: 403 });
442
+ }
443
+ const handler = this.fHandlers[WebsocketTypes.HandlerAction.CONNECTION]?.[namespace];
444
+ if (handler) try {
445
+ const result = await handler.callback(parameters, request);
446
+ if (result === false) return new Response("Unauthorized", { status: 403 });
447
+ } catch (error) {
448
+ if (error instanceof Error) return new Response(error.message, { status: 403 });
449
+ return new Response("Unknown error", { status: 403 });
450
+ }
451
+ return {
452
+ namespace,
453
+ headers: {}
454
+ };
455
+ },
456
+ open: async (peer) => {
457
+ const namespace = peer.namespace?.toLowerCase();
458
+ if (namespace && this.fNamespaces[namespace]) this.fNamespaces[namespace].push(peer);
459
+ },
460
+ message: async (peer, message) => {
461
+ await this.handleMessage(peer, message);
462
+ },
463
+ close: async (peer) => {
464
+ const namespace = peer.namespace?.toLowerCase();
465
+ if (namespace && this.fNamespaces[namespace]) {
466
+ const peers = this.fNamespaces[namespace];
467
+ this.fNamespaces[namespace] = peers.filter((p) => p.id !== peer.id);
468
+ }
469
+ },
470
+ error: async (peer, error) => {
471
+ this.gLogger?.error("WebsocketService::initialize", `Error: ${error.message}`, { peer });
472
+ }
473
+ });
474
+ const serverPlugin = plugin(hooks);
475
+ this.gHttpServer.addPlugin(serverPlugin);
476
+ }
477
+ /**
478
+ * Handle an incoming websocket message for a peer.
479
+ *
480
+ * @param {Peer} peer - The peer receiving the message.
481
+ * @param {Message} rawMessage - The raw websocket message.
482
+ * @returns {Promise<void>}
483
+ */
484
+ async handleMessage(peer, rawMessage) {
485
+ try {
486
+ const msg = JSON.parse(rawMessage.text());
487
+ const namespace = peer.namespace?.toLowerCase();
488
+ const event = msg.event;
489
+ const data = msg.data;
490
+ const handler = this.fHandlers[WebsocketTypes.HandlerAction.MESSAGE]?.[namespace]?.[event];
491
+ if (!handler) {
492
+ this.gLogger?.warn("WebsocketService::handleMessage", `No message handler for event "${event}" in namespace "${namespace}"`);
493
+ return;
494
+ }
495
+ if (handler.schema) {
496
+ if (!this.gValidationProvider) {
497
+ this.gLogger?.warn("WebsocketService::handleMessage", "ValidationProvider is not registered");
498
+ return;
499
+ }
500
+ const result = await this.gValidationProvider.validate(handler.schema, data);
501
+ if (result?.issues?.length) throw new BadRequestError("Websocket message validation error", result.issues);
502
+ }
503
+ await handler.callback(data, peer);
504
+ } catch (error) {
505
+ this.gLogger?.error("WebsocketService::handleMessage", `Failed to process message: ${error}`);
506
+ }
507
+ }
508
+ };
509
+ (0, import_decorate.default)([Inject(HttpServer)], WebsocketService.prototype, "gHttpServer", void 0);
510
+ (0, import_decorate.default)([InjectOptional(ValidationProvider)], WebsocketService.prototype, "gValidationProvider", void 0);
511
+ (0, import_decorate.default)([InjectOptional(Logger)], WebsocketService.prototype, "gLogger", void 0);
512
+
513
+ //#endregion
514
+ //#region src/Plugins/WebsocketPlugin.ts
515
+ /**
516
+ * Websocket Plugin for Vercube framework
517
+ *
518
+ * Enables websocket connections and use of decorators related
519
+ * to the Websocket package.
520
+ *
521
+ * @example
522
+ * ```ts
523
+ * import { createApp } from '@vercube/core';
524
+ * import { WebsocketPlugin } from '@vercube/ws';
525
+ *
526
+ * const app = createApp({
527
+ * setup: async (app) => {
528
+ * app.addPlugin(WebsocketPlugin);
529
+ * }
530
+ * });
531
+ * ```
532
+ *
533
+ * @see {@link https://vercube.dev} for full documentation
534
+ */
535
+ var WebsocketPlugin = class extends BasePlugin {
536
+ /**
537
+ * The name of the plugin.
538
+ * @override
539
+ */
540
+ name = "WebsocketPlugin";
541
+ /**
542
+ * Method to use the plugin with the given app.
543
+ * @param {App} app - The application instance.
544
+ * @returns {void | Promise<void>}
545
+ * @override
546
+ */
547
+ use(app, options) {
548
+ app.container.bind($WebsocketService, WebsocketService);
549
+ app.container.get($WebsocketService).initialize();
550
+ }
551
+ };
552
+
553
+ //#endregion
554
+ export { $WebsocketService, Broadcast, BroadcastOthers, Emit, Message, Namespace, OnConnectionAttempt, WebsocketPlugin, WebsocketService, WebsocketTypes };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@vercube/ws",
3
+ "version": "0.0.19",
4
+ "description": "Websocket module for Vercube framework",
5
+ "repository": "@vercube/ws",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "type": "module",
9
+ "main": "./dist/index.mjs",
10
+ "module": "./dist/index.mjs",
11
+ "exports": {
12
+ ".": "./dist/index.mjs",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "types": "./dist/index.d.mts",
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "devDependencies": {
21
+ "zod": "4.0.10"
22
+ },
23
+ "dependencies": {
24
+ "crossws": "0.4.1",
25
+ "srvx": "0.8.2",
26
+ "@vercube/core": "0.0.19",
27
+ "@vercube/logger": "0.0.19",
28
+ "@vercube/di": "0.0.19"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }