Channel: WebSocket Bridge¶
The Channel service is a high-performance Rust component that bridges the gap between the frontend (WebSocket) and backend (Redis). It implements a subset of the Phoenix Channels protocol, allowing the frontend to subscribe to data streams without needing a direct Redis connection.
Overview¶
Channel acts as a translation layer:
- Downstream: Subscribes to Redis
to:<topic>:*patterns and pushes data to browser WebSockets. - Upstream: Re-publishes browser commands to Redis
from:<topic>:<event>. - Supports multiple named channels that clients can join and leave
- Secures channel access with JSON Web Tokens
- Tracks and broadcasts client presence information
Communication Flow¶
Backend to Frontend¶
- Backend plugins publish messages to Redis topics in the format
to:<topic>:<event>. - Channel subscribes to Redis using
PSUBSCRIBE to:<topic>:*. - When a message arrives, Channel forwards it to all WebSocket clients joined to that topic.
sequenceDiagram
participant P as Backend Plugin
participant R as Redis
participant C as Channel Service
participant F as Frontend Client
P->>R: PUBLISH to:system:update, "data"
R-->>C: Receives message
C->>F: PUSH system:update, "data"
Frontend to Backend¶
- WebSocket clients send messages to a specific channel with an event name
- Channel receives these messages and publishes them to Redis topics in the format
from:<channel>:<event> - Backend plugins subscribe to these Redis topics to receive client messages
- Plugins process the messages and can respond by publishing back to
to:<channel>:<event>
sequenceDiagram
participant F as Frontend Client
participant C as Channel Service
participant R as Redis
participant P as Backend Plugin
F->>C: PUSH system:command, "payload"
C->>R: PUBLISH from:system:command, "payload"
R-->>P: Receives message
Channel Protocol¶
The WebSocket protocol follows the Phoenix Channels message format, which uses JSON arrays with the following structure:
[join_ref, ref, topic, event, payload]
Where:
join_ref: Reference to the channel join request (null for system messages)ref: Message reference for tracking responsestopic: Channel nameevent: Event namepayload: Message data
Events¶
phx_join: Join a channel (requires JWT token)phx_leave: Leave a channelphx_reply: Acknowledgment of a messagepresence_state: Current state of all clients in a channelpresence_diff: Changes in channel presence- Custom events: Any custom event name can be used for application-specific messages
Usage in Tangram¶
Predefined Channels¶
phoenix: System channel for heartbeats and connection managementadmin: Administrative channel for monitoring and controlsystem: General system information (includes regular datetime broadcasts)- Custom channels: Application-specific channels can be created dynamically
Client Connection¶
Clients connect to the WebSocket endpoint and can join multiple channels:
// Connect to the WebSocket
const socket = new Phoenix.Socket("/websocket", {
params: { userToken: token },
});
socket.connect();
// Join a channel
const channel = socket.channel("system", { token: systemToken });
channel
.join()
.receive("ok", (response) => console.log("Joined successfully", response))
.receive("error", (response) => console.log("Join failed", response));
// Listen for events
channel.on("datetime", (payload) => {
console.log("Current time:", payload.response.datetime);
});
// Send events
channel.push("custom_event", { message: "Hello from client" });
Backend Integration¶
Backend plugins can communicate with the frontend by using Redis pub/sub:
import redis
import json
r = redis.Redis()
# Send message to frontend clients
await r.publish('to:system:update', json.dumps({
'type': 'message',
'message': 'Update from backend'
}))
# Listen for messages from frontend
p = r.pubsub()
await p.psubscribe('from:system:*')
async for message in p.listen():
if message['type'] == 'pmessage':
print(f"Received: {message['data']}")
Running the Channel Service¶
The Channel service is an integrated part of the core tangram application. It is automatically started as a background service when you run the tangram serve command. You do not need to run it separately.
Its behavior is configured in the [channel] section of your tangram.toml file.
[channel]
host = "127.0.0.1"
port = 2347
# The public-facing URL for the channel service.
# Required when running behind a reverse proxy.
# public_url = "http://localhost:2347"
jwt_secret = "a-better-secret-than-this"
Token Authentication¶
Channels require JWT tokens for authentication. Tokens can be requested from the /token endpoint, which is exposed on the port defined in your configuration.
curl -X POST http://localhost:2347/token \
-H "Content-Type: application/json" \
-d '{"channel": "system", "id": "client1"}'
The response includes a JWT token that can be used for channel authentication:
{
"id": "client1",
"channel": "system",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Redis Commands for Debugging¶
# Subscribe to all outgoing messages from clients
redis-cli psubscribe "from:*"
# Subscribe to all incoming messages to clients
redis-cli psubscribe "to:*"
# Publish a test message to clients
redis-cli publish "to:system:test" '{"type":"message","message":"Test from Redis"}'