@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:
bun install @sodacore/ws
And within your main.ts
file you can use the plugin like so:
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:
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.
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:
{
"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:
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:
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.