Oven Kravecki
Oven Kravecki

Reputation: 21

Cant connect WS on React

The HTTPS working and accessable. Reverse proxy enabled for communicating with backend. Its running on local development but on server its not connected:

WebSocket connection to 'wss://example.com/api' failed: WebSocket is closed before the connection is established.
chatService.js:31 WebSocket error: Event {isTrusted: true, type: 'error', target: WebSocket, currentTarget: WebSocket, eventPhase: 2, …}

Its node backend on 5000 and frontend on 3000. Frontend env:

REACT_APP_API_URL=http://example.com/api
REACT_APP_WEBSOCKET_URL=wss://example.com/api

frontend/chatService.js

// chatService.js
import useChatStore from '../store/chatStore';

class ChatService {
    constructor() {
        this.ws = null;
        this.reconnectAttempts = 0;
        this.maxReconnectAttempts = 5;
    }

    connectWebSocket(user) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) return;

        this.ws = new WebSocket(`${process.env.REACT_APP_WEBSOCKET_URL}`);

        this.ws.onopen = () => {
            console.log('WebSocket connected');
            this.reconnectAttempts = 0;
            useChatStore.getState().setIsConnected(true);
            this.sendJoinMessage(user);
        };

        this.ws.onmessage = this.handleMessage;

        this.ws.onclose = this.handleClose(user);

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

    sendJoinMessage(user) {
        this.sendMessage({
            type: 'join',
            username: user.username,
            userId: user.id
        });
    }

    handleMessage = (event) => {
        const data = JSON.parse(event.data);
        if (data.type === 'history') {
            useChatStore.getState().setMessages(data.messages);
        } else if (data.type === 'error') {
            console.error('WebSocket error:', data.message);
        } else if (data.type === 'message') {
            useChatStore.getState().addMessage(data);
        }
    }

    handleClose = (user) => () => {
        console.log('WebSocket disconnected');
        useChatStore.getState().setIsConnected(false);
        if (this.reconnectAttempts < this.maxReconnectAttempts) {
            this.reconnectAttempts++;
            setTimeout(() => this.connectWebSocket(user), 3000);
        }
    }

    sendMessage(messageData) {
        if (this.ws && this.ws.readyState === WebSocket.OPEN) {
            this.ws.send(JSON.stringify(messageData));
        }
    }

    disconnect() {
        if (this.ws) {
            this.ws.close();
        }
    }
}

export default new ChatService();

frontend/chatStore.js

// chatStore.js
import { create } from 'zustand';

const useChatStore = create((set) => ({
    messages: [],
    isConnected: false,
    setMessages: (messages) => set({ messages }),
    addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })),
    setIsConnected: (isConnected) => set({ isConnected }),
}));

export default useChatStore;

frontend/Chat.js

// Chat.js
import React, { useState, useEffect, useRef } from 'react';
import useAuthStore from '../../store/authStore';
import useChatStore from '../../store/chatStore';
import chatService from '../../services/chatService';

const Chat = () => {
    const [inputMessage, setInputMessage] = useState('');
    const { user, isAuthenticated } = useAuthStore();
    const { messages, isConnected } = useChatStore();
    const messagesEndRef = useRef(null);

    useEffect(() => {
        if (!isAuthenticated || !user) return;

        chatService.connectWebSocket(user);

        return () => {
            chatService.disconnect();
        };
    }, [isAuthenticated, user]);

    useEffect(() => {
        // Scroll to the bottom whenever messages are updated
        messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
    }, [messages]);

    const handleSendMessage = (e) => {
        e.preventDefault();
        if (inputMessage.trim() && user) {
            chatService.sendMessage({
                type: 'message',
                username: user.username,
                userId: user.id,
                message: inputMessage.trim(),
            });
            setInputMessage('');
        }
    };

backend/webSocket.js

// webSocket.js
const WebSocket = require('ws');
const db = require('../db'); // Make sure your database module is set up

function setupWebSocket(server) {
    const wss = new WebSocket.Server({ server, path: '/api' });
    const users = new Map();

    async function getRecentMessages() {
        try {
            const { rows } = await db.query(`
                SELECT * FROM chat_messages 
                ORDER BY timestamp DESC 
                LIMIT 30
            `);
            return rows.reverse();
        } catch (error) {
            console.error('Error fetching messages:', error);
            return [];
        }
    }

    async function saveMessage(messageData) {
        try {
            const { username, message, type, user_id } = messageData;
            const { rows } = await db.query(
                `INSERT INTO chat_messages (username, message, type, user_id) 
                 VALUES ($1, $2, $3, $4) 
                 RETURNING *`,
                [username, message, type, user_id]
            );
            return rows[0];
        } catch (error) {
            console.error('Error saving message:', error);
            return null;
        }
    }

    function validateMessage(data) {
        if (!data || typeof data !== 'object') return false;
        if (!['join', 'message'].includes(data.type)) return false;
        if (data.type === 'join' && (!data.username || !data.userId)) return false;
        if (data.type === 'message' && (!data.message || typeof data.message !== 'string')) return false;
        return true;
    }

    wss.on('connection', async (ws) => {
        console.log('New WebSocket connection');
        const recentMessages = await getRecentMessages();
        ws.send(JSON.stringify({ type: 'history', messages: recentMessages }));

        ws.on('message', async (message) => {
            try {
                const data = JSON.parse(message);
                if (!validateMessage(data)) {
                    ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
                    return;
                }

                switch (data.type) {
                    case 'join':
                        users.set(ws, { username: data.username, userId: data.userId });
                        break;

                    case 'message':
                        const user = users.get(ws);
                        if (user && user.username) {
                            const chatMessage = await saveMessage({
                                type: 'message',
                                message: data.message,
                                username: user.username,
                                user_id: user.userId
                            });
                            if (chatMessage) {
                                wss.clients.forEach(client => {
                                    if (client.readyState === WebSocket.OPEN) {
                                        client.send(JSON.stringify({
                                            type: 'message',
                                            username: chatMessage.username,
                                            message: chatMessage.message,
                                            timestamp: chatMessage.timestamp
                                        }));
                                    }
                                });
                            }
                        } else {
                            ws.send(JSON.stringify({ type: 'error', message: 'User not authenticated' }));
                        }
                        break;
                }
            } catch (error) {
                console.error('Message processing error:', error);
                ws.send(JSON.stringify({ type: 'error', message: 'Server error occurred' }));
            }
        });

        ws.on('close', () => {
            const user = users.get(ws);
            if (user) {
                users.delete(ws);
            }
        });
    });
}

module.exports = { setupWebSocket };

backend/server.js

const https = require('https');
const { setupWebSocket } = require('./Chat/webSocket');
const WebSocket = require('ws');

sslOptions ={
     pathFor certs.
}

const server = https.createServer(sslOptions, app);

// Start the server
const PORT = process.env.SERVER_PORT;
const HOST = process.env.HOST;

// Start HTTPS server with the loaded SSL certificates
server.listen(PORT, HOST, () => {
    console.log(`Server running on https://${HOST}:${PORT}`);
    console.log('WebSocket server initialized');
});

setupWebSocket(server);

tried without { bracelets } too.

Backend on express; esmodule and commonjs both. Frontend create-react-app React. Working on local but problematic on server.

.htaccess

RewriteEngine On

# 1. Redirect HTTP to HTTPS
RewriteCond %{HTTPS} !=on
RewriteRule ^/?(.*) https://%{SERVER_NAME}/$1 [R,L]

# 2. Proxy API requests to the backend on port 5000
# This handles all requests starting with /api and proxies them to the backend server
RewriteCond %{REQUEST_URI} ^/api
RewriteRule ^(.*)$ https://backend/$1 [P,L,E=PROXY-HOST:example.com]
#RewriteRule ^(.*)$ http://127.0.0.1:5000/$1 [P,L]  << commented

# If it's not an /api request, serve the frontend's index.html file for all other requests
RewriteCond %{REQUEST_URI} !^/api << might be the problem ?
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ /index.html [L

And is it okay to use same API for site and ws itself ?

Upvotes: 0

Views: 24

Answers (0)

Related Questions