1

What the title says, I'm trying to make battleships with websockets, yesterday everything was going more or less smooth, aside from having multiple ws opening so after changing stuff around for a while and deleting <StrictMode> i solved that problem but realized that users couldnt send messages anymore.

On Frontend those are the 2 tsx files, im new to both React and websockets so perhaps im misusing the component type or something idk.

Lobby.tsx :

import './Lobby.css';
import React, { useEffect, useState, useRef } from 'react';
import ChatLog from '../components/ChatLog';
import BattleShipGame from '../components/BattleShipGame';

const Lobby: React.FC = () => {
  const [messages, setMessages] = useState<{ message: string, username: string }[]>([]);
  const [message, setMessage] = useState('');
  const [ws, setWs] = useState<WebSocket | null>(null);
  const isWsOpen = useRef(false);

  useEffect(() => {
    const roomName = new URLSearchParams(window.location.search).get('room');
    const token = localStorage.getItem('accessToken');

    if (roomName && token && !isWsOpen.current) {
      console.log('Attempting to open WebSocket connection');
      const socket = new WebSocket(`ws://192.168.1.125:8000/ws/lobby/${roomName}/${token}/`);

      socket.onmessage = (event) => {
        const data = JSON.parse(event.data);
        console.log('Received WebSocket message:', data); 
        if (data.type === 'chat_message') {
          console.log('Received chat message:', data); 
          setMessages((prev) => {
            const newMessages = [...prev, { message: data.message, username: data.username }];
            console.log('Updating messages state with:', newMessages);
            return newMessages;
          });
        }
      };

      socket.onopen = () => {
        setWs(socket);
        isWsOpen.current = true;
        console.log('WebSocket connection established');
      };

      socket.onerror = (error) => {
        console.error('WebSocket error:', error);
      };

      socket.onclose = (event) => {
        console.log('WebSocket connection closed:', event);
        setWs(null);
        isWsOpen.current = false;
      };

      return () => {
        console.log('Cleaning up WebSocket connection');
        if (socket.readyState === WebSocket.OPEN) {
          socket.close();
          console.log('WebSocket connection closed by cleanup');
        }
      };
    }
  }, []);

  const sendMessage = () => {
    if (ws && message) {
      console.log('Sending message:', message);
      const username = localStorage.getItem('username');
      ws.send(JSON.stringify({ 'type': 'chat_message', 'message': message, 'username': username }));
      setMessage(''); 
    }
  };

  return (
    <div className='lobby-container'>
      {ws && <BattleShipGame ws={ws} />}
      <ChatLog messages={messages} message={message} setMessage={setMessage} sendMessage={sendMessage} />
    </div>
  );
};

export default Lobby;

ChatLog.tsx:

import './ChatLog.css';
import React from 'react';

interface ChatLogProps {
  messages: { message: string, username: string }[];
  message: string;
  setMessage: React.Dispatch<React.SetStateAction<string>>;
  sendMessage: () => void; 
}

const ChatLog: React.FC<ChatLogProps> = ({ messages, message, setMessage, sendMessage }) => {
  
  return (
    <div id="RoomActivityContainer">
      <div id="RoomActivityTitle">
        <span>Room Activity</span>
      </div>
      <div id="RoomActivityMessages">
        {messages.map((msg, index) => (
          <div key={index} className="message">
            <div className="msg-content">{msg.message}</div>
            <div className="msg-username-wrapper">
              <div>- </div>
              <div className="msg-username">{msg.username}</div>
            </div>
          </div>
        ))}
      </div>
      <div id="RoomActivityInput">
        <input
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          placeholder="send message"
          onKeyUp={(e) => {
            if (e.key === 'Enter') {
              sendMessage();
            }
          }}
        />
      </div>
    </div>
  );
};

export default ChatLog;

on backend this is the relevant consumers.py code:

import json
import jwt
import random
from channels.generic.websocket import AsyncWebsocketConsumer
from django.conf import settings

class BattleshipConsumer(AsyncWebsocketConsumer):

    async def connect(self):
        self.room_group_name = self.scope['url_route']['kwargs']['room_id']
        token = self.scope['url_route']['kwargs']['token']

        try:
            # Decode and verify JWT token
            payload = jwt.decode(token, settings.SECRET_KEY, algorithms=['HS256'])
            self.username = payload['username']
            self.guest_id = payload['guest_id']
            print(self.username, self.guest_id)

            # Add player to the group (room)
            await self.channel_layer.group_add(
                self.room_group_name,
                self.channel_name
            )
            await self.accept()

            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': f'{self.username} has joined the room',
                    'username': 'Server'
                }
            )

        except jwt.ExpiredSignatureError:
            await self.close(code=4001)  # Custom error code for token expired
        except jwt.InvalidTokenError:
            await self.close(code=4002)  # Custom error code for invalid token

    async def disconnect(self):
        # Remove player from the group
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': f'{self.username} has left the room',
                'username': 'Server'
            }
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message_type = text_data_json['type']
        print(f"Received message type: {message_type}")

        if message_type == 'chat_message':
           
            print(f"Message content: {text_data_json['message']}")
            await self.channel_layer.group_send(
                self.room_group_name,
                {
                    'type': 'chat_message',
                    'message': text_data_json['message'],
                    'username': self.username                    
                }
            )
            print(f"Sent message to group: {text_data_json['message']}")

            
        elif message_type == 'randomize_ships':
            ships = self.randomize_ships()
            await self.send(text_data=json.dumps({
                'type': 'setup',
                'ships': ships
            }))

    async def chat_message(self, event):
        message = event['message']
        username = event['username']
        print(f"Sending message to WebSocket: {message} from {username}")
        await self.send(text_data=json.dumps({
            'type': 'chat_message',
            'message': message,
            'username': username
        }))

Tried logging everything and the data flow looks fine except when backend send msg back to client it gets ignored (Although FireFox's Network monitor detects ) FireFox network tab

I honestly have no clue on what to try, why user messages dont get shown while server messages do. here are screenshots of the browser console and Django's logs when entering a lobby and sending a message "test"

FireFox console logs Django's logs

1 Answer 1

0

The problem was that I overrode the socket.onmessage function with ws.onmessage inside BattleShipGame.tsx.

Not the answer you're looking for? Browse other questions tagged or ask your own question.