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.
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:
- A packet — any struct/class implementing
IPacketBase - 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:
| Method | Description |
|---|---|
AddConnectionAsync | Add a new client connection |
RemoveConnectionAsync | Remove a client connection |
GetConnectionAsync | Get a connection by ID |
GetAllConnectionsAsync | Get all active connections |
CreateRoomAsync | Create a new room |
JoinRoomAsync | Add a client to a room |
GetConnectionsInRoomAsync | Get all connections in a room |
FindAvailableRoomAsync | Find a room with available capacity |
DeleteRoomAsync | Delete 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.