I am using Celery and Channels to notify a user when a certain action is performed on the server by another user. On the browser, I am using WebSockets to connect the user to the consumer. Unfortunately, if the user is not online (which means the WebSocket is closed) when an event occurs, they will miss the notification, which I store in the database. How can I check if the user is still connected and send the information, or if not, wait for the connection to be established and then send the notification?
class MyConsumer(AsyncWebsocketConsumer):
async def connect(self):
self.user_inbox = f'inbox_{self.scope["user"].username}'
# Check if user is anonymous and close the connection when true
if self.scope["user"].is_anonymous:
await self.close()
self.connected_user = self.scope["user"]
self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
self.room_group_name = "chat_%s" % self.room_name
# Join room group
await self.channel_layer.group_add(self.room_group_name, self.channel_name)
await self.accept()
async def disconnect(self, close_code):
# Leave room group
await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
async def chat_message(self, event):
"""FIRES WHEN MESSAGE IS SENT TO LAYERS"""
event_dict = {
"message_header": event["message_header"],
"message_body": event["message_body"],
"sender": event["sender"],
"subject": event["subject"],
}
# Send message to WebSocket
await self.send(text_data=json.dumps(event_dict))
@shared_task
def send_notification_to_ws(ws_channel, ws_event):
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(ws_channel, ws_event)
ws_event = {
"type": "chat_message",
"message_header": message_header,
"message_body": message_body,
"sender": sender.username,
"subject": subject,
}
ws_channel = "chat_%s" % recipient.username
send_notification_to_ws.delay(ws_channel=ws_channel, ws_event=ws_event)
I have been thinking about whether there is a way to check if a user is present in a group channel layer, given that I am creating a group channel for a specific user. This means that the group will only consist of a single user.And maybe use the information above to run a loop until the user is available and the send the information..am not sure if this is is even possible or if it is the elegant way of tackling this problem.just thoughts????? any help please.
Note: Everything works fine when the user is online.
There is another way, but it will take some restructuring of your code and logic.
I like to think of the websocket code as reactive to the command that is sent to it from the front end client. In this case we need another application (a AsyncWebsocketConsumer
class in consumers.py) that helps to manage this application. Consider the following application.
An application that receives a command when the user first connects is able to save that user's ID to a connected list(or generate a new one). That userID tells us how to connect to the user in the websocket that we are going to use. If that user is able to connect, if the room is available, then we send back a message telling it to run the javascript function that connects to the websocket with their userID if needed. Every 15 seconds or x amount of time, we ping that userID on the first websocket and wait for a response that is programmed into the front end. If the user responds, we do nothing, if they do not respond, we remove them from our connected user's list.
Then, on our original websocket, we can simply have an application that searches this list of connected users and sends to them the string of information that you want them to read from the database. You may need to add to your database entries here and make sure that you keep track of a BooleanField
which follows whether or not that string has been sent. You can make it default to True, then set it to False in the websocket function when its sent (and only send if its True).
Something to note here, you said you store that information in your database, if you want to access that inside a asynchronous function like your websocket consumer, make sure to user the decorator @database_sync_to_async
after importing it at the top of consumers.py
from channels.db import database_sync_to_async