I'm experimenting a bit with socket.io to create a chat app in real time.
Everything seems to work fine but after a few second socket.io gives me this error:
"websocket.js:50 WebSocket connection to 'ws://localhost:5000/socket.io/?EIO=4&transport=websocket&sid=gbx90aAo73oDoP86AAVP' failed: Insufficient resources"
What am I doing wrong?
Client
import React from "react";
import { useState, useEffect } from "react";
import { io } from "socket.io-client";
const App = () => {
const [conv, setConv] = useState([]);
const [input, setInput] = useState({
sender: "user",
text: "",
});
const socket = io("http://localhost:5000");
const handleChange = (e) => {
setInput({
sender: "user",
text: e.target.value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
socket.emit("input", input);
};
useEffect(() => {
socket.on("output", (data) => {
setConv(data[0].messages);
});
}, [conv]);
return (
<div className="App">
<form onSubmit={handleSubmit}>
<h1>chat</h1>
<div id="messages">
{conv.map((msg, index) => (
<div key={index}>
<p>
{msg.sender}: {msg.text}
</p>
</div>
))}
</div>
<input onChange={handleChange} type="text" />
<input type="submit" />
</form>
</div>
);
};
export default App;
Server
const express = require("express");
const socketIo = require("socket.io");
const http = require("http");
const PORT = 5000;
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "http://localhost:3000",
},
}); //in case server and client run on different urls
const MongoClient = require("mongodb").MongoClient;
MongoClient.connect("mongodb://localhost:27017", (err, db) => {
if (err) throw err;
console.log("connected");
const database = db.db("local");
const chat = database.collection("chat");
//connect to socket .io
io.on("connection", (socket) => {
// get chat from collection
chat.find().toArray((err, res) => {
if (err) throw err;
// emit the messages
socket.emit("output", res);
});
//handle input events
socket.on("input", (input) => {
let sender = input.sender;
let receiver = "bot";
let text = input.text;
// check if inside chat collection the members array contains the sender and receiver
chat.find({ members: { $all: [sender, receiver] } }).toArray((err, res) => {
if (err) throw err;
let senderInDb = res[0].members[0];
let receiverInDb = res[0].members[1];
//if yes then push the message to the messages array
if (sender === senderInDb && receiver === receiverInDb) {
chat.updateOne(
{ members: { $all: [sender, receiver] } },
{ $push: { messages: { sender: sender, text: text } } }
);
}
});
});
});
});
server.listen(PORT, (err) => {
if (err) console.log(err);
console.log("Server running on Port ", PORT);
});
Related
I'm trying to create a realtime online chat using React, node and mongodb. In my opinion, the chat shoudl work this way: client send the object of the message he created to the server to save it via rest(works properly) and via socket so the socketserver "broadcast" the message to every socket that is in the same room(socket on connection is put in a room according to something store in localstorage). So, other client in the same room should then recive the message, and happend it to the chat. But it is not workink. In fact, the following error shows up: Uncaught TypeError: msg.map is not a function
This is my react code:
import {useState, useEffect} from 'react';
import axios from 'axios';
import { io } from "socket.io-client";
const Chat = () => {
const [msg, setMsg] = useState([]);
const [message, setMessage] = useState('');
const socket = io("http://localhost:5050");
useEffect(() => {
if(v.group.name){
axios.get(`http://localhost:5050/chat/getmsg/${v.group.name}`)
.then(group => {
setMsg(group.data)
})
}
}, [v.group.name])
useEffect(() => {
if(localStorage.getItem('isG') === '1'){
socket.on("connect", () => {
socket.emit("groupName", {id:localStorage.getItem('gruop')})
})
socket.on("message", messageS => {
if(messageS.sender !== localStorage.getItem('user'))
setMsg(...msg, messageS)
})
}
// eslint-disable-next-line
}, [socket])
const sendMSG = (e) => {
e.preventDefault();
if(message !== ""){
axios.post("http://localhost:5050/chat/sendmsg", {name:v.group.name, sender:localStorage.getItem('user'), text:message})
.then(() => {
setMessage('');
socket.emit("message", {name:v.group.name, sender:localStorage.getItem('user'), text:message})
setMsg(...msg, {name:v.group.name, sender:localStorage.getItem('user'), text:message})
});
}
}
return <div className="containerLogin1">
<div>
<h3>Chat Name</h3>
</div>
<div className="chatSpace">
{
msg.map((m) => {
return <p key={m._id}>{m.text}</p>
})
}
</div>
<form className="sMSG">
<input type="input" style={{'border':'2px solid black'}} value={message} onChange={(e) => setMessage(e.target.value)}/>
<button className="buttSend" onClick={sendMSG} spellCheck="false">Send</button>
</form>
</div>
}
And this is server code, but I think that he is working fine:
....
const httpServer = app.listen(port, () => {console.log(`Server listening on port ${port}`)});
const { Server } = require("socket.io");
const io = new Server(httpServer, {
cors : {
origin: "*",
methods:["GET", "POST"]
}
} );
io.on("connection", (socket) => {
let currentRoom = "";
socket.on("groupName", msg => {
socket.join(msg.id + "")
currentRoom = msg.id
})
socket.on("text-change", newText => {
io.to(currentRoom).emit( "text-change", {text:newText, emitter:socket.id})
})
socket.on("message", message => {
io.to(currentRoom).emit("message", message);
})
})
I tryied with a bounch of console.log to see where the error could be, but I couldn't find out. It seems that somewhere in the code, msg turns from an array to an object, and so map function crashes. Can someone please help me? Thanks
You have these 2 lines in your code where you are trying to copy the last array and add new object to it:
setMsg(...msg, messageS);
And:
setMsg(...msg, {name:v.group.name, sender:localStorage.getItem('user'), text:message});
These parts are the problem, you should add [] around them. So:
setMsg([...msg, messageS]);
setMsg([...msg, {name:v.group.name, sender:localStorage.getItem('user'), text:message}]);
Folks! I am writing a socket.io chat. When the user sends a message, the socket.on event occurs. Everything works, but the amount of listeners is growing exponentially. I tried to remove the listener with socket.off/socket.remove..., take out the event socket.on separately from connection - nothing works. Please, please help me figure it out. I`m using react, node, socket & mongoDB
Server part:
const express = require("express");
const app = express();
const cors = require("cors");
const { UserModel } = require("./models");
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.options("http://localhost:3000", cors());
const mongoose = require("mongoose");
require("dotenv").config();
const http = require("http").createServer(app);
const io = require("socket.io")(http, {
cors: {
origin: ["http://localhost:3002"],
},
});
http.listen(3001, function () {
console.log("Server is running");
});
io.on("connection", (socket) => {
console.log("Socket connected", socket.id);
socket.on("message:send", (data) => {
console.log("Пришло в socket.onMessage", data);
socket.emit("message:fromServer", data);
// socket.removeListener("message:fromServer");
});
});
const { userApi } = require("./api/userApi");
app.use("/", userApi);
app.use((err, _, res, __) => {
return res.status(500).json({
status: "fail",
code: 500,
message: err.message,
});
});
const { PORT, DB_HOST } = process.env;
const dbConnection = mongoose.connect(DB_HOST, {
useNewUrlParser: true,
useFindAndModify: false,
useCreateIndex: true,
useUnifiedTopology: true,
});
dbConnection
.then(() => {
console.log("DB connect");
app.listen(PORT || 3000, () => {
console.log("server running");
});
})
.catch((err) => {
console.log(err);
});
Client part:
io.js
import { io } from "socket.io-client";
export const socket = io("http://localhost:3001/");
Message component
import React from "react";
import { useState } from "react";
// import { useSelector } from "react-redux";
// import { getMessages } from "../../Redux/selectors";
import { socket } from "../helpers/io";
import Message from "../Message/Message";
import { nanoid } from "nanoid";
export default function MessageFlow() {
const [message, setMessage] = useState([]);
socket.on("message:fromServer", (data) => {
console.log("На фронт пришло сообщение: ", data);
setMessage([...message, data]);
// setMessage((message) => [...message, data]);
console.log("Массив сообщений компонента MessageFlow", message);
console.log(socket.io.engine.transports);
// socket.off();
// getEventListeners(socket)['testComplete'][0].remove()
// socket.removeListener("message:fromServer");
});
// const dispatch = useDispatch();
// const allMessages = useSelector(getMessages);
return (
<div id="mainDiv">
{message &&
message.map((i) => {
// return <Message />;
return <Message content={i.userId} id={nanoid()} />;
})}
</div>
);
}
Message Form - the beginning of emitting process
import React, { useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { sendMessage } from "../../Redux/Chat/Chat-operations";
import { getUser } from "../../Redux/selectors";
import { getToken } from "../../Redux/Auth/Auth-selectors";
import { socket } from "../helpers/io";
import { useEffect } from "react";
import styles from "./MessageForm.module.css";
export default function MessageForm() {
const [message, setMessage] = useState("");
const dispatch = useDispatch();
const userId = useSelector(getUser);
const currentToken = useSelector(getToken);
// const getAll = useSelector(allContacts);
const updateMessage = (evt) => {
setMessage(evt.target.value);
};
const handleSubmit = (e) => {
e.preventDefault();
if (message) {
socket.emit("message:send", { userId: message });
dispatch(
sendMessage(
{
_id: userId,
text: message,
},
currentToken
)
);
} else {
alert(`Молчать будем?`);
}
};
return (
<div className={styles.messageInputContainer}>
<form>
<input
type="text"
value={message}
onChange={updateMessage}
required
className={styles.messageInput}
placeholder="Type message to send"
/>
<button
type="submit"
className={styles.messageAddBtn}
onClick={handleSubmit}
>
Send
</button>
</form>
</div>
);
}
You add a listener on every render, you should use useEffect hook
export default function MessageFlow() {
useEffect(()=>{ // triggered on component mount or when dependency array change
const callback = (data) => {
// what you want to do
}
socket.on("message:fromServer", callback);
return () => { // on unmount, clean your listeners
socket.removeListener('message:fromServer', callback);
}
}, []) // dependency array : list variables used in your listener
// [...]
}
(Working) I can see the message was sent to the server just fine:
// function used in reactjs(client)
const sendMessage = () => {
const data = {
room: room,
content: {
author: userName,
message: message
}
}
Promise.resolve(socket.emit('send_message', data))
.then(() => setMessage(''));
}
useEffect(() => {
socket.on('receive_message', data => {
console.log(data);
});
}, []);
// function used in index.js(server)
io.on('connection', (socket) => {
console.log(socket.id);
socket.on('join_room', (roomName) => {
socket.join(roomName); // creates room with the name
console.log('User Joined Room:', roomName);
});
socket.on('send_message', (data) => {
console.log("data =", data);
socket.to(data.room).emit('receive_message', data.content);
});
socket.on('disconnect', () => {
console.log('USER DISCONNECTED')
});
});
(Problem) Can anyone tell me why receive_message is not showing up in the console.log?
Note: it will only show if I do something like add some comment to the react file and save it.(weird)
The Full Code of both files
index.js(server)
const express = require('express');
const morgan = require('morgan');
const { createServer } = require('http');
const { Server } = require('socket.io');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
app.use(morgan('dev'));
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: true,
origins: ['http://localhost:3000']
});
io.on('connection', (socket) => {
console.log(socket.id);
socket.on('join_room', (roomName) => {
socket.join(roomName); // creates room with the name
console.log('User Joined Room:', roomName);
});
socket.on('send_message', (data) => {
console.log("data =", data);
socket.to(data.room).emit('receive_message', data.content);
});
socket.on('disconnect', () => {
console.log('USER DISCONNECTED')
});
});
httpServer.listen(3002, () => console.log(`Socket.io test running on port 3002`))
app.js(client)
import React, { useEffect, useState } from 'react';
import io from 'socket.io-client';
import './App.css';
let socket = io('localhost:3002/');
function App() {
// variables
const [loggedIn, setLoggedIn] = useState(false);
const [room, setRoom] = useState('');
const [userName, setUserName] = useState('');
const [message, setMessage] = useState('');
const [messageList, setMessageList] = useState([]);
// functions
const connectToRoom = () => {
console.log('button clicked');
setLoggedIn(true);
socket.emit('join_room', room); // join_room copied from backend
}
const sendMessage = () => {
const data = {
room: room,
content: {
author: userName,
message: message
}
}
Promise.resolve(socket.emit('send_message', data))
.then(() => setMessage(''));
}
// setup
useEffect(() => {
socket.on('receive_message', data => {
console.log(data);
});
}, []);
return (
<div className="App">
{!loggedIn ?
<div className="logIn">
<div className="inputs">
<input type="text" placeholder="Name..." value={userName} onChange={ev => setUserName(ev.target.value)} />
<input type="text" placeholder="Room..." value={room} onChange={ev => setRoom(ev.target.value)} />
</div>
<button onClick={connectToRoom}>Enter Chat</button>
</div> :
<div className="chatContainer">
<div className="messages">
<h1>{room}</h1>
{messageList.map((messageInfo, index) =>
<div key={index} className="messageBox">
<p>{messageInfo.author}: {messageInfo.message}</p>
</div>
)}
</div>
<div className="messageInputs">
<input type="text" placeholder="Message..." value={message} onChange={ev => setMessage(ev.target.value)} />
<button onClick={sendMessage}>Send</button>
</div>
</div>
}
</div>
);
}
export default App;
I found the issue. The state is managed in two different places on the client side.
For the socket.on('receive_message', ...) in useEffect, this will show up on every single other browser console logged into the current room except the current browser's console(the browser sending the message).
To also get it to show in current console browser window, I also needed to add the state in where I am submitting it.
const sendMessage = () => {
const data = {
room: room,
content: {
author: userName,
message: message
}
}
Promise.resolve(socket.emit('send_message', data))
.then(() => console.log(data)) // <------- add this line here
.then(() => setMessage(''));
}
This of course means setMessageList will need to be called both inside the sendMessage() and the socket.on('receive_message',...)(within the useEffect())
However, be careful when calling setMessageList inside socket.on('receive_message',...). The reasons for this is because it is called within a single useEffect one time, it captures the state from when that function was called and will not update. In other words, you cannot use setMessageList([...messageList, data]) because this function will only read messageList from when this function was first called initially.
To get around this, I see two options...
You can remove the [] inside the useEffect((...), []) forcing it to run in an infinite loop (this seems expensive).
The other option is to have the data on the backend update, save, then send back to which the setMessageList(data)(inside socket.on('receive_message',...)) will have to reload all the messages and re-render them all. (still seems expensive, but not as much as the first option)
Problem:
I am working on a chat application. When I send more than 9-10 requests, the browser slows down and eventually, it just hangs. On refreshing the page, everything is back to normal.
I searched the socket.io documentation but couldn't get any solution regarding this matter.
Code:
Here is my Backend Express.JS code:
index.js
const express = require("express");
const { addUser, getUser, deleteUser, getAllUsers } = require("./users_api");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
});
const port = 5000 || process.env.PORT;
io.on("connection", (socket) => {
socket.on("join", ({ name, room }, callback) => {
const { err, user } = addUser({ id: socket.id, name, room });
if (err) return callback(err);
if (user) {
socket.emit("message", {
user: "admin",
text: `${user.name} has entered the room ${user.room}.`,
});
socket.broadcast.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has joined the room.`,
});
socket.join(user.room);
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
callback();
}
});
socket.on("client_message", (msg) => {
const user = getUser(socket.id);
if (user) io.to(user.room).emit("message", { user: user.name, text: msg });
});
socket.on("disconnect", () => {
console.log(6);
const user = deleteUser(socket.id);
if (user) {
io.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has left the room.`,
});
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
}
});
});
server.listen(port, () => console.log(`Server started at port ${port}.`));
users_api.js:
const current_users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase().split(" ").join("_");
room = room.trim().toLowerCase();
const exist_user = current_users.find(
(user) => name === user.name && room === user.room
);
if (exist_user)
return {
err: "User with this name already exists.",
};
current_users.push({ id, name, room });
return { user: { id, name, room } };
};
const deleteUser = (id) => {
const index = current_users.findIndex((user) => user.id === id);
if (index !== -1) return current_users.splice(index, 1)[0];
};
const getUser = (id) => current_users.find((user) => user.id === id);
const getAllUsers = (room) =>
current_users.filter((user) => user.room === room);
module.exports = { addUser, deleteUser, getUser, getAllUsers };
Front-end Code:
import io from "socket.io-client";
import React, { useEffect, useRef, useState } from "react";
import queryString from "query-string";
import "./ChatPage.css";
const END_POINT = "http://localhost:5000";
const ChatPage = (props) => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const socket = useRef(null);
const handleSubmit = () => {
socket.current.emit("client_message", message);
setMessage("");
};
useEffect(() => {
const { name, room } = queryString.parse(props.location.search);
socket.current = io(END_POINT);
socket.current.emit("join", { name, room }, (error) => {
if (error) alert(error);
});
return () => {
socket.current.disconnect();
socket.current.off();
};
}, [props.location.search, END_POINT]);
useEffect(() => {
socket.current.on("message", (msg) => {
setMessages([...messages, msg]);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
return (
<div className="chat-room-container">
<div className="chat-room-left">
<ul>
{messages.map((message, i) => (
<li key={i}>
{message.name}
{message.text}
</li>
))}
</ul>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="button" onClick={handleSubmit}>
Submit
</button>
</div>
<div className="chat-room-right">
<ul>
{users.map((user, i) => (
<li key={i}>{user.name}</li>
))}
</ul>
</div>
</div>
);
};
export default ChatPage;
I don't understand what's wrong, please help.
Instead of sending a message on event or broadcast try to send list of messages to particular user or all users list with their messages belonging to same room. This will leads to less render in frontend.
For example,
Your Frontend code will change like,
useEffect(() => {
socket.current.on("message", (msgList) => {
setMessages(msgList);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
Also if different rooms then there occurs a problem to filter the messages. So to solve this there are two possible ways:
When you send filter data from server.
Filter data in client according to user name and room name.
As this solution is not a good practice as every time the whole list of message will come and applying filter on that will increase time complexity. But you can reduce to some extent by making proper structure of data state on server.
I think this will help you.
You are creating new socket connection on every useEffect, so after ten messages, you have ten connections.
socket = io(END_POINT);
I store the created socket connection in useRef - so if it is created again, it is overwritten with the new one - and it does not duplicate.
const socketConection = useRef(null)
useEffect(() => {
socketConnection.current = io(END_POINT)
}, [deps])
Of course, in all following uses you have to use socketConection.current
server side
const express = require("express");
const socket = require("socket.io");
const http = require("http");
const { addUser, removeUser, getUser, getUsersInRoom } = require("./user");
const PORT = process.env.PORT || 8000;
// 2. 라우터 설정
const router = require("./router");
const app = express();
app.use(function (req, res, next) {
// Website you wish to allow to connect
res.setHeader("Access-Control-Allow-Origin", "http://localhost:3001");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
res.setHeader("Access-Control-Allow-Credentials", true);
next();
});
const server = http.createServer(app);
const io = socket(PORT);
io.on("connection", (socket) => {
console.log("소켓 연결 완료");
socket.on("join", ({ name, room }, callback) => {
const { error, user } = addUser({ id: socket.id, name, room });
if (error) return callback(error); // username taken
socket.join(user.room);
socket.emit("message", {
user: "admin",
text: `${user.name}, welcome to the room ${user.room}`,
});
socket.broadcast
.to(user.room)
.emit("message", { user: "admin", text: `${user.name}, has joined!` });
io.to(user.room).emit("roomData", {
room: user.room,
users: getUsersInRoom(user.room),
});
callback();
});
socket.on("sendMessage", (message, callback) => {
const user = getUser(socket.id);
io.to(user.room).emit("message", { user: user.name, text: message });
// callback();
});
socket.on("disconnect", () => {
const user = removeUser(socket.id);
console.log("유저가 떠났습니다..");
if (user) {
io.to(user.room).emit("message", {
user: "Admin",
text: `${user.name} has left.`,
});
io.to(user.room).emit("roomData", {
room: user.room,
users: getUsersInRoom(user.room),
});
}
});
});
app.use(router);
server.listen(PORT, () => console.log(`server has started on port ${PORT}`));
and this is client side
import React, { useEffect, useState } from "react";
import queryString from "query-string";
import io from "socket.io-client";
import "./Chat.css";
// 하위 컴포넌트
import Messages from "../Messages/Messages";
import RoomInfo from "../RoomInfo/RoomInfo";
import Input from "../Input/Input";
let socket;
const Chat = ({ location }) => {
const [name, setName] = useState("");
const [room, setRoom] = useState("");
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState("");
const ENDPOINT = "http://localhost:8000/";
useEffect(() => {
const { name, room } = queryString.parse(location.search);
socket = io.connect(ENDPOINT); // 소켓 연결
setName(name);
setRoom(room);
console.log(name, room); // lama peru
// console.log(socket);
socket.emit("join", { name, room }, (error) => {
// console.log("error");
// 에러 처리
if (error) {
alert(error);
}
});
}, [ENDPOINT, location.search]);
useEffect(() => {
// 서버에서 message 이벤트가 올 경우에 대해서 `on`
socket.on("message", (message) => {
setMessages([...messages, message]);
});
socket.on("roomData", ({ users }) => {
setUsers(users);
});
}, [messages]);
// 메세지 보내기 함수
const sendMessage = (e) => {
e.preventDefault();
if (message) {
socket.emit("sendMessage", message, setMessage(""));
}
};
console.log(message, messages);
console.log(users, "users");
return (
<div className="chatOuterContainer">
<div className="chatInnerContainer">
<div className="appbar"></div>
<div className="chatScreen">
<div className="chatScreen">
<RoomInfo room={room} />
<div className="messageContainer">
<Messages messages={messages} name={name} />
</div>
<Input message={message} setMessage={setMessage} sendMessage={sendMessage} />
</div>
</div>
</div>
</div>
);
};
export default Chat;
and when I try the website I get this
[enter image description here][1]
as you can see there's no Access-Control-Allow-Origin in response header..
I start my project by following.
first start server by 'node server.js'
start client by command 'yarn start'
the cors options don't seem to apply.
[1]: https://i.stack.imgur.com/QpQ7o.png
would be very thankful if you help me!
try this:
app.all('/*', function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Credentials", "true")
res.header("Access-Control-Allow-Headers", "Origin,X-Requested-With,Content-Type,Accept,Authorization")
res.header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,PATCH,OPTIONS")
next()
})