Why am I not receiving emitted messages sent to socket server? - node.js

I'm trying to receive a message from a post request and forward it to a socket id.
I defined the socket logic so that when a user connects, they get a socket id matched with their phone number in a Map global variable called users
Socket logic
const socket = (io) => {
global.users = new Map()
io.on("connection", (socket) => {
global.rooms = [socket.id]
socket.join(global.rooms)
socket.on("introduce", function (phone) {
io.emit("new_connection", phone)
global.users.set(phone, socket.id)
console.log("users map --> ", global.users)
})
socket.on("disconnect", async () => {
console.log("user has disconnected")
})
})
}
API POST requests
messageHandler = async (req,res) => {
const receiver = global.users.get(req.body.To)
const sender = global.users.get(req.body.From)
const io: Server = req.app.get("io")
const message = {
body: req.body.Body,
from: req.body.From,
to: req.body.To,
dateCreated: new Date(),
}
if (receiver) {
const receiverRoom = global.users.get(receiver)
io.to(receiverRoom).emit("message-receive", message)
res.status(200).send("Message recieved")
}
}
according to my logic, I should be able to listen on "message-receive" in the client and receive the messages, but nothing happens when I do so.
Client side
import io from "socket.io-client"
import { domain, socketBase } from "../config"
const socket = io.connect(domain, { path: socketBase })
export class socketService {
static onSmsMessage(message) {
socket.on("message-receive", message)
}
static removeListeners() {
socket.removeAllListeners()
}
}
const testing = useCallback(
(message) => {
console.log(message)
},
[]
)
// Refresh when a new sms message received
useEffect(() => {
// socketService.onSmsMessage(SmsReceived)
socketService.onSmsMessage(testing)
return () => {
socketService.removeListeners()
}
}, [testing])

Related

wrong behavior of messages socket.io

I use socket.io and socket.io-client to create chat user - operator. I've made connections, all works good, but message sending has weird behavior.
Like I send message from first client to room in socket and don't see it, but other client can
When I send second message, the first sent msg just changes for the second one
I see this all messages in my Map on backend(I don't use DB yet) but can't see it on client side.
server
app.get("/rooms/:id", (req, res) => {
const roomId = req.params.id;
const obj = rooms.has(roomId)
? { messages: [...rooms.get(roomId).get("messages").values()] }
: { messages: [] };
res.send(obj);
});
app.post("/rooms", (req, res) => {
const { roomId, name } = req.body;
if (!rooms.has(roomId)) {
rooms.set(
roomId,
new Map([
["users", new Map()],
["messages", []],
])
);
}
res.json([...rooms.values()]);
});
io.on("connection", (socket) => {
socket.on("ROOM:JOIN", ({ roomId, name }) => {
socket.join(roomId);
rooms.get(roomId).get("users").set(socket.id, name);
const users = rooms && [...rooms.get(roomId).get("users").values()];
socket.to(roomId).emit("ROOM:JOINED", users);
});
socket.on("ROOM:NEW_MESSAGE", ({ roomId, name, text }) => {
const obj = { name, text };
console.log(rooms.get(roomId).get("messages"));
rooms.get(roomId).get("messages").push(obj);
socket.to(roomId).emit("ROOM:NEW_MESSAGE", obj);
});
socket.on("disconnect", () => {
rooms.forEach((value, roomId) => {
if (value.get("users").delete(socket.id)) {
const users = [...value.get("users").values()];
socket.to(roomId).emit("ROOM:JOINED", users);
}
});
});
});
client side
const [messageValue, setMessageValue] = useState<string>("");
const [freshData, setFreshData] = useState<Message[]>([]);
console.log(freshData);
const onSendMessage = () => {
socket.emit("ROOM:NEW_MESSAGE", {
roomId: socketData.roomId,
text: messageValue,
name: socketData.name,
});
setMessageValue("");
};
const getFreshData = async () => {
const { data } = await axios.get(
`http://localhost:4444/rooms/${socketData.roomId}`
);
setFreshData(data.messages);
};
useEffect(() => {
if (join) {
socket.emit("ROOM:JOIN", socketData);
getFreshData();
socket.on("ROOM:JOINED", (users) => {
console.log("new user", users);
});
}
}, [join]);
useEffect(() => {
socket.on("ROOM:NEW_MESSAGE", (message) =>
setFreshData([...freshData, message])
);
socket.on("ROOM:JOINED", (users) => {
console.log("new user", users);
});
}, []);
and just map through all messages, that I should get from server
Where I could make a mistake ? Thank you
So I had a mistake on client, I need to call socket function in other useEffect with dependency onSendMessage like this:
useEffect(() => {
socket.on("ROOM:NEW_MESSAGE", (message) =>
setFreshData([...freshData, message])
);
}, [onSendMessage]);
and to show your sent messages I need to make onSendMessage update state with messages like this:
const onSendMessage = () => {
socket.emit("ROOM:NEW_MESSAGE", {
roomId: socketData.roomId,
text: messageValue,
name: socketData.name,
});
setFreshData([...freshData, { text: messageValue, name: socketData.name }]);
setMessageValue("");
};
And it works !

SlackBot repeat the event without control

I am trying to create a bot in Slack called "Chochon", the problem is that when I receive the event "app_mention", chochon responds more than once several times in a row, instead of sending 1 message and stopping until they mention it again.
This is my code, chochon function:
socketModeClient.on('app_mention', async ({ event }) => {
try {
console.log(event);
let userBox = await Chochon.users.info({ user: event.user });
let userProfile = userBox.user.profile;
console.log(cli.green(`Event received : [ ${event.type} ] from [ ${userProfile.display_name} ]`));
// Respond to the event
Chochon.chat.postMessage({
channel: event.channel,
text: `Hello <#${event.user}>, I'm Chochon!, I'm a bot that can help you to manage your team.`
});
} catch (error) {
console.error(error);
}
});
The slack client:
Full code:
// Dependencies :
const dotenv = require('dotenv').config();
const path = require('path');
const cli = require('cli-color');
// Web client [CLI]
const { WebClient } = require('#slack/web-api');
const Chochon = new WebClient(process.env.SLACK_BOT_TOKEN.trim());
// Socket IO
const { SocketModeClient } = require('#slack/socket-mode');
const appToken = process.env.SLACK_APP_TOKEN;
const socketModeClient = new SocketModeClient({ appToken });
socketModeClient.start();
// Internal functions
//const eventManager = require(path.resolve(__dirname, './utils/events/manager'));
socketModeClient.on('app_mention', async ({ event }) => {
try {
console.log(event);
let userBox = await Chochon.users.info({ user: event.user });
let userProfile = userBox.user.profile;
console.log(cli.green(`Event received : [ ${event.type} ] from [ ${userProfile.display_name} ]`));
// Respond to the event
Chochon.chat.postMessage({
channel: event.channel,
text: `Hello <#${event.user}>, I'm Chochon!, I'm a bot that can help you to manage your team.`
});
} catch (error) {
console.error(error);
}
});
socketModeClient.on('slash_commands', async ({ body, ack }) => {
if (body.command === "/ping") {
console.log(cli.green(`Event received : [ ${body.command} ]`));
await ack({"text": "I got it, pong!"});
}
});

React Native one to one conversation using socket.io

i currently have a react native app with nodejs express Sequelize as my backend and postgres as my database.
So, on my posts screen next to each post, i have a text input and a button where the current user can send the user of the post an initial message. Once the button is pressed, a conversation between these 2 users about this post is created in my database and stored in my conversation table and an entry of the message sent is also stored in my messages table.
I have implemented bidirectional communication between these 2 users. But my problem is i need to refresh the app in order to show the user current user the sent message and to show the receiving user the received message.
I have been researching for a while now and trying to understand how to implement this feature using socket.io but could not get anywhere.
Client Side
Here is my Chat Screen
function ChatScreen({route,navigation}) {
const message = route.params.message;
const [messages, setMessages] = useState(message.Messages);
const [text, setText] = useState('');
const { user } = useAuth();
const [socket, setSocket] = useState(null);
useEffect(() => {
const newsocket =io.connect(socketurl)
setMessages(messages);
newsocket.on('connection', msg => {
console.log('i have joined')
setMessages(messages=>messages.concat(msg))
setSocket(newsocket)
})
return()=>newsocket.close;
}, []);
const updateText=(text)=>{
setText(text);
}
const onSend = (ConversationId,senderId,receiverId,message) => {
console.log("sent")
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
setText("")
socket.emit('message', { to: (user.id===route.params.message.user1 ?
route.params.message.user2 : route.params.message.user1), from:
user.id, message,ConversationId });
};
return(
<Text>{user.id === message.Recipient.id ?
message.Creator.name:message.Recipient.name}</Text>
<KeyboardAvoidingView
style={{
display: "flex",
flex: 1,
}}
behavior={Platform.OS === "ios" ? "padding" : null}
keyboardVerticalOffset={Platform.OS === "ios" ? 25 : 0}
>
<FlatList
inverted
data={message.Messages}
keyExtractor={(message) => message.id.toString()}
renderItem={({item,index})=>(
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
)}/>
<View style={styles.messageBoxContainer}>
<TextInput
style={styles.messageBox}
placeholder="Message..."
multiline
clearButtonMode="while-editing"
onChangeText={updateText}
value={text}
autoCorrect={false}
/>
<TouchableOpacity onPress={onSend}>
<Text style={styles.send}>Send</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
)
Server Side
index.js
const express = require("express");
const app = express();
const http = require("http");
const socketio = require("socket.io")
const server=http.createServer(app);
const io =socketio(server)
io.on("connection", socket => {
socket.on('message', (data) => {
socket.join(data.ConversationId);
io.sockets.in(data.to).emit('send_message', { message: data.message,
to: data.to });
});
});
const port = process.env.PORT || config.get("port");
server.listen(port, function () {
console.log(`Server started on port ${port}...`);
});
Currently when i send a message, the message gets stored in my database but my chat does not update instantly (ie. not live), i need to refresh my app and the messages appear.
Can someone please help me and check if the implementation of socket i currently have is correct and if so, how do i render my flatlist instantly?
UPDATE
i think something is wrong in my useEffect, because when i open the chat i am not getting "i have joined" in the console:
useEffect(() => {
setMessages(messages);
socket.on('connect', msg => {
console.log('i have joined')
setMessages(messages=>messages.concat(msg))
})
}, []);
Your currently creating a new connection on every state change.. const socket =io.connect(socketurl)
You have a useEffect callback and that would be the logical place to put your connection logic, currently your only listening once for a connection, but creating multiple connections, so your 'connection' event is never called on these new connections. But you only want to connect once anyway, so we just need to put the connection logic also inside the useEffect, not just the connection event.
Because connecting to a socket is async, you will want to wait for the connection before rendering. So what we could do is store the socket in state, and when we get a connection set socket state, this will fire a re-render with a now valid socket.
eg.
const [socket, setSocket] = useState(null);
...
useEffect(() => {
const socket = io.connect(socketurl)
setMessages(messages);
newsocket.on('connect', msg => { //connect not connection
console.log('i have joined')
setMessages(messages=>messages.concat(msg));
setSocket(newSocket);
});
//might make sense to close the socket too,
//otherwise a memory leak.
return () => newSocket.close();
}, [route, navigation]);
if (!socket) {
//we don't have a socket yet,
return "loading..";
} else {
// we have a socket,
const onSend = (ConversationId,senderId,receiverId,message) => {...
....
// now render..
return (
<Text>{.........
Socket Index.js
/** Socket.io server listens to our app **/
var io = require('socket.io').listen(app);
io.on('connection', function (socket) {
/** On User Log In **/
socket.on('login', function (data) {
console.log('user joined >>', data)
userList.addUser(data, socket);
});
// Example Event //
socket.on('get_online_friends', userId => {
//Get List Data And Then //
let data = [UserList];
socket.emit('send_online_friend', data);
}
)
// On Logout //
socket.on('disconnect', function (reason) {
var offlineId = userList.removeUser(socket)
);
}
user_list.js
var userList = {};
module.exports = userList;
var userData = [];
var userSocData = {};
userList.user = userData;
userList.userSoc = userSocData;
userList.getUserList = function () {
return userSocData;
};
userList.addUser = function (user, client) {
userSocData[user] = {
socketId: client.id
}
};
userList.setReceiverId = function (user, client) {
var index = userData.findIndex(x => x.user_id == user['user_id']);
if (index !== -1) {
userData[index]['receiver_id'] = user['receiver_id'];
}
userSocData[user['user_id']] = {
socket: client.id
};
};
userList.removeUser = function (client) {
for (const property in userSocData) {
if (client.id === userSocData[property].socketId) {
var userID = property;
delete userSocData[property]
}
}
return userID;
};
Front End
***
socket.emit("get_request", userData.user_id);
socket.on("get_request_data", function (data) {
if (data.status) {
self.setState({ onlineFriends: data.data });
}
});
***

AMQP + NodeJS wait for channel

I have a service in FeathersJS that initiates a connection to RabbitMQ, the issue is how to await for a channel to be ready before receiving requests:
class Service {
constructor({ amqpConnection, queueName }) {
this.amqpConnection = amqpConnection;
this.queueName = queueName;
this.replyQueueName = queueName + "Reply"
}
async create(data, params) {
new Promise(resolve => {
if (!this.channel) await this.createChannel();
channel.responseEmitter.once(correlationId, resolve);
channel.sendToQueue(this.queueName, Buffer.from(data), {
correlationId: asyncLocalStorage.getStore(),
replyTo: this.replyQueueName,
});
});
}
async createChannel() {
let connection = this.amqpConnection();
let channel = await connection.createChannel();
await channel.assertQueue(this.queueName, {
durable: false,
});
this.channel = channel;
channel.responseEmitter = new EventEmitter();
channel.responseEmitter.setMaxListeners(0);
channel.consume(
this.replyQueueName,
(msg) => {
channel.responseEmitter.emit(
msg.properties.correlationId,
msg.content.toString("utf8")
);
},
{ noAck: true }
);
}
....
}
Waiting for the channel to be created during a request seems like a waste. How should this be done "correctly"?
Feathers services can implement a setup method which will be called when the server is started (or you call app.setup() yourself):
class Service {
async setup () {
await this.createChannel();
}
}

Unable to properly establish a connection between React Native client and Node.js server with redux-saga and socket.io

Quick context: I'm trying to build a react native prototype of a comment page where users can receive live updates (comments, users entering the comment screen, users leaving, etc.). To do this, I am using react-redux, redux-saga, socket.io, and node.js (server). I'm new to redux-saga so I might be missing something obvious here, so hang on, please... The culprit definitely lies in the watchCommentActions function/saga...
The problem: As soon as it is done mounting, the comment screen dispatches the following action { type: comment.room.join, value }, which is then correctly acknowledged by rootSaga, however, when trying to connect to the socket using a promise-resolve structure via const socket = yield call(connect); the promise never resolves, which blocks the generator (it does not proceed to the next yield). What's weird is that on the other side the server does log the connection to the socket, so the connection client --> server appears to be ok. Also, by hot reloading the app I can manage to resolve the promise (it's like the generator needs to run twice to resolve the socket connection), but then the socket.emit("join-room") never reaches the server and the generator gets stuck again.
Similarly, when I try to fire the write generator by posting a comment and thus dispatching {type: comment.post.start, value } the *socket.emit("comment", {text: value.text}) does not reach the server either.
To sum it up briefly nothing's really working and no error is getting thrown... GREAT.
Last words: Before moving my socket logic to saga the socket connection was working seamlessly. I've also tried to reuse the documentation's implementation with channels by using the same connect function instead of createWebSocketConection (https://redux-saga.js.org/docs/advanced/Channels.html) but the promise-resolve-socket situation still occurs. Also, I've noticed similar questions derived from the same git repo I've studied to understand the sagas logic (https://github.com/kuy/redux-saga-chat-example/blob/master/src/client/sagas.js), however, none of them allowed me to understand what's wrong with my implementation. Finally, if there is a better way to implement this logic with redux-saga, I am interested, all I want is a robust, centralized, and reusable implementation.
Sagas/index.js
import { all, takeEvery, takeLatest } from "redux-saga/effects";
import { comment } from "../Reducers/commentCacheReducer";
import { like } from "../Reducers/postsCacheReducer";
import { posts } from "../Reducers/postsReducer";
import flow from "./commentSagas";
import { likePost, unlikePosts } from "./likeSagas";
import { fetchPosts } from "./postsSagas";
function* watchLikeActions() {
yield takeLatest(like.add.start, likePost);
yield takeLatest(like.remove.start, unlikePost);
}
function* watchFetchActions() {
yield takeEvery(posts.fetch.start, fetchPosts);
}
function* watchCommentsActions() {
yield takeEvery(comment.room.join, flow);
}
export default function* rootSaga() {
yield all([watchLikeActions(), watchFetchActions(), watchCommentsActions()]);
}
Sagas/commentSaga.js
import { eventChannel } from "redux-saga";
import { call, cancel, fork, put, take } from "redux-saga/effects";
import io from "socket.io-client";
import { endpoint } from "../../API/ServerAPI";
import { addUser, fetchComment, leaveRoom, removeUser } from "../Actions/commentActions";
import { comment } from "../Reducers/commentCacheReducer";
function connect() {
const socket = io(endpoint);
return new Promise((resolve) => {
socket.on("connection", () => {
resolve(socket);
});
});
}
function subscribe(socket) {
return new eventChannel((emit) => {
socket.on("users.join-room", ({ userId }) => {
emit(addUser({ userId }));
});
socket.on("users.leave-room", ({ userId }) => {
emit(removeUser({ userId }));
});
socket.on("comments.new", ({ comments }) => {
emit(fetchComment({ comments }));
});
socket.on("users.join-room", ({ userId }) => {
emit(addUser({ userId }));
});
return () => {};
});
}
function* read(socket) {
const channel = yield call(subscribe, socket);
while (true) {
let action = yield take(channel);
yield put(action);
}
}
function* write(socket) {
while (true) {
const { value } = yield take(comment.post.start);
socket.emit("comment", { text: value.text });
}
}
function* handleIO(socket) {
yield fork(read, socket);
yield fork(write, socket);
}
export default function* flow() {
const socket = yield call(connect);
socket.emit("join-room", (res) => {
console.log(JSON.stringify(res));
});
const task = yield fork(handleIO, socket);
let action = yield take(leaveRoom);
yield cancel(task);
yield put(action);
socket.emit("leave-room");
}
server.js
const http = require("http");
const app = require("./app");
const socketIo = require("socket.io");
const mongoose = require("mongoose");
const normalizePort = (val) => {
const port = parseInt(val, 10);
if (isNaN(port)) {
return val;
}
if (port >= 0) {
return port;
}
return false;
};
const port = normalizePort(process.env.PORT || "3000");
app.set("port", port);
const errorHandler = (error) => {
if (error.syscall !== "listen") {
throw error;
}
const address = server.address();
const bind = typeof address === "string" ? "pipe " + address : "port: " + port;
switch (error.code) {
case "EACCES":
console.error(bind + " requires elevated privileges.");
process.exit(1);
break;
case "EADDRINUSE":
console.error(bind + " is already in use.");
process.exit(1);
break;
default:
throw error;
}
};
const server = http.createServer(app);
const io = socketIo(server);
server.on("error", errorHandler);
server.on("listening", () => {
const address = server.address();
const bind = typeof address === "string" ? "pipe " + address : "port " + port;
console.log("Listening on " + bind);
});
// comments room
// Storing in variable just for testing purposes, will
// connect to MongoDB once the socket problem gets solved.
let userIds = [];
io.on("connection", (socket) => {
console.log("[server] connect");
});
io.on("join-room", (socket, {userId}) => {
console.log(`[server] join-room: ${userId}`);
userIds.push(userId);
socket.socket.username = userId;
socket.broadcast.emit("users.join-room", { userId });
});
io.on("leave-room", (socket) => {
const { userId } = socket.socket;
if (userId) {
console.log(`[server] leaving-room: ${userId}`);
userIds = userIds.filter((u) => u !== userId);
delete socket.socket["userId"];
socket.broadcast("users.leave-room", { userId });
}
});
// Storing in variable just for testing purposes, will
// connect to MongoDB once the socket problem gets solved.
let messages = [];
io.on("comment", (socket, { text }) => {
console.log(`[server] message: ${text}`);
const message = {
id: messages.length,
text,
userId: socket.socket.userId
};
messages.push(message);
socket.broadcast("comments.new", { message });
});
EDIT 1
After quickly going through socket.io documentation I realised that my server quick implementation was faulty, I simply forgot to register event handlers inside the connecting protocol... However, the generator still requires to be triggered twice for the socket connection to start, allowing the promise to resolve and the user to join the socket room.
io.on("connect", (socket) => {
console.log("[server] connect");
socket.on("join-room", ({ userId }) => {
console.log(`[server] join-room: ${userId}`);
userIds.push(userId);
socket.username = userId;
socket.broadcast.emit("users.join-room", { userId });
});
socket.on("leave-room", ({ userId }) => {
if (userId) {
console.log(`[server] leaving-room: ${userId}`);
userIds = userIds.filter((u) => u !== userId);
delete socket["userId"];
socket.broadcast.emit("users.leave-room", { userId });
}
});
socket.on("comment", ({ text }) => {
console.log(`[server] message: ${text}`);
const message = {
id: messages.length,
text,
userId: socket.userId
};
messages.push(message);
socket.broadcast.emit("comments.new", { message });
});
});
It’s connect, not connection
https://github.com/socketio/socket.io-client
(commentSagas.js > connect())

Resources