@vercube/ws 0.0.22 → 0.0.24

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.
Files changed (3) hide show
  1. package/README.md +10 -5
  2. package/dist/index.mjs +214 -253
  3. package/package.json +6 -6
package/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  <br>
4
4
  <br>
5
5
 
6
- # Vercube
7
-
8
- Next generation HTTP framework
9
-
6
+ # Vercube
7
+
8
+ Next generation HTTP framework
9
+
10
10
  <a href="https://www.npmjs.com/package/@vercube/auth">
11
11
  <img src="https://img.shields.io/npm/v/%40vercube%2Fauth?style=for-the-badge&logo=npm&color=%23767eff" alt="npm"/>
12
12
  </a>
@@ -16,6 +16,9 @@
16
16
  <a href="https://github.com/vercube/vercube/blob/main/LICENSE" target="_blank">
17
17
  <img src="https://img.shields.io/npm/l/%40vercube%2Fauth?style=for-the-badge&color=%23767eff" alt="License"/>
18
18
  </a>
19
+ <a href="https://codecov.io/gh/vercube/vercube" target="_blank">
20
+ <img src="https://img.shields.io/codecov/c/github/vercube/vercube?style=for-the-badge&color=%23767eff" alt="Coverage"/>
21
+ </a>
19
22
  <br/>
20
23
  <br/>
21
24
  </div>
@@ -23,7 +26,9 @@
23
26
  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
27
 
25
28
  ## <a name="module">Websocket module</a>
29
+
26
30
  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
31
 
28
32
  ## <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.
33
+
34
+ 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.
package/dist/index.mjs CHANGED
@@ -1,36 +1,9 @@
1
- import "node:module";
2
1
  import { BadRequestError, BasePlugin, HttpServer, ValidationProvider, initializeMetadata, initializeMetadataMethod } from "@vercube/core";
3
2
  import { BaseDecorator, Identity, Inject, InjectOptional, createDecorator } from "@vercube/di";
4
- import { plugin } from "crossws/server";
5
- import { defineHooks } from "crossws";
6
3
  import { Logger } from "@vercube/logger";
4
+ import { defineHooks } from "crossws";
5
+ import { plugin } from "crossws/server";
7
6
 
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
7
  //#region src/Decorators/Namespace.ts
35
8
  /**
36
9
  * A decorator function for defining a websocket namespace and accepting websocket connections.
@@ -55,37 +28,215 @@ function Namespace(path) {
55
28
  //#region src/Types/WebsocketTypes.ts
56
29
  let WebsocketTypes;
57
30
  (function(_WebsocketTypes) {
58
- let HandlerAction = /* @__PURE__ */ function(HandlerAction$1) {
59
- HandlerAction$1["CONNECTION"] = "connection";
60
- HandlerAction$1["MESSAGE"] = "message";
61
- return HandlerAction$1;
31
+ _WebsocketTypes.HandlerAction = /* @__PURE__ */ function(HandlerAction) {
32
+ HandlerAction["CONNECTION"] = "connection";
33
+ HandlerAction["MESSAGE"] = "message";
34
+ return HandlerAction;
62
35
  }({});
63
- _WebsocketTypes.HandlerAction = HandlerAction;
64
36
  })(WebsocketTypes || (WebsocketTypes = {}));
65
37
 
66
38
  //#endregion
67
- //#region src/Symbols/WebsocketSymbols.ts
68
- const $WebsocketService = Identity("WebsocketService");
39
+ //#region \0@oxc-project+runtime@0.90.0/helpers/decorate.js
40
+ function __decorate(decorators, target, key, desc) {
41
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
42
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
43
+ 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;
44
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
45
+ }
69
46
 
70
47
  //#endregion
71
- //#region ../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/node_modules/@oxc-project/runtime/src/helpers/decorate.js
72
- var require_decorate = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/@oxc-project+runtime@0.81.0/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;
48
+ //#region src/Services/WebsocketService.ts
49
+ /**
50
+ * WebsocketService class responsible for dealing with Websocket connections.
51
+ *
52
+ * This class is responsible for:
53
+ * - Registering namespaces and accepting websocket connections for them
54
+ * - Registering event handlers and handling them
55
+ */
56
+ var WebsocketService = class {
57
+ /**
58
+ * Http Server for injecting the server plugin
59
+ */
60
+ gHttpServer;
61
+ /**
62
+ * Validation provider for running the schema validation
63
+ */
64
+ gValidationProvider;
65
+ gLogger;
66
+ /**
67
+ * Internal namespace registry
68
+ */
69
+ fNamespaces = {};
70
+ /**
71
+ * Internal handlers registry
72
+ */
73
+ fHandlers = {
74
+ [WebsocketTypes.HandlerAction.CONNECTION]: {},
75
+ [WebsocketTypes.HandlerAction.MESSAGE]: {}
76
+ };
77
+ /**
78
+ * Register a new namespace.
79
+ *
80
+ * @param {string} path - The namespace path to register.
81
+ * @returns {void}
82
+ */
83
+ registerNamespace(path) {
84
+ if (!this?.fNamespaces?.[path.toLowerCase()]) this.fNamespaces[path.toLowerCase()] = [];
85
+ }
86
+ /**
87
+ * Register a new handler for a namespace or event.
88
+ *
89
+ * @param {WebsocketTypes.HandlerAction} action - The handler action type.
90
+ * @param {string} namespace - The namespace to register the handler for.
91
+ * @param {WebsocketTypes.HandlerAttributes} handler - The handler attributes.
92
+ * @returns {void}
93
+ */
94
+ registerHandler(action, namespace, handler) {
95
+ const normalizedNamespace = namespace.toLowerCase();
96
+ if (action === WebsocketTypes.HandlerAction.CONNECTION) this.fHandlers[action][normalizedNamespace] = handler;
97
+ if (action === WebsocketTypes.HandlerAction.MESSAGE) {
98
+ const event = handler.event;
99
+ if (!event) {
100
+ this.gLogger?.warn("WebsocketService::registerHandler", `Cannot register message handler without an event name for namespace "${normalizedNamespace}".`);
101
+ return;
102
+ }
103
+ if (!this.fHandlers[action][normalizedNamespace]) this.fHandlers[action][normalizedNamespace] = {};
104
+ this.fHandlers[action][normalizedNamespace][event] = handler;
105
+ }
106
+ this.registerNamespace(normalizedNamespace);
107
+ }
108
+ /**
109
+ * Broadcast a message to all peers in the same namespace (including the sender).
110
+ *
111
+ * @param {Peer} peer - The sender peer (used to determine the namespace).
112
+ * @param {unknown} message - The message to broadcast.
113
+ * @returns {void}
114
+ */
115
+ broadcast(peer, message) {
116
+ const namespace = peer.namespace?.toLowerCase();
117
+ if (!namespace) return;
118
+ const peers = this.fNamespaces[namespace];
119
+ if (!peers || peers.length === 0) return;
120
+ for (const p of peers) p.send(message);
121
+ }
122
+ /**
123
+ * Emit a message to a single peer.
124
+ *
125
+ * @param {Peer} peer - The peer to send the message to.
126
+ * @param {unknown} message - The message to send.
127
+ * @returns {void}
128
+ */
129
+ emit(peer, message) {
130
+ peer.send(message);
131
+ }
132
+ /**
133
+ * Broadcast a message to all peers in the same namespace except the sender.
134
+ *
135
+ * @param {Peer} peer - The sender peer (used to determine the namespace).
136
+ * @param {unknown} message - The message to broadcast.
137
+ * @returns {void}
138
+ */
139
+ broadcastOthers(peer, message) {
140
+ const namespace = peer.namespace?.toLowerCase();
141
+ if (!namespace) return;
142
+ const peers = this.fNamespaces[namespace];
143
+ if (!peers || peers.length === 0) return;
144
+ for (const p of peers) if (p.id !== peer.id) p.send(message);
78
145
  }
79
- module.exports = __decorate, module.exports.__esModule = true, module.exports["default"] = module.exports;
80
- }) });
146
+ /**
147
+ * Initialize the websocket service and attach the server plugin.
148
+ *
149
+ * @returns {void}
150
+ */
151
+ initialize() {
152
+ const hooks = defineHooks({
153
+ upgrade: async (request) => {
154
+ const url = new URL(request.url);
155
+ const namespace = url.pathname;
156
+ const parameters = Object.fromEntries(url.searchParams.entries());
157
+ if (!!!this.fNamespaces?.[namespace?.toLowerCase()]) {
158
+ this.gLogger?.warn("WebsocketService::initialize", `Namespace "${namespace}" is not registered. Connection rejected.`);
159
+ return new Response("Namespace not registered", { status: 403 });
160
+ }
161
+ const handler = this.fHandlers[WebsocketTypes.HandlerAction.CONNECTION]?.[namespace];
162
+ if (handler) try {
163
+ if (await handler.callback(parameters, request) === false) return new Response("Unauthorized", { status: 403 });
164
+ } catch (error) {
165
+ if (error instanceof Error) return new Response(error.message, { status: 403 });
166
+ return new Response("Unknown error", { status: 403 });
167
+ }
168
+ return {
169
+ namespace,
170
+ headers: {}
171
+ };
172
+ },
173
+ open: async (peer) => {
174
+ const namespace = peer.namespace?.toLowerCase();
175
+ if (namespace && this.fNamespaces[namespace]) this.fNamespaces[namespace].push(peer);
176
+ },
177
+ message: async (peer, message) => {
178
+ await this.handleMessage(peer, message);
179
+ },
180
+ close: async (peer) => {
181
+ const namespace = peer.namespace?.toLowerCase();
182
+ if (namespace && this.fNamespaces[namespace]) {
183
+ const peers = this.fNamespaces[namespace];
184
+ this.fNamespaces[namespace] = peers.filter((p) => p.id !== peer.id);
185
+ }
186
+ },
187
+ error: async (peer, error) => {
188
+ this.gLogger?.error("WebsocketService::initialize", `Error: ${error.message}`, { peer });
189
+ }
190
+ });
191
+ const serverPlugin = plugin(hooks);
192
+ this.gHttpServer.addPlugin(serverPlugin);
193
+ }
194
+ /**
195
+ * Handle an incoming websocket message for a peer.
196
+ *
197
+ * @param {Peer} peer - The peer receiving the message.
198
+ * @param {Message} rawMessage - The raw websocket message.
199
+ * @returns {Promise<void>}
200
+ */
201
+ async handleMessage(peer, rawMessage) {
202
+ try {
203
+ const msg = JSON.parse(rawMessage.text());
204
+ const namespace = peer.namespace?.toLowerCase();
205
+ const event = msg.event;
206
+ const data = msg.data;
207
+ const handler = this.fHandlers[WebsocketTypes.HandlerAction.MESSAGE]?.[namespace]?.[event];
208
+ if (!handler) {
209
+ this.gLogger?.warn("WebsocketService::handleMessage", `No message handler for event "${event}" in namespace "${namespace}"`);
210
+ return;
211
+ }
212
+ if (handler.schema) {
213
+ if (!this.gValidationProvider) {
214
+ this.gLogger?.warn("WebsocketService::handleMessage", "ValidationProvider is not registered");
215
+ return;
216
+ }
217
+ const result = await this.gValidationProvider.validate(handler.schema, data);
218
+ if (result?.issues?.length) throw new BadRequestError("Websocket message validation error", result.issues);
219
+ }
220
+ await handler.callback(data, peer);
221
+ } catch (error) {
222
+ this.gLogger?.error("WebsocketService::handleMessage", `Failed to process message: ${error}`);
223
+ }
224
+ }
225
+ };
226
+ __decorate([Inject(HttpServer)], WebsocketService.prototype, "gHttpServer", void 0);
227
+ __decorate([InjectOptional(ValidationProvider)], WebsocketService.prototype, "gValidationProvider", void 0);
228
+ __decorate([InjectOptional(Logger)], WebsocketService.prototype, "gLogger", void 0);
229
+
230
+ //#endregion
231
+ //#region src/Symbols/WebsocketSymbols.ts
232
+ const $WebsocketService = Identity("WebsocketService");
81
233
 
82
234
  //#endregion
83
235
  //#region src/Decorators/Message.ts
84
- var import_decorate$5 = /* @__PURE__ */ __toESM(require_decorate(), 1);
85
236
  /**
86
237
  * A decorator class for listening to websocket messages under
87
238
  * a specific event.
88
- *
239
+ *
89
240
  * This class extends the BaseDecorator and is used to listen
90
241
  * to websocket messages under a specific event.
91
242
  *
@@ -117,7 +268,7 @@ var MessageDecorator = class extends BaseDecorator {
117
268
  });
118
269
  }
119
270
  };
120
- (0, import_decorate$5.default)([InjectOptional($WebsocketService)], MessageDecorator.prototype, "gWebsocketService", void 0);
271
+ __decorate([InjectOptional($WebsocketService)], MessageDecorator.prototype, "gWebsocketService", void 0);
121
272
  /**
122
273
  * A decorator function for listening to websocket messages under a specific event.
123
274
  *
@@ -133,7 +284,6 @@ function Message(params) {
133
284
 
134
285
  //#endregion
135
286
  //#region src/Decorators/Emit.ts
136
- var import_decorate$4 = /* @__PURE__ */ __toESM(require_decorate(), 1);
137
287
  /**
138
288
  * A decorator class for emitting websocket messages to the peer.
139
289
  *
@@ -162,7 +312,7 @@ var EmitDecorator = class extends BaseDecorator {
162
312
  };
163
313
  }
164
314
  };
165
- (0, import_decorate$4.default)([InjectOptional($WebsocketService)], EmitDecorator.prototype, "gWebsocketService", void 0);
315
+ __decorate([InjectOptional($WebsocketService)], EmitDecorator.prototype, "gWebsocketService", void 0);
166
316
  /**
167
317
  * A decorator function for emitting websocket messages to the peer.
168
318
  *
@@ -178,11 +328,10 @@ function Emit(event) {
178
328
 
179
329
  //#endregion
180
330
  //#region src/Decorators/Broadcast.ts
181
- var import_decorate$3 = /* @__PURE__ */ __toESM(require_decorate(), 1);
182
331
  /**
183
- * A decorator class for broadcasting websocket messages to everyone
332
+ * A decorator class for broadcasting websocket messages to everyone
184
333
  * on the namespace (including the peer).
185
- *
334
+ *
186
335
  * This class extends the BaseDecorator and is used to emit the result of
187
336
  * your function as a websocket message to everyone on the namespace.
188
337
  *
@@ -208,7 +357,7 @@ var BroadcastDecorator = class extends BaseDecorator {
208
357
  };
209
358
  }
210
359
  };
211
- (0, import_decorate$3.default)([InjectOptional($WebsocketService)], BroadcastDecorator.prototype, "gWebsocketService", void 0);
360
+ __decorate([InjectOptional($WebsocketService)], BroadcastDecorator.prototype, "gWebsocketService", void 0);
212
361
  /**
213
362
  * A decorator function for broadcasting websocket messages to everyone on the namespace (including the peer).
214
363
  *
@@ -224,11 +373,10 @@ function Broadcast(event) {
224
373
 
225
374
  //#endregion
226
375
  //#region src/Decorators/BroadcastOthers.ts
227
- var import_decorate$2 = /* @__PURE__ */ __toESM(require_decorate(), 1);
228
376
  /**
229
- * A decorator class for broadcasting websocket messages to everyone
377
+ * A decorator class for broadcasting websocket messages to everyone
230
378
  * on the namespace (except the peer).
231
- *
379
+ *
232
380
  * This class extends the BaseDecorator and is used to emit the result of
233
381
  * your function as a websocket message to everyone on the namespace
234
382
  * (except the peer).
@@ -255,7 +403,7 @@ var BroadcastOthersDecorator = class extends BaseDecorator {
255
403
  };
256
404
  }
257
405
  };
258
- (0, import_decorate$2.default)([InjectOptional($WebsocketService)], BroadcastOthersDecorator.prototype, "gWebsocketService", void 0);
406
+ __decorate([InjectOptional($WebsocketService)], BroadcastOthersDecorator.prototype, "gWebsocketService", void 0);
259
407
  /**
260
408
  * A decorator function for broadcasting websocket messages to everyone on the namespace (except the peer).
261
409
  *
@@ -271,18 +419,17 @@ function BroadcastOthers(event) {
271
419
 
272
420
  //#endregion
273
421
  //#region src/Decorators/OnConnectionAttempt.ts
274
- var import_decorate$1 = /* @__PURE__ */ __toESM(require_decorate(), 1);
275
422
  /**
276
423
  * A decorator class for handling websocket connection attempts.
277
424
  *
278
425
  * This class extends the BaseDecorator and is used to handle a
279
426
  * websocket connection attempt to a namespace.
280
- *
427
+ *
281
428
  * If your function throws, or returns false, the connection
282
429
  * will not be accepted.
283
- *
430
+ *
284
431
  * The decorated function will receive the following parameters:
285
- *
432
+ *
286
433
  * @param {Record<string, unknown>} params - The connection query parameters.
287
434
  * @param {Request} request - The original HTTP request.
288
435
  *
@@ -306,7 +453,7 @@ var OnConnectionAttemptDecorator = class extends BaseDecorator {
306
453
  this.gWebsocketService.registerHandler(WebsocketTypes.HandlerAction.CONNECTION, namespace, { callback: originalMethod });
307
454
  }
308
455
  };
309
- (0, import_decorate$1.default)([InjectOptional($WebsocketService)], OnConnectionAttemptDecorator.prototype, "gWebsocketService", void 0);
456
+ __decorate([InjectOptional($WebsocketService)], OnConnectionAttemptDecorator.prototype, "gWebsocketService", void 0);
310
457
  /**
311
458
  * A decorator function for handling websocket connection attempts to a namespace.
312
459
  *
@@ -324,212 +471,26 @@ function OnConnectionAttempt() {
324
471
  return createDecorator(OnConnectionAttemptDecorator, void 0);
325
472
  }
326
473
 
327
- //#endregion
328
- //#region src/Services/WebsocketService.ts
329
- var import_decorate = /* @__PURE__ */ __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
474
  //#endregion
514
475
  //#region src/Plugins/WebsocketPlugin.ts
515
476
  /**
516
477
  * Websocket Plugin for Vercube framework
517
- *
478
+ *
518
479
  * Enables websocket connections and use of decorators related
519
480
  * to the Websocket package.
520
- *
481
+ *
521
482
  * @example
522
483
  * ```ts
523
484
  * import { createApp } from '@vercube/core';
524
485
  * import { WebsocketPlugin } from '@vercube/ws';
525
- *
486
+ *
526
487
  * const app = createApp({
527
488
  * setup: async (app) => {
528
489
  * app.addPlugin(WebsocketPlugin);
529
490
  * }
530
491
  * });
531
492
  * ```
532
- *
493
+ *
533
494
  * @see {@link https://vercube.dev} for full documentation
534
495
  */
535
496
  var WebsocketPlugin = class extends BasePlugin {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercube/ws",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "Websocket module for Vercube framework",
5
5
  "repository": "@vercube/ws",
6
6
  "license": "MIT",
@@ -18,14 +18,14 @@
18
18
  "README.md"
19
19
  ],
20
20
  "devDependencies": {
21
- "zod": "4.0.17"
21
+ "zod": "4.1.11"
22
22
  },
23
23
  "dependencies": {
24
24
  "crossws": "0.4.1",
25
- "srvx": "0.8.6",
26
- "@vercube/core": "0.0.22",
27
- "@vercube/di": "0.0.22",
28
- "@vercube/logger": "0.0.22"
25
+ "srvx": "0.8.7",
26
+ "@vercube/core": "0.0.24",
27
+ "@vercube/logger": "0.0.24",
28
+ "@vercube/di": "0.0.24"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"