Users may not be connecting to the same websocket - django channels

25 Views Asked by At

I’m developing a chat feature using django channels. when a message is sent, It gets created on the server side and I think it’s being broadcasted. in my consumer I'm adding both the sender and receiver to the same group. on the client side I’m using javascript to get the sender and receiver IDs and connect them to the websocket. but when user A sends a message to user B. even though the message object is created and sent, it doesn’t appear for User B. I've verified that both users are connected to the websocket and there are no errors. but i think the issue might be that they are not connecting to the same websocket but I’m not sure why this is happening. also I'm new to django channels and javascript.

Javascript

let senderId = null;
let receiverId = null;

document.addEventListener('DOMContentLoaded', function () {
  const chatLog = document.querySelector('#chat-conversation-content');
  const messageInput = document.querySelector('#chat-message-input');
  const chatMessageSubmit = document.querySelector('#chat-message-submit');
  const activeChat = document.querySelector('.fade.tab-pane.show');

  let chatSocket = null;

  function connectToChat(sender_id, receiver_id) {
      const wsURL = 'ws://' + window.location.host + '/ws/messenger/' + sender_id + '/' + receiver_id + '/';
      try {
          chatSocket = new WebSocket(wsURL);

          chatSocket.onopen = function (e) {
              console.log('WebSocket connection established.');
          };

          chatSocket.onmessage = function (e) {
              try {
                  const data = JSON.parse(e.data);

                  const messageElement = document.createElement('div');
                  if (data.sender_id === senderId) {
                      // Render message for sender
                      messageElement.innerHTML = `
                          <div class="d-flex justify-content-end text-end mb-1">
                              <div class="w-100">
                                  <div class="d-flex flex-column align-items-end">
                                      <div class="bg-primary text-white p-2 px-3 rounded-2">${data.message}</div>
                                      <div class="small my-2">${data.timestamp}</div>
                                  </div>
                              </div>
                          </div>
                      `;
                  } else {
                      messageElement.innerHTML = `
                          <div class="d-flex mb-1">
                              <div class="flex-shrink-0 avatar avatar-xs me-2">
                                  <img class="avatar-img rounded-circle" src="${data.message}" alt="">
                              </div>
                              <div class="flex-grow-1">
                                  <div class="w-100">
                                      <div class="d-flex flex-column align-items-start">
                                          <div class="bg-light text-secondary p-2 px-3 rounded-2">${data.message}</div>
                                          <div class="small my-2">${data.timestamp}</div>
                                      </div>
                                  </div>
                              </div>
                          </div>
                      `;
                  }

                  chatLog.appendChild(messageElement);
              } catch (error) {
                  console.error('Error processing received message:', error);
              }
          };

          chatSocket.onerror = function (error) {
              console.error('WebSocket error:', error);
          };

          chatSocket.onclose = function (e) {
              console.log('WebSocket connection closed.');
          };
      } catch (error) {
          console.error('WebSocket connection error:', error);
      }
  }

  document.querySelectorAll('.friend-name').forEach(function (nameElement) {
    
      nameElement.addEventListener('click', function (e) {
          e.preventDefault();
          senderId = this.getAttribute('data-sender-id');
          receiverId = this.getAttribute('data-receiver-id');
          if (chatSocket !== null && chatSocket.readyState === WebSocket.OPEN) {
              chatSocket.close();
          }
          connectToChat(senderId, receiverId);
      });
  });
  
  function sendMessage() {
    const message = messageInput.value.trim();
    if (message !== '') {
        chatSocket.send(JSON.stringify({
            'message': message,
            'sender_id': senderId,
            'receiver_id': receiverId,
        }));
        messageInput.value = '';
    }
}

  chatMessageSubmit.addEventListener('click', sendMessage);
});

Html

{% for friend in user_friends.friends.all %}
<li data-bs-dismiss="offcanvas">
<a href="#chat-{{ forloop.counter }}" class="nav-link text-start friend-name" id="chat-{{ forloop.counter }}-tab" data-sender-id="{{ user.id }}" data-receiver-id="{{ friend.id }}" data-bs-toggle="pill" role="tab">
  <div class="d-flex">
    <div class="flex-shrink-0 avatar avatar-story me-2 status-online">
      <img class="avatar-img rounded-circle" src="{{ friend.profile.profile_picture.url }}">
    </div>
    <div class="flex-grow-1 d-block">
      <h6 class="mb-0 mt-1">{{ friend.get_full_name|title }}</h6>
      <div class="small text-secondary"></div>
    </div>
  </div>
</a>
</li>
{% endfor %}



<div class="col-lg-8 col-xxl-9">
  <div class="card card-chat rounded-start-lg-0 border-start-lg-0">
    <div class="card-body h-100">
      <div class="tab-content py-0 mb-0 h-100" id="chatTabsContent">
        <!-- Conversation item START -->
        {% for friend in user_friends.friends.all %}
        <div class="fade tab-pane show h-100" id="chat-{{ forloop.counter }}" role="tabpanel" aria-labelledby="chat-{{ forloop.counter }}-tab">
          <!-- Top avatar and status START -->
          <div class="d-sm-flex justify-content-between align-items-center">
            <div class="d-flex mb-2 mb-sm-0">
              <div class="flex-shrink-0 avatar me-2">
                <img class="avatar-img rounded-circle" src="{{ friend.profile.profile_picture.url }}" alt="">
              </div>
              <div class="d-block flex-grow-1">
                <h6 class="mb-0 mt-1">{{ friend.get_full_name|title }}</h6>
                <div class="small text-secondary"><i class="fa-solid fa-circle text-success me-1"></i>Online</div>
              </div>
            </div>
            <div class="d-flex align-items-center">
              <div class="dropdown">
                <a class="icon-md rounded-circle btn btn-primary-soft me-2 px-2" href="#" id="chatcoversationDropdown" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false"><i class="bi bi-three-dots-vertical"></i></a>               
                <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="chatcoversationDropdown">
                  <li><a class="dropdown-item" href="#"><i class="bi bi-check-lg me-2 fw-icon"></i>Mark as read</a></li>
                  <li><a class="dropdown-item" href="{% url "core:view-profile" username=friend.username %}"><i class="bi bi-person-check me-2 fw-icon"></i>View profile</a></li>
                  <li><a class="dropdown-item" href="#"><i class="bi bi-trash me-2 fw-icon"></i>Delete chat</a></li>
                  <li class="dropdown-divider"></li>
                </ul>
              </div>
            </div>
          </div>
          <!-- Top avatar and status END -->
          <hr>
          <!-- Chat conversation START -->
          <div class="chat-conversation-content overflow-auto custom-scrollbar" id="chat-conversation-content">
            <!-- Chat time -->
            <div class="text-center small my-2">Jul 16, 2022, 06:15 am</div>
            <!-- Chat message left -->
            {% for message in messages %}
              {% if message.sender == request.user %}
                <div class="d-flex justify-content-end text-end mb-1">
                  <div class="w-100">
                    <div class="d-flex flex-column align-items-end">
                      <div class="bg-primary text-white p-2 px-3 rounded-2">{{ message.content }}</div>
                      <div class="small my-2">{{ message.timestamp|date:"g:i A" }}</div>

                    </div>
                  </div>
                </div>
              {% else %}
                <div class="d-flex mb-1">
                  <div class="flex-shrink-0 avatar avatar-xs me-2">
                    <img class="avatar-img rounded-circle" src="{{ message.sender.profile.profile_picture.url }}" alt="">
                  </div>
                  <div class="flex-grow-1">
                    <div class="w-100">
                      <div class="d-flex flex-column align-items-start">
                        <div class="bg-light text-secondary p-2 px-3 rounded-2">{{ message.content }}</div>
                        <div class="small my-2">{{ message.timestamp|date:"g:i A" }}</div>
                      </div>
                    </div>
                  </div>
                </div>
              {% endif %}
            {% endfor %}
          </div>
          <!-- Chat conversation END -->
        </div>
        {% endfor %}
        <!-- Conversation item END -->
      </div>
    </div>
    <div class="card-footer">
      <div class="d-sm-flex align-items-end">
        <textarea id="chat-message-input" class="form-control mb-sm-0 mb-3" data-autoresize placeholder="Type a message" rows="1"></textarea>
        <button class="btn btn-sm btn-secondary-soft ms-2"><i class="fa-solid fa-paperclip fs-6"></i></button>
        <button id="chat-message-submit" type="submit" class="btn btn-sm btn-primary ms-2"><i class="fa-solid fa-paper-plane fs-6"></i></button>
      </div>
    </div>
  </div>
</div>

Consumers

class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
    self.sender_id = self.scope['url_route']['kwargs']['sender_id']
    self.receiver_id = self.scope['url_route']['kwargs']['receiver_id']
    self.room_group_name = f'chat_{self.sender_id}_{self.receiver_id}'

    await self.channel_layer.group_add(
        self.room_group_name,
        self.channel_name
    )

    await self.accept()

async def receive(self, text_data):
    text_data_json = json.loads(text_data)
    message = text_data_json['message']
    sender_id = text_data_json['sender_id']
    receiver_id = text_data_json['receiver_id']
    
    sender_profile_picture = await sync_to_async(self.get_profile_picture)(sender_id)
    receiver_profile_picture = await sync_to_async(self.get_profile_picture)(receiver_id)

    message_obj = await sync_to_async(Message.objects.create)(
        sender_id=sender_id,
        receiver_id=receiver_id,
        content=message
    )

    await self.channel_layer.group_send(
        self.room_group_name,
        {
            'type': 'chat_message',
            'message': message,
            'sender_id': sender_id,
            'receiver_id': receiver_id,
            'sender_profile_picture': sender_profile_picture,
            'receiver_profile_picture': receiver_profile_picture,
            'timestamp': message_obj.timestamp.strftime('%I:%M %p')
        }
    )

async def disconnect(self, close_code):
    await self.channel_layer.group_discard(
        self.room_group_name,
        self.channel_name
    )

async def chat_message(self, event):
    message = event['message']
    sender_id = event['sender_id']
    receiver_id = event['receiver_id']
    sender_profile_picture = event['sender_profile_picture']
    receiver_profile_picture = event['receiver_profile_picture']
    timestamp = event['timestamp']

    await self.send(text_data=json.dumps({
        'message': message,
        'sender_id': sender_id,
        'receiver_id': receiver_id,
        'sender_profile_picture': sender_profile_picture,
        'receiver_profile_picture': receiver_profile_picture,
        'timestamp': timestamp
    }))

def get_profile_picture(self, user_id):
    return Profile.objects.get(user__id=user_id).profile_picture.url

Url routing

websocket_urlpatterns = [
    path('ws/messenger/<int:sender_id>/<int:receiver_id>/', ChatConsumer.as_asgi())
]
0

There are 0 best solutions below