Django Channel - Distinguish between message sender and receiver

Hello!

I am working on a project where I need to create live communication between users. I am using Django Channel for that, but I have come across an issue where I cannot differentiate between message sender and receiver in JavaScript.

Here is a picture of what I want:

And here is the actual result from browser 1:

And here is the same result from browser 2:

In the first image, I have created two messages for a message room instance via the admin panel. It is very clear who sent the message and who received the message.

In the second image and third image, I have texted messages through the browser but the messages appear on the same side for the user that sent the messages and in the opposite side for the receiver.

How can I distinguish the sender and the receiver and place their messages on left or right side?

I am not looking for actual code, but an idea of how I can solve this issue. My “solution” was to check if the authenticated users ID match the sender/receiver ID of each Chatroom instances, and based on that separate the sender and receiver message using JS. But it clearly does not work.

JS:

// Thos document only handles the websocket connection.

class DjangoChannels {
    constructor(sendMessageSelector, chatInputSelector, chatMessageSubmitSelector, messagePreviewSelector, csrftoken) {
        this.sendMessage = document.getElementById(sendMessageSelector);
        this.chatInput = document.getElementById(chatInputSelector);
        this.chatMessageSubmit = document.getElementById(chatMessageSubmitSelector);
        this.messagePreview = document.querySelectorAll(messagePreviewSelector);

        this.csrftoken = csrftoken;

        this.setupEventListeners();
    }

    setupEventListeners() {
        if (this.messagePreview) {
            this.messagePreview.forEach((btn) => {
                btn.addEventListener("click", (e) => {
                    e.preventDefault();
                    const urlExtension = btn.dataset.urlextension;
                    const url = btn.dataset.url;
                    this.setups(urlExtension, url);
                })
            })
        }
    }

    // Opening up a websocket connection
    setups(urlExtension, url) {
        const websocketProtocol = window.location.protocol === "https:" ? "wss" : "ws";
        const wsEndpoint = `${websocketProtocol}://${window.location.host}/meddelanden/${urlExtension}/`;
        this.chatSocket = new WebSocket(wsEndpoint);

        // Websocket message event listener
        this.chatSocket.onmessage = async (e) => {
            e.preventDefault();
            const response = await fetch(url, {
                method: "GET",
                headers: {
                    "X-Requested-With": "XMLHttpRequest",
                },
            })

            if (response.ok) {
                console.log("Response is OK");
                const data = await response.json();
                const msg = JSON.parse(e.data).message
                const text_message = msg;
                this.displayMessages(data.auth_user_id, data.main_user_sender, data.secondary_user_receiver, text_message)
            }
           
        };

        // Websocket close event listener
        this.chatSocket.onclose = (e) => {
            console.log("Chat socket closed unexpectedly");
        };

        // Input keydown event listener
        this.chatInput.focus();
        this.chatInput.onkeydown = (e) => {
            if (e.key === "Enter") {
                e.preventDefault();
                this.chatMessageSubmit.click();
            }
        };

        // Submit button on click event listener
        this.chatMessageSubmit.onclick = (e) => {
            e.preventDefault();
            const message = this.chatInput.value.trim();
            if (message !== "") {
                this.chatSocket.send(JSON.stringify({
                    "message": message,
                    "url_extension": urlExtension,
                }));
            }
            this.chatInput.value = "";
        };
    }

    displayMessages(auth_user_id, main_user_sender, secondary_user_receiver, text_message) {
        // console.log(`The text message is: ${text_message}`)
        // console.log(`Authenticated user ID is: ${auth_user_id}`)
        // console.log(`Main user ID is: ${main_user_sender}`)
        // console.log(`Secondary user ID is: ${secondary_user_receiver}`)

        const messagesContainer = document.querySelector(".message_fields");
        
        const messageContent = text_message;
        // const timestamp = new Date(timestamp).toLocaleString(); // Format timestamp as needed
        let messageHTML = '';

        if (auth_user_id == main_user_sender) {
            messageHTML = `
                <div class="message-box message-sender">
                    <small>Du</small>
                    <div>
                        <div>${messageContent}</div>
                        <div class="message-timestamp"></div>
                    </div>
                </div>
            `;
        } else {
            messageHTML = `
                <div class="message-box message-receiver">
                    <small></small> <!-- Assuming sender_username is available -->
                    <div>
                        <div>${messageContent}</div>
                        <div class="message-timestamp"></div>
                    </div>
                </div>
            `;
        }

        const newMessageDiv = document.createElement('div');
        newMessageDiv.innerHTML = messageHTML;
        messagesContainer.appendChild(newMessageDiv);
    }

}
export default DjangoChannels

Consumer:

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_id = self.scope['url_route']['kwargs']['room_id'] # Find the room id from the connection scope

        await self.channel_layer.group_add(
            self.room_id,
            self.channel_name,
        )
        await self.accept()
        
        await self.send(text_data=json.dumps({
            "type": "connection_established",
            "succesful_connection_message": "You are now connected!",
        }))

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)        
        message = text_data_json["message"].strip()
        url_extension = text_data_json["url_extension"]

        # Register the message in the database asynchronously
        # await self.register_message(message, url_extension)

        await self.channel_layer.group_send(
            self.room_id,
            {
                'type': 'send_message',
                'message': message,
            }
        )
    
    async def disconnect(self, close_code):
        await self.channel_layer.group_discard(self.room_id, self.channel_name)

    # @database_sync_to_async
    # def register_message(self, message, url_extension):
    #     chat_room = Chatroom.objects.get(url_extension=url_extension)
    #     chat_message = ChatMessage.objects.create(room=chat_room)
        
    async def send_message(self, event):
        message = event['message']
        await self.send(text_data=json.dumps({
            'message': message
        }))

AJAX view:

def renderChatMessages(request, chatroom_id):
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        if request.method == "GET":
            try:
                chatroom = get_object_or_404(Chatroom, pk=chatroom_id)
                chatroom_messages = chatroom.chatroom_messages.all()
                auth_user_id = request.user.id
                print(auth_user_id)
                main_user_sender = chatroom.sender.pk
                secondary_user_receiver = chatroom.receiver.pk
                serialized_messages = serialize('json', chatroom_messages)
                return JsonResponse({
                    "chatroomMessages": serialized_messages,
                    "auth_user_id": auth_user_id,
                    "main_user_sender": main_user_sender,
                    "secondary_user_receiver": secondary_user_receiver,
                }, status=200)
            except json.JSONDecodeError:
                return JsonResponse({"error": "Invalid JSON request"}, status=500)
    else:
        return JsonResponse({"error": "Invalid request"}, status=400)

Model (Ignore the method for URL extension, I am changing this with Django-sesame soon):

class Chatroom(models.Model):
    room = models.ForeignKey(Room, on_delete=models.CASCADE, blank=True, null=True)
    chatroom = models.TextField(_("Meddelande"), max_length=100, blank=True)
    sender = models.ForeignKey(Member, on_delete=models.CASCADE, related_name="chatroom_creator", blank=False)
    receiver = models.ForeignKey(Member, on_delete=models.CASCADE, related_name="chatroom_receiver", blank=False)
    timestamp = models.DateTimeField(_("Skapad"), auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    url_extension = models.CharField(_("ULR förlänging"), max_length=settings.URL_EXTENSION_LENGTH, blank=False, unique=True)
    
    class Meta:
        ordering = ["-updated"]
    
    def __str__(self):
        return self.chatroom
    
    def get_profilepage(self):
        return reverse("profilepage_app:profile-redirect", args=[self.room.member.username_slug])
    
    def save(self, *args, **kwargs):
        if not self.url_extension:  # If URL extension is empty
            self.url_extension = get_random_string(length=settings.URL_EXTENSION_LENGTH)
        try:
            super().save(*args, **kwargs)
        except IntegrityError:  # If a non-unique URL extension is generated
            self.url_extension = get_random_string(length=settings.URL_EXTENSION_LENGTH)
            super().save(*args, **kwargs)

class ChatMessage(models.Model):
    room = models.ForeignKey(Chatroom, on_delete=models.CASCADE, related_name="chatroom_messages")    
    sender = models.ForeignKey(Member, on_delete=models.CASCADE, related_name="chatmessage_sender", null=False, blank=False, default="")
    receiver = models.ForeignKey(Member, on_delete=models.CASCADE, related_name="chatmessage_receiver", null=False, blank=False, default = "")
    message = models.TextField(blank=False, max_length=settings.MAX_CHATMESSAGE_LENGTH)  
    timestamp = models.DateTimeField(_("Skapad"), auto_now_add=True)

    def __str__(self):
        return str(f"{self.sender.username} - {self.message}")

    class Meta:
        ordering = ['timestamp']

How are you using Channels here? I see no websockets being created. I don’t see any consumers, I don’t see any usage of the Channel layer. I don’t see any JavaScript to send or receive messages.

It looks to me like what you have created is an AJAX-based system built around a short-polling protocol.

Hello Mr Ken!

My apologies, I shared the wrong JS and forgot the consumer. I have now updated my question.

The handshaking is working fine and I can interact with both the server and the client.

The only the web socket connection is disconnected is when I leave the page or refresh it. Otherwise I see no other issues.

Also, I am not saving the messages to the database in the consumer. I am aware of that, and I will fix that later.

I still don’t think I understand what you’re trying to do here.

If you’re sending the message out through the websocket, what’s the purpose of the GET within the socket receive handler?

Is there a specific reason why you’re looking to store these messages in the database?

Hello!

I have managed to solve the issue and create a one-to-one chat application. I will share my codes after some final touches.