Skip to content

Real-Time Collaboration

TruePPM uses Django Channels 4 to push project changes to connected clients over WebSocket.

ws://localhost:8000/ws/projects/{project_id}/?token=<jwt>

Authentication via ?token= JWT. Requires Member role or above (ordinal ≥ 1). Viewers are rejected with close code 4003.

{
"type": "board.event",
"event": "<event_name>",
"payload": { ... }
}
EventPayload
schedule_updated{"project_id": "..."}
EventPayload
task_created{"task_id": "..."}
task_updated{"task_id": "..."}
task_deleted{"task_id": "..."}
EventPayload
dependency_created{"dependency_id": "..."}
dependency_deleted{"dependency_id": "..."}
EventPayload
member_added{"membership_id": "...", "user_id": "...", "role": 1}
member_role_changed{"membership_id": "...", "user_id": "...", "role": 2}
member_removed{"membership_id": "...", "user_id": "..."}

All broadcasts are deferred inside transaction.on_commit() — events only fire if the database write committed successfully. No phantom events for rolled-back transactions.

Uses Valkey (the BSD-licensed Linux Foundation fork of Redis; wire-compatible) configured via REDIS_URL. All api and celery containers share the same Valkey instance, so Celery-originated broadcasts (e.g. schedule_updated) reach WebSocket clients connected to any API container — safe for horizontal scaling. Existing Redis-compatible managed services (ElastiCache, Memorystore, Azure Cache for Redis) work as drop-in alternatives.

const ws = new WebSocket(
`ws://localhost:8000/ws/projects/${projectId}/?token=${jwt}`
);
ws.onmessage = (event) => {
const { event: name, payload } = JSON.parse(event.data);
if (name === 'schedule_updated') fetchSchedule(payload.project_id);
if (name === 'task_updated') fetchTask(payload.task_id);
};