Websockets
Websockets are used in Precious Leads to notify the front end of new events.
Redis required
In order to use websockets, Redis must be running.
Ping Redis to verify it’s running:
redis-cli ping
Should respond with PONG.
Django Channels
Websockets are implemented using Django Channels.
With Django Channels, clients can connect to a “group”, for example Category_1.
Each connection to a group is a channel. One user can have several channels in the same group. For example, several tabs opened or several browsers opened.
Every time a message is sent to a group, it will be broadcasted to all channels of that group.
Category endpoint
In order to receive updates regarding a category, a user must connect to the following endpoint:
ws://localhost:8000/ws/category/
Required query_params:
category_id: The id of the category you want to be notified of.
token: The access token of the user.
As of January 21st, the category endpoint is the only endpoint we have.
Keep in mind:
In production we should be using wss:// instead of ws://. I have not tested this yet, but Railway should take care of it.
Permissions and authentication
The websocket endpoints require users to be authenticated and have the right permissions.
If the token is invalid or the user does not have permission to subscribe to a particular group (category in this case), the websocket connection will be refused.
Lead Users
Lead users do not currently have permission to subscribe to category groups.
This means that lead users cannot receive updates regarding new leads or new imports.
A possible solution is to implement a CategoryUser endpoint (for example, category 5 and user 7). This would separate messages by user and would allow us to notify lead users of new leads only if they belong to them.
Types of messages received
When subscribed to a category websocket, there are 2 types of messages the client can receive:
TYPE: lead_import_update
First, you will get a message when a new import has started:
{
"message": {
"type": "lead_import_update",
"status": "STARTED"
"result": "In Progress"
}
}
Then, you will get a message when the import has finished:
{
"message": {
"type": "lead_import_update",
"status": "SUCCESS"
"result": {
"id": 28,
"category_id": 1,
"submitted_by_user_id": 2,
"celery_task_id": "7e908a71-33e1-430a-b9ed-cd5f00f4abcf",
"state": "SUCCESS",
"progress": "Import finished.",
"total_leads": 6837,
"imported_leads": 6833,
"leads_with_errors": 4,
"errors": [],
"user_checked": false,
"date_added": "2025-01-21T22:36:50.263634Z",
"last_updated": "2025-01-21T22:36:58.173902Z"
}
}
TYPE: lead_added
The message will be as follows:
{
"message": {
"type": "lead_added",
"lead: {
"id": 185641,
"category_id": 1,
"status_id": 20,
"assigned_user_id": "4",
"import_task_id": null,
"source": "",
"full_name": "Janick Gers",
"first_name": "Janick",
"last_name": "Gers",
"email": "test@gmail.com",
"phone": "740994451",
"street_address": "Street 54nd",
"city": "Los Angeles",
"state": "California",
"country": "US",
"zip_code": "50021",
"gender": "Male",
"birthdate": "1985-03-11",
"order_index": 1,
"date_added": "2025-01-21T22:29:55.747729Z",
"last_updated": "2025-01-21T22:29:55.758863Z",
"custom_fields": null
}
}
Closing channels when user permissions change
This is not implemented yet.
Quick testing with HTML file
You can quickly test websockets functionality using the following html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSocket Test</title>
</head>
<body>
<h1>WebSocket Test</h1>
<script>
const socket = new WebSocket("ws://localhost:8000/ws/category/?category_id=1&token=jv0QIZk1u8UHfjNmPqnpEEuN48p1u7");
socket.onopen = () => {
console.log("WebSocket connected.");
socket.send(JSON.stringify({ test: "Hello from client" }));
};
socket.onmessage = (event) => {
console.log("Message from server:", event.data);
};
socket.onerror = (error) => {
console.error("WebSocket error:", error);
};
socket.onclose = () => {
console.log("WebSocket closed.");
};
</script>
</body>
</html>