Skip to content

WebSocket

OzeanJs has a built-in system for real-time communication via WebSockets, using an architecture called Gateways. This makes managing connections and events systematic and easy.

What are Gateways?

A Gateway is a class that acts like a Controller but operates on WebSocket events instead of HTTP requests. It's a "gate" for bi-directional communication between the client and the server.

Main responsibilities of a Gateway:

  • Listen for messages from clients via specified events.
  • Emit messages back to clients (either specifically, broadcast, or to a room).
  • Manage Connection Lifecycle: Such as when a client connects (connection) or disconnects (disconnect).

Creating a Gateway

We create a Gateway by creating a class decorated with @WebSocketGateway() and should implement various Lifecycle Hooks to manage connections.

1. @WebSocketGateway(options?: object)

  • @WebSocketGateway(): A class decorator to mark a class as a Gateway.
  • options (Optional): In the future, it will be able to accept options like port or path (not yet implemented).

2. Lifecycle Hooks

These are Interfaces you can implement in your Gateway class, and OzeanJs will call these methods automatically.

  • OnGatewayInit: Has an afterInit(server: Server) method, called after the Gateway is created and the Server is ready. Ideal for logging or initial setup.
  • OnGatewayConnection: Has a handleConnection(client: ServerWebSocket, request: Request) method, called every time a new client connects.
  • OnGatewayDisconnect: Has a handleDisconnect(client: ServerWebSocket) method, called every time a client disconnects.

3. Receiving and Sending Messages

We use various decorators to handle incoming messages.

  • @SubscribeMessage('event_name'): A method decorator to bind a method to handle messages with a matching event name.
  • @MessageBody(): A parameter decorator to extract the data (payload) sent with an event.
  • @ConnectedSocket(): A parameter decorator to inject the ServerWebSocket instance of the client sending the message.
  • @WebSocketServer(): A property decorator to inject the Bun.Server instance into the Gateway class, used for broadcasting or managing all clients.

Usage Example: Complete ChatGateway

Here is an example of creating a Chat Gateway that demonstrates the use of all these features together.

typescript
// src/chat/chat.gateway.ts
import {
  WebSocketGateway,
  SubscribeMessage,
  MessageBody,
  ConnectedSocket,
  WebSocketServer,
  OnGatewayInit,
  OnGatewayConnection,
  OnGatewayDisconnect,
  Injectable,
} from 'ozeanjs';
import type { Server, ServerWebSocket } from 'bun';

@WebSocketGateway()
@Injectable()
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer()
  server!: Server; // 1. Inject the server instance to use for broadcasting

  // --- Lifecycle Hooks ---

  afterInit(server: Server) {
    console.log(`[ChatGateway] Gateway initialized on port ${server.port}`);
  }

  handleConnection(client: ServerWebSocket<any>, request: Request) {
    console.log(`[ChatGateway] Client connected: ${client.remoteAddress}`);
    // Automatically subscribe every client to the "chat_room"
    client.subscribe('chat_room');
    // Send a welcome message back to the newly connected client
    client.send(JSON.stringify({ event: 'connection', data: 'Welcome to the OzeanJs Chat!' }));
  }

  handleDisconnect(client: ServerWebSocket<any>) {
    console.log(`[ChatGateway] Client disconnected: ${client.remoteAddress}`);
    // Optionally, notify everyone in the room that a user has left
    this.server.publish(
      'chat_room',
      JSON.stringify({
        event: 'user-left',
        data: `User ${client.remoteAddress} has left the chat.`,
      })
    );
  }

  // --- Message Handlers ---

  @SubscribeMessage('message')
  handleMessage(
    @MessageBody() data: { text: string },
    @ConnectedSocket() client: ServerWebSocket<any>
  ): void {
    // A handler doesn't necessarily need to return a value
    console.log(`[ChatGateway] Received message: "${data.text}" from ${client.remoteAddress}`);

    const response = {
      event: 'message',
      data: {
        user: `User-${client.remoteAddress}`,
        text: data.text, // Echo the original message
      },
    };

    // Broadcast the message to everyone in the 'chat_room'
    this.server.publish('chat_room', JSON.stringify(response));
  }

  @SubscribeMessage('private-message')
  handlePrivateMessage(@MessageBody() data: any, @ConnectedSocket() client: ServerWebSocket<any>) {
    // A handler can return a value, and the framework will send it back only to the client who sent the message
    console.log(`[ChatGateway] Received private message:`, data);
    return { event: 'private-reply', data: `Your private message was received!` };
  }
}

Registering a Gateway in a Module

Finally, remember that a Gateway is also a type of Provider, so you must register it in the providers array of the relevant Module.

typescript
// src/chat/chat.module.ts
import { Module } from 'ozeanjs';
import { ChatGateway } from './chat.gateway';

@Module({
  providers: [ChatGateway],
})
export class ChatModule {}