Skip to content

@sodacore/ws

The WebSocket plugin will provide real-time capabilities to your Sodacore application, it requires the @sodacore/http package to be installed and simply adds WebSocket handlers to your existing HTTP server and configures a default WebSocket endpoint at /ws (or defined in the config).

WARNING

While in development, be aware when using VSCode, and using the built-in port forwarding feature, that the port forwarding, for some unknown reason, does not transmit the close event to through the WebSocket connection, therefore you may notice that connections do not close properly when a client disconnects, looks like VSCode doesn't drop the connection. Read more.

Installation

To install the plugin, simply install it via bun:

bash
bun install @sodacore/ws

And within your main.ts file you can use the plugin like so:

typescript
import { Application } from '@sodacore/core';
import WsPlugin from '@sodacore/ws';

const app = new Application({
	autowire: true,
});

app.use(new WsPlugin({
	path: '/ws',
}));

app.start().catch(console.error);

Configuration

The WebSocket plugin has the following configuration options:

ts
export type IConfig = {
	path?: string | string[],			// The default path to allow WebSocket connections, defaults to '/ws'.
	keepAlive?: boolean,				// Whether to attempt to keep the connection alive, defaults to true.
	idleTimeout?: number,				// The idle timeout for the connection, defaults to false.
	backpressureLimit?: number,			// The backpressure limit for the connection, defaults to (1024 * 1024) 1MB.
	maxPayloadLength?: number,			// The maximum payload length for the connection, defaults to (1024 * 1024 * 16) 1MB.
	closeOnBackpressureLimit?: boolean,	// Whether to close the connection on backpressure limit, defaults to false.
	publishToSelf?: boolean,			// Whether to publish messages to the sender, defaults to false.
	perMessageDeflate?: boolean,		// Whether to enable per-message deflate, defaults to false.
};

Controllers

You can create WebSocket controllers like so:

TIP

In the WebSocket plugin, the way routing works is you send a structured JSON object that contains a command which is <namespace>:<method> and a context object, which contains any data you want to send to the server.

ts
import { Controller, Expose, WsContext } from '@sodacore/ws';

@Controller('chat')
export class ChatController {

	@Expose()
	public async message(ctx: WsContext) {
		// Handle incoming WebSocket message.
	}
}

Essentially, the controller dictates a namespace, and then each method you want to make available over WebSocket is decorated with the @Expose decorator.

When a client passes a structured packet of data like so:

json
{
	"command": "chat:message",
	"context": {
		"some": "data"
	}
}

The message method on the ChatController will be invoked, and the context object will be passed as part of the WsContext argument.

WsContext

The WsContext object contains the following properties:

  • getId(): string - A method that returns the unique ID of the WebSocket connection.
  • getRemoteAddress(): string - A method that returns the remote address of the WebSocket connection.
  • send(command: string, context: Record<string, any>): void - A method that sends a message to the client.
  • sendRaw(data: string): void - A method that sends raw data to the client.
  • getData<T = Record<string, any>>(): T - A method that returns the context data sent by the client.
  • close(reason?: string, code?: number): void - A method that closes the WebSocket connection.

This context is passed into every exposed method, so you can access the data and get easy access to the send methods.

Transformers

The WebSocket plugin does support a per-controller, per-method style of transformers (not global ones like HTTP), you can create a transformer like so:

ts
import type { WsContext } from '@sodacore/ws';

export function MyTransformer(context: WsContext, data: any) {
	// Transform the data here.
	return data;
}

You can then apply these transformers to a controller or method like so:

ts
import { Controller, Expose, Transform } from '@sodacore/ws';

@Controller('chat')
@Transform(MyTransformer) // Apply to all methods in the controller.
export class ChatController {

	@Expose()
	@Transform(AnotherTransformer) // Apply to this specific method.
	public async message(ctx: WsContext) {
		// Handle incoming WebSocket message.
	}
}

WsConnections

The WsConnections provider allows you to access all active WebSocket connections, and send messages to them.

It has the same functions as the SseConnectionsProvider from the @sodacore/http package;

  • broadcast(command: string, context: Record<string, any> = {}) - Broadcasts a packet to all connected clients.
  • broadcastRaw(data: string) - Broadcasts raw data to all connected clients (you must format it yourself).
  • broadcastFor(id: string | string[], command: string, context: Record<string, any> = {}) - Broadcasts a packet to a specific client or clients by ID.
  • broadcastRawFor(id: string | string[], data: string) - Broadcasts raw data to a specific client or clients by ID (you must format it yourself).
  • broadcastExcept(id: string | string[], command: string, context: Record<string, any> = {}) - Broadcasts a packet to all connected clients except the given ID or IDs.
  • broadcastRawExcept(id: string | string[], data: string) - Broadcasts raw data to all connected clients except the given ID or IDs (you must format it yourself).

There are other methods for managing connections, but you likely won't need to use those as the plugin manages them automatically, but you can use type-hinting within your editor to see what is available.

Packets

The WebSocket plugin expects packets to be structured in a specific way, as mentioned above, the packet must contain a command and a context object.

A command is a string that is structured as <namespace>:<method>, where namespace is the controller name, and method is the exposed method name. The context object is simply an object that contains any data you want to send to the server.

Hooks

The WebSocket plugin also provides a few available hooks that can be used to run code when specific things happen, as of right now, the available hooks are:

  • wsOpen - Called when a WebSocket connection is opened.
  • wsClose - Called when a WebSocket connection is closed.
  • wsDrain - Called when a WebSocket connection is drained.

Released under the Apache-2.0 License.