Reputation: 21
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