Portals & Gates

Overview

Portals are the entry points for real-time client connections. They define how packets are routed and handled. Each portal is a class extending Portal, annotated with [Portal("/path")], and containing [Gate("event")] methods that handle specific events.

Creating a Portal

using Altruist;

[Portal("/ws")]
public class ChatPortal : Portal
{
    private readonly IAltruistRouter _router;

    public ChatPortal(IAltruistRouter router)
    {
        _router = router;
    }

    [Gate("chat")]
    public async Task OnChat(ChatPacket packet, string clientId)
    {
        await _router.Broadcast.SendAsync(packet, excludeClientId: clientId);
    }

    [Gate("whisper")]
    public async Task OnWhisper(WhisperPacket packet, string clientId)
    {
        await _router.Client.SendAsync(packet.TargetId, packet);
    }
}

The [Portal("/ws")] attribute maps this portal to the WebSocket path /ws. Each [Gate] method handles a specific event name.

Loading diagram...

How the Client Sends Events

The client sends a packet with messageCode: 4 and an event field. The framework routes it to the matching [Gate]:

[MessagePackObject]
public class EventPacket
{
    [Key(0)][JsonPropertyName("messageCode")] public uint MessageCode { get; set; } = 4;
    [Key(1)][JsonPropertyName("event")] public string Event { get; set; } = "";
}

Note:

messageCode: 4 is the routing packet. The event field determines which [Gate] receives it. All other fields in the JSON are deserialized into your packet type on the server.

What is a Gate?

A Gate defines the event that the portal receives. Methods annotated with [Gate("event-name")] are called when a client sends a packet with the matching event. Each gate method takes exactly two parameters:

  1. A packet — any struct/class implementing IPacketBase
  2. A clientId (string) — the ID of the client that sent the message
[Gate("join-game")]
public async Task JoinGame(JoinGamePacket packet, string clientId)
{
    // packet contains the deserialized client data
    // clientId identifies who sent it
}

Multiple Portals on the Same Path

A powerful pattern is registering multiple portals on the same endpoint. Each portal handles different events:

[Portal("/game")]
public class LoginPortal : Portal
{
    [Gate("login")]
    public async Task OnLogin(LoginPacket packet, string clientId) { ... }

    [Gate("character-select")]
    public async Task OnSelect(SelectPacket packet, string clientId) { ... }
}

[Portal("/game")]
public class ChatPortal : Portal
{
    [Gate("chat")]
    public async Task OnChat(ChatPacket packet, string clientId) { ... }
}

[Portal("/game")]
public class CombatPortal : Portal
{
    [Gate("attack")]
    public async Task OnAttack(AttackPacket packet, string clientId) { ... }
}

All three portals share the same connection path /game but handle completely different events. This keeps your code modular.

Connection Lifecycle

Portals can hook into connection and disconnection events by implementing interfaces:

OnConnectedAsync

[Portal("/ws")]
public class MyPortal : Portal, OnConnectedAsync
{
    public async Task OnConnectedAsync(
        string clientId,
        ConnectionManager connectionManager,
        AltruistConnection connection)
    {
        // Called when a new client connects to this portal's route
    }
}

OnDisconnectedAsync

[Portal("/ws")]
public class MyPortal : Portal, OnDisconnectedAsync
{
    public async Task OnDisconnectedAsync(string clientId, Exception? exception)
    {
        // Called when a client disconnects
        // exception is non-null if the disconnect was due to an error
    }
}

Portal with Router

Every portal can inject IAltruistRouter to send messages:

// Send to a specific client
await _router.Client.SendAsync(clientId, packet);

// Send to all clients in a room
await _router.Room.SendAsync(roomId, packet);

// Broadcast to all connected clients
await _router.Broadcast.SendAsync(packet);

// Broadcast but exclude one client
await _router.Broadcast.SendAsync(packet, excludeClientId: senderId);

Connection & Room Management

Portals have access to connection and room management through the IConnectionManager / ISocketManager services:

MethodDescription
AddConnectionAsyncAdd a new client connection
RemoveConnectionAsyncRemove a client connection
GetConnectionAsyncGet a connection by ID
GetAllConnectionsAsyncGet all active connections
CreateRoomAsyncCreate a new room
JoinRoomAsyncAdd a client to a room
GetConnectionsInRoomAsyncGet all connections in a room
FindAvailableRoomAsyncFind a room with available capacity
DeleteRoomAsyncDelete a room

Securing Portals

Use the [Shield] attribute to require authentication:

[Portal("/ws")]
[Shield(typeof(JwtAuth))]
public class SecurePortal : Portal
{
    // Only authenticated clients can access this portal
}

See Authentication for details.