@vercube/ws 1.2.0 → 1.3.0

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.mts CHANGED
@@ -10,7 +10,7 @@ import { Message as Message$1, Peer, WSError } from "crossws";
10
10
  * @param {string} path - The namespace path for websocket connections.
11
11
  * @returns {Function} - The decorator function.
12
12
  */
13
- declare function Namespace(path: string): Function;
13
+ export declare function Namespace(path: string): Function;
14
14
  //#endregion
15
15
  //#region src/Decorators/Message.d.ts
16
16
  interface MessageDecoratorOptions {
@@ -26,7 +26,7 @@ interface MessageDecoratorOptions {
26
26
  * @param {MessageDecoratorOptions} params - The options for the message handler, including event and optional validation schema.
27
27
  * @returns {Function} - The decorator function.
28
28
  */
29
- declare function Message(params: MessageDecoratorOptions): Function;
29
+ export declare function Message(params: MessageDecoratorOptions): Function;
30
30
  //#endregion
31
31
  //#region src/Decorators/Emit.d.ts
32
32
  /**
@@ -38,7 +38,7 @@ declare function Message(params: MessageDecoratorOptions): Function;
38
38
  * @param {string} event - The event name for the emitted websocket message.
39
39
  * @returns {Function} - The decorator function.
40
40
  */
41
- declare function Emit(event: string): Function;
41
+ export declare function Emit(event: string): Function;
42
42
  //#endregion
43
43
  //#region src/Decorators/Broadcast.d.ts
44
44
  /**
@@ -50,7 +50,7 @@ declare function Emit(event: string): Function;
50
50
  * @param {string} event - The event name for the broadcasted websocket message.
51
51
  * @returns {Function} - The decorator function.
52
52
  */
53
- declare function Broadcast(event: string): Function;
53
+ export declare function Broadcast(event: string): Function;
54
54
  //#endregion
55
55
  //#region src/Decorators/BroadcastOthers.d.ts
56
56
  /**
@@ -62,7 +62,7 @@ declare function Broadcast(event: string): Function;
62
62
  * @param {string} event - The event name for the broadcasted websocket message.
63
63
  * @returns {Function} - The decorator function.
64
64
  */
65
- declare function BroadcastOthers(event: string): Function;
65
+ export declare function BroadcastOthers(event: string): Function;
66
66
  //#endregion
67
67
  //#region src/Decorators/OnConnectionAttempt.d.ts
68
68
  /**
@@ -78,7 +78,7 @@ declare function BroadcastOthers(event: string): Function;
78
78
  *
79
79
  * @returns {Function} - The decorator function.
80
80
  */
81
- declare function OnConnectionAttempt(): Function;
81
+ export declare function OnConnectionAttempt(): Function;
82
82
  //#endregion
83
83
  //#region src/Plugins/WebsocketPlugin.d.ts
84
84
  /**
@@ -101,7 +101,7 @@ declare function OnConnectionAttempt(): Function;
101
101
  *
102
102
  * @see {@link https://vercube.dev} for full documentation
103
103
  */
104
- declare class WebsocketPlugin<T = unknown> extends BasePlugin<T> {
104
+ export declare class WebsocketPlugin<T = unknown> extends BasePlugin<T> {
105
105
  /**
106
106
  * The name of the plugin.
107
107
  * @override
@@ -117,7 +117,7 @@ declare class WebsocketPlugin<T = unknown> extends BasePlugin<T> {
117
117
  }
118
118
  //#endregion
119
119
  //#region src/Types/WebsocketTypes.d.ts
120
- declare namespace WebsocketTypes {
120
+ export declare namespace WebsocketTypes {
121
121
  enum HandlerAction {
122
122
  CONNECTION = "connection",
123
123
  MESSAGE = "message"
@@ -130,14 +130,7 @@ declare namespace WebsocketTypes {
130
130
  }
131
131
  //#endregion
132
132
  //#region src/Services/WebsocketService.d.ts
133
- /**
134
- * WebsocketService class responsible for dealing with Websocket connections.
135
- *
136
- * This class is responsible for:
137
- * - Registering namespaces and accepting websocket connections for them
138
- * - Registering event handlers and handling them
139
- */
140
- declare class WebsocketService {
133
+ export declare class WebsocketService {
141
134
  /**
142
135
  * Http Server for injecting the server plugin
143
136
  */
@@ -213,9 +206,19 @@ declare class WebsocketService {
213
206
  * @returns {Promise<void>}
214
207
  */
215
208
  private handleMessage;
209
+ /**
210
+ * Dispatches one websocket message to its handler.
211
+ *
212
+ * @param {WSPeer} peer - The peer the message came from
213
+ * @param {WSMessage} rawMessage - The raw message
214
+ * @param {Span} span - The span covering this message
215
+ * @returns {Promise<void>} Resolves once the handler has run
216
+ * @private
217
+ */
218
+ private internalHandleMessage;
216
219
  }
217
220
  //#endregion
218
221
  //#region src/Symbols/WebsocketSymbols.d.ts
219
- declare const $WebsocketService: symbol;
222
+ export declare const $WebsocketService: symbol;
220
223
  //#endregion
221
- export { $WebsocketService, Broadcast, BroadcastOthers, Emit, Message, Namespace, OnConnectionAttempt, type WSError, type Message$1 as WSMessage, type Peer as WSPeer, WebsocketPlugin, WebsocketService, WebsocketTypes };
224
+ export type { WSError, Message$1 as WSMessage, Peer as WSPeer };
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { BadRequestError, BasePlugin, HttpServer, ValidationProvider, initializeMetadata, initializeMetadataMethod, safeJsonParse, sanitizeObject } from "@vercube/core";
2
2
  import { BaseDecorator, Identity, Inject, InjectOptional, createDecorator } from "@vercube/di";
3
3
  import { Logger } from "@vercube/logger";
4
+ import { SpanKind, ValueType, createInstrument } from "@vercube/telemetry/instrument";
4
5
  import { defineHooks } from "crossws";
5
6
  import { plugin } from "crossws/server";
6
7
  //#region src/Decorators/Namespace.ts
@@ -33,7 +34,7 @@ let WebsocketTypes;
33
34
  }({});
34
35
  })(WebsocketTypes || (WebsocketTypes = {}));
35
36
  //#endregion
36
- //#region \0@oxc-project+runtime@0.140.0/helpers/esm/decorate.js
37
+ //#region \0@oxc-project+runtime@0.150.0/helpers/esm/decorate.js
37
38
  function __decorate(decorators, target, key, desc) {
38
39
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
39
40
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -49,6 +50,33 @@ function __decorate(decorators, target, key, desc) {
49
50
  * - Registering namespaces and accepting websocket connections for them
50
51
  * - Registering event handlers and handling them
51
52
  */
53
+ /**
54
+ * Records websocket signals.
55
+ *
56
+ * The toolkit comes from `@vercube/telemetry/instrument`, which is the only
57
+ * place in the framework that speaks to OpenTelemetry directly.
58
+ */
59
+ const instrument = createInstrument("@vercube/ws");
60
+ /** Attribute naming the namespace a peer or message belongs to. */
61
+ const WS_NAMESPACE = "vercube.ws.namespace";
62
+ /**
63
+ * Records a change in the number of connected peers.
64
+ *
65
+ * An up/down counter rather than a gauge: connects and disconnects are events
66
+ * the service already sees, and counting them is exact, where sampling a peer
67
+ * list on a timer would miss everything that happened between two samples.
68
+ *
69
+ * @param {number} delta - `1` on connect, `-1` on disconnect
70
+ * @param {string} namespace - The namespace the peer belongs to
71
+ * @returns {void}
72
+ */
73
+ function countConnection(delta, namespace) {
74
+ instrument.upDownCounter("vercube.ws.connections", {
75
+ description: "Currently connected websocket peers.",
76
+ unit: "{connection}",
77
+ valueType: ValueType.INT
78
+ }).add(delta, { [WS_NAMESPACE]: namespace });
79
+ }
52
80
  var WebsocketService = class {
53
81
  /**
54
82
  * Http Server for injecting the server plugin
@@ -153,7 +181,8 @@ var WebsocketService = class {
153
181
  upgrade: async (request) => {
154
182
  const url = new URL(request.url);
155
183
  const namespace = url.pathname;
156
- const parameters = sanitizeObject(Object.fromEntries(url.searchParams));
184
+ const unsafeParameters = Object.fromEntries(url.searchParams);
185
+ const parameters = sanitizeObject(unsafeParameters);
157
186
  if (!!!this.fNamespaces?.[namespace?.toLowerCase()]) {
158
187
  this.gLogger?.warn("WebsocketService::initialize", `Namespace "${namespace}" is not registered. Connection rejected.`);
159
188
  return new Response("Namespace not registered", { status: 403 });
@@ -172,7 +201,10 @@ var WebsocketService = class {
172
201
  },
173
202
  open: async (peer) => {
174
203
  const namespace = peer.namespace?.toLowerCase();
175
- if (namespace && this.fNamespaces[namespace]) this.fNamespaces[namespace].push(peer);
204
+ if (namespace && this.fNamespaces[namespace]) {
205
+ this.fNamespaces[namespace].push(peer);
206
+ countConnection(1, namespace);
207
+ }
176
208
  },
177
209
  message: async (peer, message) => {
178
210
  await this.handleMessage(peer, message);
@@ -182,6 +214,7 @@ var WebsocketService = class {
182
214
  if (namespace && this.fNamespaces[namespace]) {
183
215
  const peers = this.fNamespaces[namespace];
184
216
  this.fNamespaces[namespace] = peers.filter((p) => p.id !== peer.id);
217
+ countConnection(-1, namespace);
185
218
  }
186
219
  },
187
220
  error: async (peer, error) => {
@@ -203,11 +236,28 @@ var WebsocketService = class {
203
236
  * @returns {Promise<void>}
204
237
  */
205
238
  async handleMessage(peer, rawMessage) {
239
+ await instrument.span("ws.message", {
240
+ kind: SpanKind.CONSUMER,
241
+ attributes: { [WS_NAMESPACE]: peer.namespace ?? "" }
242
+ }, (span) => this.internalHandleMessage(peer, rawMessage, span));
243
+ }
244
+ /**
245
+ * Dispatches one websocket message to its handler.
246
+ *
247
+ * @param {WSPeer} peer - The peer the message came from
248
+ * @param {WSMessage} rawMessage - The raw message
249
+ * @param {Span} span - The span covering this message
250
+ * @returns {Promise<void>} Resolves once the handler has run
251
+ * @private
252
+ */
253
+ async internalHandleMessage(peer, rawMessage, span) {
206
254
  try {
207
255
  const msg = safeJsonParse(rawMessage.text());
208
256
  const namespace = peer.namespace?.toLowerCase();
209
257
  const event = msg.event;
210
258
  const data = msg.data;
259
+ span.updateName(`ws.message ${String(event)}`);
260
+ span.setAttribute("vercube.ws.event", String(event));
211
261
  const handler = this.fHandlers[WebsocketTypes.HandlerAction.MESSAGE]?.[namespace]?.[event];
212
262
  if (!handler) {
213
263
  this.gLogger?.warn("WebsocketService::handleMessage", `No message handler for event "${event}" in namespace "${namespace}"`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercube/ws",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Websocket module for Vercube framework",
5
5
  "repository": {
6
6
  "type": "git",
@@ -22,14 +22,15 @@
22
22
  "README.md"
23
23
  ],
24
24
  "devDependencies": {
25
- "zod": "4.4.3"
25
+ "zod": "4.6.5"
26
26
  },
27
27
  "dependencies": {
28
- "crossws": "0.4.10",
29
- "srvx": "0.12.5",
30
- "@vercube/core": "1.2.0",
31
- "@vercube/logger": "1.2.0",
32
- "@vercube/di": "1.2.0"
28
+ "@vercube/core": "1.3.0",
29
+ "@vercube/di": "1.3.0",
30
+ "@vercube/logger": "1.3.0",
31
+ "@vercube/telemetry": "1.3.0",
32
+ "crossws": "0.4.12",
33
+ "srvx": "1.0.5"
33
34
  },
34
35
  "publishConfig": {
35
36
  "access": "public"