@onebun/core 0.1.19 → 0.1.21

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.
@@ -15,7 +15,7 @@ import type { WsServiceDefinition } from './ws-service-definition';
15
15
 
16
16
  import { useFakeTimers } from '../testing/test-utils';
17
17
 
18
- import { createWsClient } from './ws-client';
18
+ import { createWsClient, createNativeWsClient } from './ws-client';
19
19
  import { WsConnectionState } from './ws-client.types';
20
20
  import { WsHandlerType } from './ws.types';
21
21
 
@@ -114,7 +114,7 @@ describe('WsClient', () => {
114
114
  it('should create client with default options', () => {
115
115
  const definition = createMockDefinition();
116
116
  const client = createWsClient(definition, { url: 'ws://localhost:3000' });
117
-
117
+
118
118
  expect(client).toBeDefined();
119
119
  expect(typeof client.connect).toBe('function');
120
120
  expect(typeof client.disconnect).toBe('function');
@@ -123,6 +123,40 @@ describe('WsClient', () => {
123
123
  });
124
124
  });
125
125
 
126
+ describe('createNativeWsClient', () => {
127
+ it('should create standalone client without definition', () => {
128
+ const client = createNativeWsClient({ url: 'ws://localhost:3000/chat' });
129
+
130
+ expect(client).toBeDefined();
131
+ expect(typeof client.connect).toBe('function');
132
+ expect(typeof client.disconnect).toBe('function');
133
+ expect(typeof client.isConnected).toBe('function');
134
+ expect(typeof client.getState).toBe('function');
135
+ expect(typeof client.on).toBe('function');
136
+ expect(typeof client.off).toBe('function');
137
+ expect(typeof client.emit).toBe('function');
138
+ expect(typeof client.send).toBe('function');
139
+ });
140
+
141
+ it('should connect and use emit/send/on like typed client', async () => {
142
+ const client = createNativeWsClient({ url: 'ws://localhost:3000/chat' });
143
+ await client.connect();
144
+
145
+ const ws = MockWebSocket.getLastInstance();
146
+ expect(ws).toBeDefined();
147
+ expect(client.isConnected()).toBe(true);
148
+
149
+ client.on('welcome', (data) => expect(data).toBeDefined());
150
+ ws?.receiveMessage(JSON.stringify({ event: 'welcome', data: { msg: 'hi' } }));
151
+
152
+ client.send('ping', {});
153
+ expect(ws?.sentMessages.some((m) => m.includes('ping'))).toBe(true);
154
+
155
+ client.disconnect();
156
+ expect(client.isConnected()).toBe(false);
157
+ });
158
+ });
159
+
126
160
  describe('connect', () => {
127
161
  it('should connect to WebSocket server', async () => {
128
162
  const definition = createMockDefinition();
@@ -312,20 +346,23 @@ describe('WsClient', () => {
312
346
  expect(handler).toHaveBeenCalled();
313
347
  });
314
348
 
315
- it('should handle Engine.IO PING packet', async () => {
349
+ it('should handle Engine.IO PING packet when using Socket.IO protocol', async () => {
316
350
  const definition = createMockDefinition();
317
- const client = createWsClient(definition, { url: 'ws://localhost:3000' });
318
-
351
+ const client = createWsClient(definition, {
352
+ url: 'ws://localhost:3000/socket.io',
353
+ protocol: 'socketio',
354
+ });
355
+
319
356
  await client.connect();
320
-
357
+
321
358
  const ws = MockWebSocket.getLastInstance();
322
359
  if (ws) {
323
- ws.sentMessages.length = 0; // Clear previous messages
324
-
360
+ ws.sentMessages.length = 0;
361
+
325
362
  // Send PING (Engine.IO packet type 2)
326
363
  ws.receiveMessage('2');
327
364
  }
328
-
365
+
329
366
  // Should respond with PONG (Engine.IO packet type 3)
330
367
  expect(ws?.sentMessages).toContain('3');
331
368
  });
@@ -6,27 +6,49 @@
6
6
  */
7
7
 
8
8
  /* eslint-disable @typescript-eslint/no-magic-numbers */
9
+ /* eslint-disable import/order -- type vs value from same path; keep single mixed import */
9
10
 
10
- import type {
11
- WsClientOptions,
12
- WsClient,
13
- WsGatewayClient,
14
- WsEventListener,
15
- WsClientEvent,
16
- WsClientEventListeners,
17
- PendingRequest,
18
- TypedWsClient,
11
+ import {
12
+ type NativeWsClient,
13
+ type PendingRequest,
14
+ type TypedWsClient,
15
+ type WsClient,
16
+ type WsClientEventListeners,
17
+ type WsClientEvent,
18
+ type WsClientOptions,
19
+ type WsEventListener,
20
+ type WsGatewayClient,
21
+ WsConnectionState,
19
22
  } from './ws-client.types';
20
23
  import type { WsServiceDefinition } from './ws-service-definition';
21
24
 
22
- import { WsConnectionState } from './ws-client.types';
25
+ /** Client lifecycle event names (subscriptions go to client, not server events) */
26
+ const WS_CLIENT_EVENTS: WsClientEvent[] = [
27
+ 'connect',
28
+ 'disconnect',
29
+ 'error',
30
+ 'reconnect',
31
+ 'reconnect_attempt',
32
+ 'reconnect_failed',
33
+ ];
34
+
35
+ function isClientEvent(event: string): event is WsClientEvent {
36
+ return WS_CLIENT_EVENTS.includes(event as WsClientEvent);
37
+ }
38
+
39
+ /** Dummy definition for standalone client (single logical gateway) */
40
+ const NATIVE_WS_DUMMY_DEFINITION: WsServiceDefinition = {
41
+ _module: null,
42
+ _endpoints: [],
43
+ _gateways: new Map([['default', { name: 'default', path: '/', events: new Map() }]]),
44
+ };
23
45
  import { matchPattern, isPattern } from './ws-pattern-matcher';
24
46
  import {
25
47
  parseMessage,
26
48
  createPongPacket,
49
+ createFullEventMessage,
27
50
  EngineIOPacketType,
28
51
  SocketIOPacketType,
29
- isNativeMessage,
30
52
  parseNativeMessage,
31
53
  createNativeMessage,
32
54
  } from './ws-socketio-protocol';
@@ -35,6 +57,7 @@ import {
35
57
  * Default client options
36
58
  */
37
59
  const DEFAULT_OPTIONS: Partial<WsClientOptions> = {
60
+ protocol: 'native',
38
61
  reconnect: true,
39
62
  reconnectInterval: 1000,
40
63
  maxReconnectAttempts: 10,
@@ -85,23 +108,22 @@ class WsClientImpl<TDef extends WsServiceDefinition> implements WsClient<TDef> {
85
108
  let url = this.options.url;
86
109
  const params = new URLSearchParams();
87
110
 
88
- // Add Socket.IO parameters
89
- params.set('EIO', '4');
90
- params.set('transport', 'websocket');
111
+ const protocol = this.options.protocol ?? 'native';
112
+ if (protocol === 'socketio') {
113
+ params.set('EIO', '4');
114
+ params.set('transport', 'websocket');
115
+ }
91
116
 
92
- // Add auth token
93
117
  if (this.options.auth?.token) {
94
118
  params.set('token', this.options.auth.token);
95
119
  }
96
120
 
97
- // Add namespace
98
121
  if (this.options.namespace) {
99
122
  params.set('namespace', this.options.namespace);
100
123
  }
101
124
 
102
- // Append params to URL
103
125
  const separator = url.includes('?') ? '&' : '?';
104
- url = `${url}${separator}${params.toString()}`;
126
+ url = params.toString() ? `${url}${separator}${params.toString()}` : url;
105
127
 
106
128
  // Create WebSocket connection
107
129
  // Use globalThis.WebSocket for browser compatibility
@@ -204,23 +226,22 @@ class WsClientImpl<TDef extends WsServiceDefinition> implements WsClient<TDef> {
204
226
  * Handle incoming message
205
227
  */
206
228
  private handleMessage(data: string): void {
207
- // Try native format first
208
- if (isNativeMessage(data)) {
229
+ const protocol = this.options.protocol ?? 'native';
230
+
231
+ if (protocol === 'native') {
209
232
  const native = parseNativeMessage(data);
210
233
  if (native) {
211
234
  this.handleEvent(native.event, native.data, native.ack);
212
-
213
- return;
214
235
  }
236
+
237
+ return;
215
238
  }
216
239
 
217
- // Parse Socket.IO format
240
+ // Socket.IO format
218
241
  const { engineIO, socketIO } = parseMessage(data);
219
242
 
220
- // Handle Engine.IO packets
221
243
  switch (engineIO.type) {
222
244
  case EngineIOPacketType.OPEN:
223
- // Handle handshake
224
245
  if (engineIO.data) {
225
246
  try {
226
247
  const handshake = JSON.parse(engineIO.data as string);
@@ -239,7 +260,6 @@ class WsClientImpl<TDef extends WsServiceDefinition> implements WsClient<TDef> {
239
260
  return;
240
261
 
241
262
  case EngineIOPacketType.PONG:
242
- // Server responded to our ping
243
263
  return;
244
264
 
245
265
  case EngineIOPacketType.CLOSE:
@@ -248,7 +268,6 @@ class WsClientImpl<TDef extends WsServiceDefinition> implements WsClient<TDef> {
248
268
  return;
249
269
 
250
270
  case EngineIOPacketType.MESSAGE:
251
- // Socket.IO packet
252
271
  if (socketIO) {
253
272
  this.handleSocketIOPacket(socketIO);
254
273
  }
@@ -468,8 +487,11 @@ class WsClientImpl<TDef extends WsServiceDefinition> implements WsClient<TDef> {
468
487
  throw new Error('Not connected');
469
488
  }
470
489
 
471
- // Use native format (simpler)
472
- const message = createNativeMessage(event, data, ackId);
490
+ const protocol = this.options.protocol ?? 'native';
491
+ const message =
492
+ protocol === 'socketio'
493
+ ? createFullEventMessage(event, data ?? {}, '/', ackId)
494
+ : createNativeMessage(event, data, ackId);
473
495
  this.ws.send(message);
474
496
  }
475
497
 
@@ -626,3 +648,64 @@ export function createWsClient<TDef extends WsServiceDefinition>(
626
648
  },
627
649
  });
628
650
  }
651
+
652
+ /**
653
+ * Create a standalone WebSocket client without a service definition.
654
+ * Uses the same native message format and API (emit, send, on, off) as the typed client.
655
+ * Use in frontend or when you do not want to depend on backend modules.
656
+ *
657
+ * @param options - Client options (url, protocol, auth, reconnect, etc.)
658
+ * @returns Standalone client with connect, disconnect, on, off, emit, send
659
+ *
660
+ * @example
661
+ * ```typescript
662
+ * import { createNativeWsClient } from '@onebun/core';
663
+ *
664
+ * const client = createNativeWsClient({
665
+ * url: 'ws://localhost:3000/chat',
666
+ * protocol: 'native',
667
+ * auth: { token: 'xxx' },
668
+ * });
669
+ *
670
+ * await client.connect();
671
+ * client.on('welcome', (data) => console.log(data));
672
+ * client.on('connect', () => console.log('Connected'));
673
+ *
674
+ * await client.emit('chat:message', { text: 'Hello' });
675
+ * client.send('typing', {});
676
+ * client.disconnect();
677
+ * ```
678
+ */
679
+ export function createNativeWsClient(options: WsClientOptions): NativeWsClient {
680
+ const typed = createWsClient(NATIVE_WS_DUMMY_DEFINITION, options);
681
+ const gateway = (typed as unknown as Record<string, WsGatewayClient>).default;
682
+
683
+ return {
684
+ connect: () => typed.connect(),
685
+ disconnect: () => typed.disconnect(),
686
+ isConnected: () => typed.isConnected(),
687
+ getState: () => typed.getState(),
688
+
689
+ on(event: string, listener: WsEventListener | WsClientEventListeners[WsClientEvent]): void {
690
+ if (isClientEvent(event)) {
691
+ typed.on(event, listener as WsClientEventListeners[typeof event]);
692
+ } else {
693
+ gateway.on(event, listener as WsEventListener);
694
+ }
695
+ },
696
+
697
+ off(
698
+ event: string,
699
+ listener?: WsEventListener | WsClientEventListeners[WsClientEvent],
700
+ ): void {
701
+ if (isClientEvent(event)) {
702
+ typed.off(event, listener as WsClientEventListeners[typeof event]);
703
+ } else {
704
+ gateway.off(event, listener as WsEventListener);
705
+ }
706
+ },
707
+
708
+ emit: <T = unknown>(event: string, data?: unknown) => gateway.emit<T>(event, data),
709
+ send: (event: string, data?: unknown) => gateway.send(event, data),
710
+ };
711
+ }
@@ -6,12 +6,19 @@
6
6
 
7
7
  import type { WsServiceDefinition, WsGatewayDefinition } from './ws-service-definition';
8
8
 
9
+ /**
10
+ * WebSocket protocol to use when connecting
11
+ */
12
+ export type WsClientProtocol = 'native' | 'socketio';
13
+
9
14
  /**
10
15
  * Options for WebSocket client
11
16
  */
12
17
  export interface WsClientOptions {
13
- /** WebSocket server URL */
18
+ /** WebSocket server URL (for Socket.IO use the server root or socketio path, e.g. ws://host/socket.io) */
14
19
  url: string;
20
+ /** Protocol to use (default: 'native') */
21
+ protocol?: WsClientProtocol;
15
22
  /** Authentication options */
16
23
  auth?: {
17
24
  /** Bearer token */
@@ -127,3 +134,25 @@ export interface PendingRequest {
127
134
  reject: (error: Error) => void;
128
135
  timeout: ReturnType<typeof setTimeout>;
129
136
  }
137
+
138
+ /**
139
+ * Standalone WebSocket client (no service definition).
140
+ * Same message format and API as the typed client, but without gateway proxies.
141
+ * Use in frontend or when you do not want to depend on backend module/definitions.
142
+ */
143
+ export interface NativeWsClient {
144
+ connect(): Promise<void>;
145
+ disconnect(): void;
146
+ isConnected(): boolean;
147
+ getState(): WsConnectionState;
148
+ /** Lifecycle events: connect, disconnect, error, reconnect, reconnect_attempt, reconnect_failed */
149
+ on<E extends WsClientEvent>(event: E, listener: WsClientEventListeners[E]): void;
150
+ /** Server events (event names from your gateway) */
151
+ on<T = unknown>(event: string, listener: WsEventListener<T>): void;
152
+ off<E extends WsClientEvent>(event: E, listener?: WsClientEventListeners[E]): void;
153
+ off(event: string, listener?: WsEventListener): void;
154
+ /** Send event and wait for acknowledgement */
155
+ emit<T = unknown>(event: string, data?: unknown): Promise<T>;
156
+ /** Send event without waiting for response */
157
+ send(event: string, data?: unknown): void;
158
+ }
@@ -32,6 +32,7 @@ describe('ws-guards', () => {
32
32
  connectedAt: Date.now(),
33
33
  auth: null,
34
34
  metadata: {},
35
+ protocol: 'native',
35
36
  ...overrides,
36
37
  });
37
38
 
@@ -13,6 +13,7 @@ import type {
13
13
  WsHandlerMetadata,
14
14
  WebSocketApplicationOptions,
15
15
  } from './ws.types';
16
+ import type { WsHandlerResponse } from './ws.types';
16
17
  import type { Server, ServerWebSocket } from 'bun';
17
18
 
18
19
  import type { SyncLogger } from '@onebun/logger';
@@ -30,7 +31,6 @@ import {
30
31
  createFullEventMessage,
31
32
  EngineIOPacketType,
32
33
  SocketIOPacketType,
33
- isNativeMessage,
34
34
  parseNativeMessage,
35
35
  createNativeMessage,
36
36
  DEFAULT_PING_INTERVAL,
@@ -68,14 +68,19 @@ export class WsHandler {
68
68
  private pingTimeoutMs: number;
69
69
  private maxPayload: number;
70
70
  private pingIntervals: Map<string, ReturnType<typeof setInterval>> = new Map();
71
+ private socketioEnabled: boolean;
72
+ private socketioPath: string;
71
73
 
72
74
  constructor(
73
75
  private logger: SyncLogger,
74
76
  private options: WebSocketApplicationOptions = {},
75
77
  ) {
76
78
  this.storage = new InMemoryWsStorage();
77
- this.pingIntervalMs = options.pingInterval ?? DEFAULT_PING_INTERVAL;
78
- this.pingTimeoutMs = options.pingTimeout ?? DEFAULT_PING_TIMEOUT;
79
+ const socketio = options.socketio;
80
+ this.socketioEnabled = socketio?.enabled ?? false;
81
+ this.socketioPath = socketio?.path ?? '/socket.io';
82
+ this.pingIntervalMs = socketio?.pingInterval ?? DEFAULT_PING_INTERVAL;
83
+ this.pingTimeoutMs = socketio?.pingTimeout ?? DEFAULT_PING_TIMEOUT;
79
84
  this.maxPayload = options.maxPayload ?? DEFAULT_MAX_PAYLOAD;
80
85
  }
81
86
 
@@ -183,15 +188,20 @@ export class WsHandler {
183
188
  const url = new URL(req.url);
184
189
  const path = url.pathname;
185
190
 
186
- // Find matching gateway
187
- const gateway = this.getGatewayForPath(path);
188
- if (!gateway) {
189
- return new Response('Not Found', { status: 404 });
191
+ let protocol: WsClientData['protocol'] = 'native';
192
+
193
+ if (this.socketioEnabled && path.startsWith(this.socketioPath)) {
194
+ protocol = 'socketio';
195
+ } else {
196
+ const gateway = this.getGatewayForPath(path);
197
+ if (!gateway) {
198
+ return new Response('Not Found', { status: 404 });
199
+ }
190
200
  }
191
201
 
192
202
  // Extract auth from query or headers
193
- const token = url.searchParams.get('token') ||
194
- req.headers.get('Authorization')?.replace('Bearer ', '');
203
+ const token = url.searchParams.get('token') ||
204
+ req.headers.get('Authorization')?.replace('Bearer ', '');
195
205
 
196
206
  // Create client ID
197
207
  const clientId = crypto.randomUUID();
@@ -201,11 +211,14 @@ export class WsHandler {
201
211
  id: clientId,
202
212
  rooms: [],
203
213
  connectedAt: Date.now(),
204
- auth: token ? {
205
- authenticated: false,
206
- token,
207
- } : null,
214
+ auth: token
215
+ ? {
216
+ authenticated: false,
217
+ token,
218
+ }
219
+ : null,
208
220
  metadata: {},
221
+ protocol,
209
222
  };
210
223
 
211
224
  // Try to upgrade
@@ -225,7 +238,7 @@ export class WsHandler {
225
238
  */
226
239
  private async handleOpen(ws: ServerWebSocket<WsClientData>): Promise<void> {
227
240
  const client = ws.data;
228
- this.logger.debug(`WebSocket client connected: ${client.id}`);
241
+ this.logger.debug(`WebSocket client connected: ${client.id} (${client.protocol})`);
229
242
 
230
243
  // Store client
231
244
  await this.storage.addClient(client);
@@ -235,16 +248,16 @@ export class WsHandler {
235
248
  gateway.instance._registerSocket(client.id, ws);
236
249
  }
237
250
 
238
- // Send Socket.IO handshake
239
- const handshake = createHandshake(client.id, {
240
- pingInterval: this.pingIntervalMs,
241
- pingTimeout: this.pingTimeoutMs,
242
- maxPayload: this.maxPayload,
243
- });
244
- ws.send(createOpenPacket(handshake));
245
-
246
- // Start ping interval
247
- this.startPingInterval(client.id, ws);
251
+ if (client.protocol === 'socketio') {
252
+ // Send Socket.IO handshake
253
+ const handshake = createHandshake(client.id, {
254
+ pingInterval: this.pingIntervalMs,
255
+ pingTimeout: this.pingTimeoutMs,
256
+ maxPayload: this.maxPayload,
257
+ });
258
+ ws.send(createOpenPacket(handshake));
259
+ this.startPingInterval(client.id, ws);
260
+ }
248
261
 
249
262
  // Call OnConnect handlers
250
263
  for (const [_, gateway] of this.gateways) {
@@ -253,7 +266,7 @@ export class WsHandler {
253
266
  try {
254
267
  const result = await this.executeHandler(gateway, handler, ws, undefined, {});
255
268
  if (result && isWsHandlerResponse(result)) {
256
- ws.send(createNativeMessage(result.event, result.data));
269
+ ws.send(this.encodeResponse(client.protocol, result));
257
270
  }
258
271
  } catch (error) {
259
272
  this.logger.error(`Error in OnConnect handler: ${error}`);
@@ -262,6 +275,25 @@ export class WsHandler {
262
275
  }
263
276
  }
264
277
 
278
+ /**
279
+ * Encode handler response for the client's protocol
280
+ */
281
+ private encodeResponse(
282
+ protocol: WsClientData['protocol'],
283
+ result: WsHandlerResponse,
284
+ ackId?: number,
285
+ ): string {
286
+ if (protocol === 'socketio') {
287
+ if (ackId !== undefined) {
288
+ return createFullAckMessage(ackId, result);
289
+ }
290
+
291
+ return createFullEventMessage(result.event, result.data);
292
+ }
293
+
294
+ return createNativeMessage(result.event, result.data, ackId);
295
+ }
296
+
265
297
  /**
266
298
  * Handle incoming message
267
299
  */
@@ -270,21 +302,20 @@ export class WsHandler {
270
302
  message: string | Buffer,
271
303
  ): Promise<void> {
272
304
  const messageStr = typeof message === 'string' ? message : message.toString();
305
+ const protocol = ws.data.protocol;
273
306
 
274
- // Try native format first
275
- if (isNativeMessage(messageStr)) {
307
+ if (protocol === 'native') {
276
308
  const native = parseNativeMessage(messageStr);
277
309
  if (native) {
278
310
  await this.routeMessage(ws, native.event, native.data, native.ack);
279
-
280
- return;
281
311
  }
312
+
313
+ return;
282
314
  }
283
315
 
284
- // Parse Socket.IO format
316
+ // Socket.IO format
285
317
  const { engineIO, socketIO } = parseMessage(messageStr);
286
318
 
287
- // Handle Engine.IO packets
288
319
  switch (engineIO.type) {
289
320
  case EngineIOPacketType.PING:
290
321
  ws.send(createPongPacket(engineIO.data as string | undefined));
@@ -292,7 +323,6 @@ export class WsHandler {
292
323
  return;
293
324
 
294
325
  case EngineIOPacketType.PONG:
295
- // Client responded to ping - connection is alive
296
326
  return;
297
327
 
298
328
  case EngineIOPacketType.CLOSE:
@@ -301,7 +331,6 @@ export class WsHandler {
301
331
  return;
302
332
 
303
333
  case EngineIOPacketType.MESSAGE:
304
- // Socket.IO packet
305
334
  if (socketIO) {
306
335
  await this.handleSocketIOPacket(ws, socketIO);
307
336
  }
@@ -378,14 +407,10 @@ export class WsHandler {
378
407
  if (match.matched) {
379
408
  try {
380
409
  const result = await this.executeHandler(gateway, handler, ws, data, match.params);
381
-
410
+
382
411
  // Send response
383
- if (result !== undefined) {
384
- if (ackId !== undefined) {
385
- ws.send(createFullAckMessage(ackId, result));
386
- } else if (isWsHandlerResponse(result)) {
387
- ws.send(createNativeMessage(result.event, result.data));
388
- }
412
+ if (result !== undefined && isWsHandlerResponse(result)) {
413
+ ws.send(this.encodeResponse(ws.data.protocol, result, ackId));
389
414
  }
390
415
  } catch (error) {
391
416
  this.logger.error(`Error in message handler: ${error}`);
@@ -430,12 +455,8 @@ export class WsHandler {
430
455
  if (match.matched) {
431
456
  try {
432
457
  const result = await this.executeHandler(gateway, handler, ws, data, match.params, roomName);
433
- if (result !== undefined) {
434
- if (ackId !== undefined) {
435
- ws.send(createFullAckMessage(ackId, result));
436
- } else if (isWsHandlerResponse(result)) {
437
- ws.send(createNativeMessage(result.event, result.data));
438
- }
458
+ if (result !== undefined && isWsHandlerResponse(result)) {
459
+ ws.send(this.encodeResponse(ws.data.protocol, result, ackId));
439
460
  }
440
461
  } catch (error) {
441
462
  this.logger.error(`Error in OnJoinRoom handler: ${error}`);
@@ -478,12 +499,8 @@ export class WsHandler {
478
499
  if (match.matched) {
479
500
  try {
480
501
  const result = await this.executeHandler(gateway, handler, ws, data, match.params, roomName);
481
- if (result !== undefined) {
482
- if (ackId !== undefined) {
483
- ws.send(createFullAckMessage(ackId, result));
484
- } else if (isWsHandlerResponse(result)) {
485
- ws.send(createNativeMessage(result.event, result.data));
486
- }
502
+ if (result !== undefined && isWsHandlerResponse(result)) {
503
+ ws.send(this.encodeResponse(ws.data.protocol, result, ackId));
487
504
  }
488
505
  } catch (error) {
489
506
  this.logger.error(`Error in OnLeaveRoom handler: ${error}`);
@@ -28,6 +28,7 @@ describe('InMemoryWsStorage', () => {
28
28
  connectedAt: Date.now(),
29
29
  auth: null,
30
30
  metadata: {},
31
+ protocol: 'native',
31
32
  });
32
33
 
33
34
  it('should add and retrieve a client', async () => {
@@ -135,6 +136,7 @@ describe('InMemoryWsStorage', () => {
135
136
  connectedAt: Date.now(),
136
137
  auth: null,
137
138
  metadata: {},
139
+ protocol: 'native',
138
140
  });
139
141
 
140
142
  it('should add client to room', async () => {
@@ -227,6 +229,7 @@ describe('InMemoryWsStorage', () => {
227
229
  connectedAt: Date.now(),
228
230
  auth: null,
229
231
  metadata: {},
232
+ protocol: 'native',
230
233
  };
231
234
  await storage.addClient(client);
232
235
  await storage.createRoom({ name: 'test-room', clientIds: [] });
@@ -78,6 +78,7 @@ describe('RedisWsStorage', () => {
78
78
  connectedAt: Date.now(),
79
79
  auth: null,
80
80
  metadata: {},
81
+ protocol: 'native',
81
82
  ...options,
82
83
  });
83
84