Files
projects/Dockers/socket-broker
Bram baaae2b850
Build and Push Docker Images / build-and-push (push) Successful in 1m28s
Allow users to subscribe to community chat topics
2026-10-29 10:49:36 +00:00
..
2025-08-21 13:53:34 +02:00
2025-08-21 10:59:09 +02:00
2025-08-21 11:01:18 +02:00

Socket Broker

A Socket.IO broker with topic-based messaging for ComedyKit. Backend services publish over an HTTP API protected by an API key; browsers and mobile clients authenticate with short-lived, topic-scoped tokens issued by Laravel.

Features

  • Socket.IO server for real-time client subscriptions.
  • HTTP API for backend publishing.
  • API-key authentication for publishers.
  • HMAC-SHA256 client tokens for connection and topic authorization.
  • Room-based topic delivery (no in-memory subscription maps).
  • Protected topic introspection and public health check.
  • CORS allowlist, subscription limits, and subscribe rate limiting.

Architecture

Frontend / mobile clients
        │  Socket.IO (connection token + topic token)
        ▼
Socket broker  ── rooms: user/{uuid}/notifications
        ▲                 community/user/{uuid}
        │                 event/{slug}/ticket_statistics
        │  POST /publish (API key)
Laravel backend

Configuration

Copy env.example to .env:

Variable Default Description
WS_PORT 3000 Socket.IO / HTTP port
API_PORT WS_PORT Optional second HTTP API port
API_KEY default-api-key-change-me Publisher API key; never expose to clients
SOCKET_TOKEN_SECRET — Shared HMAC secret; must match Laravel SOCKET_TOKEN_SECRET
AUTH_MODE required off, optional (rollout only), or required
ALLOWED_ORIGINS https://*.comedykit.be,... Comma-separated origins; supports *.domain.tld
MAX_TOPICS_PER_SOCKET 20 Subscription cap per connection
SUBSCRIBE_RATE_LIMIT 30 Subscribe attempts per 10 seconds per connection

AUTH_MODE=required refuses to start without SOCKET_TOKEN_SECRET.

Rollout

  1. Deploy with AUTH_MODE=optional so legacy clients still work.
  2. Deploy Laravel with /notifications/socket-token and the new publisher job.
  3. Deploy the updated web/mobile clients.
  4. Switch to AUTH_MODE=required.

Client authentication

Laravel issues two token types:

// Connection token
{ "v": 1, "sub": "<user uuid>", "typ": "connect", "iat": 0, "exp": 0, "jti": "..." }

// Topic token
{ "v": 1, "sub": "<user uuid>", "typ": "topic", "topic": "event/slug/ticket_statistics", "iat": 0, "exp": 0, "jti": "..." }

Tokens are encoded as:

base64url(JSON payload) + "." + base64url(HMAC-SHA256(payload, SOCKET_TOKEN_SECRET))

Clients connect with the connection token:

const socket = io("https://bram-sockets.comedykit.be", {
  auth: { token: connectionToken },
});

A user may always subscribe to user/{their-own-id}/notifications and community/user/{their-own-id}. Any other topic requires a topic token whose sub matches the connection subject and whose topic matches the subscription exactly.

WebSocket API

Subscribe

socket.emit("subscribe", "user/<uuid>/notifications");

// Or, for a topic-scoped token:
socket.emit("subscribe", {
  topic: "event/my-event/ticket_statistics",
  token: topicToken,
});

socket.on("subscribed", data => {
  console.log(`Subscribed to ${data.topic}`);
});

Unsubscribe

socket.emit("unsubscribe", "user/<uuid>/notifications");
socket.on("unsubscribed", data => {
  console.log(`Unsubscribed from ${data.topic}`);
});

Receive messages

socket.on("message", data => {
  console.log(data.topic, data.message, data.data, data.timestamp);
});

Errors

socket.on("error", error => {
  // INVALID_TOPIC | FORBIDDEN_TOPIC | RATE_LIMITED | TOPIC_LIMIT
  console.error(error.code, error.message);
});

HTTP API

Public

  • GET /health — status, connection count, auth mode. Does not expose topic names.

Protected (API key)

  • GET /topics
  • GET /topics/:topic
  • POST /publish
  • POST /publish/:topic

API key is accepted as Authorization: Bearer <key>, x-api-key, or api_key query parameter.

Publish

curl -X POST http://localhost:3000/publish \
  -H "x-api-key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"topic":"user/<uuid>/notifications","message":"{\"id\":\"...\"}"}'

Response:

  • 200 with { success, message, subscribers, payload }
  • 204 when the topic has no subscribers
  • 400 invalid request
  • 401 invalid or missing API key

Development

npm install
npm test
AUTH_MODE=optional API_KEY=dev-key SOCKET_TOKEN_SECRET=dev-secret npm start

Docker

cp env.example .env
# set API_KEY, SOCKET_TOKEN_SECRET, AUTH_MODE, ALLOWED_ORIGINS
docker compose up -d

The Docker image runs as a non-root user and exposes port 3000. If API_PORT differs from WS_PORT, a second HTTP server is started on that port.

Security notes

  • API_KEY is publisher-only. Never ship it to clients.
  • SOCKET_TOKEN_SECRET must be at least 32 random bytes and identical in Laravel and the broker.
  • Tokens are short-lived. The broker verifies them when a subscription is created; clients re-fetch tokens on reconnect.
  • Use HTTPS/WSS in production.
  • Rotate secrets by coordinating Laravel and broker deployments; outstanding tokens become invalid.
  • AUTH_MODE=optional or off is for local development or the rollout window only.