Socket.IO Sever send to both clients - node.js

I have got a node server running locally and setting up a Socket.IO instance.
const http = require('http');
const socket = require('socket.io');
const path = require('path');
class Controller
{
constructor() {
this.localURL = path.resolve(process.cwd() + '/themes/');
this.theme = null;
const server = http.createServer();
this.io = new socket.Server(server, {
transports: ['websocket'],
});
this.io.on("connection", socket => {
// Wait for the client to send the website theme
socket.on('init', theme => {
// Inform current running client that the server is changing projects.
if (this.theme && this.theme !== theme) {
socket.emit(`message-${this.theme}`, {
type: 'message',
message: `Project changed to ${theme}, stopping ${this.theme}.`
});
return;
}
// Set the theme
this.theme = theme;
});
});
server.listen(8080);
}
}
new Controller();
Then on my website I have got a Vue component, but sometimes I could have 2 of the components, so I wanted to emit messages to BOTH of these component's from my server, I will handle accepting the messages in either Vue Instance myself.
This was working, all of a sudden it's not now, not too sure what I changed.
import { io } from 'socket.io-client';
export default {
props: [ 'code' ],
mounted: function () {
this.socket = io('ws://localhost:8080', {
forceNew: true,
timeout: 10000,
transports: ['websocket']
});
this.socket.on('connect', () => {
this.connected = true;
});
this.socket.on('disconnect', () => {
this.connected = false;
this.initiated = false;
});
this.socket.on(`stop-${this.code}`, () => {
this.started = '';
});
this.socket.on(`message-${this.code}`, message => {
console.log(message);
message.time = 'N/A';
this.messages.unshift(message);
})
this.socket.onAny((event, data) => {
if (event.indexOf(this.code) > -1) {
return;
}
event = event.replace(`-${this.code}`, '');
this[event] = data;
});
},
methods: {
initiate() {
this.messages = [];
this.socket.emit('init', this.code);
this.socket.on('started', code => {
if (code !== this.code) {
console.log('Themes don\'t match...');
this.initiated = false;
return;
}
So initially I would run initiate on one of the components, this sends some the theme name to the server and the server stores the theme in a property. Then I would run initiate on the second component, which would send a different theme, so this should hit the this.theme && this.theme !== theme if, and send a message back to the initial theme.
This message is being sent and the event names are as expected, but nothing comes through on the component.

Related

Using Socket.io not working in React Native Mobile App

I have tried to connect socket with React Native mobile App.
Node server socket.io is working i have use with react web, But when i use React Native Mobile App there was not working
Client (React Native
import io from 'socket.io-client';
const ENDPOINT = "http://192.168.1.3:7000";
const socket = io(ENDPOINT, {transports: ['websocket']});
const getMessage = () => {
socket.on('getMessage', (data) => {
console.log('socket data', data);
})
};
const sendMessage = () => {
var userid = log.userid
var message = "msg from app"
socket.emit('send msg', { userid, message });
}
Server (Node Js)
const app = express();
const server = app.listen(7000);
const io = require('socket.io')(server, {
cors : {
origin:'*'
}
});
io.on("connection",(socket)=>{
socket.on('sendMessage',({userid, message})=>{
io.emit('getMessage',{userid, message});
})
})
dev! I solved this just not using socket.io. I got that Socket.io is not a good choice in mobile apps. Use WebSocket API instead.
REACT COMPONENT
import React from 'react';
import { View, Text } from 'react-native';
class WebSocketConnection extends React.Component {
constructor(props) {
super(props);
this.webSocket = this.webSocket.bind(this);
}
webSocket() {
const ws = new WebSocket('ws:172.16.20.201:8080');
ws.onopen = (e) => {
console.log('connected on wsServer');
}
ws.addEventListener('open', function (event) {
ws.send('Hello from React Native!');
});
ws.addEventListener('message', function (event) {
this.message = event.data;
console.log('Message from server ', event.data);
});
ws.onerror = (e) => {
console.log(e);
}
}
render() {
return (
<View>
<Text>
Socket.io YES
</Text>
</View>
)
}
componentDidMount() {
this.webSocket();
}
}
export default WebSocketConnection;
SERVER
/**
* Create WebSocket server.
*/
const WebSocket = require('ws');
const serverWs = new WebSocket.Server({
port: 8080
});
let sockets = [];
serverWs.on('connection', function(socket) {
sockets.push(socket);
console.log(`connectet client`);
// When you receive a message, send that message to every socket.
socket.on('message', function(msg) {
console.log(msg);
sockets.forEach(s => s.send(msg));
});
// When a socket closes, or disconnects, remove it from the array.
socket.on('close', function() {
sockets = sockets.filter(s => s !== socket);
});
});
While using Express, I bind this server inside /bin/www and work fine to me.
I was able to work when i changed the react-native to use the ws protocol. My code was as below
useEffect(()=>{
const socket = io("ws://localhost:3000");
socket.on("connect", () => {
console.log(socket.connected); // true
});
},[])

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 });
}
});
***

New connection cause the current connections to stop working

I am making the chat application using socket (which I'm new at) with multiple tenants structure and using namespaces. Here's my code:
Socket server:
index.js
class Server {
constructor() {
this.port = process.env.PORT || 3000;
this.host = process.env.HOST || `localhost`;
this.app = express();
this.http = http.Server(this.app);
this.rootSocket = socketio(this.http);
}
run() {
new socketEvents(this.rootSocket).socketConfig();
this.app.use(express.static(__dirname + '/uploads'));
this.http.listen(this.port, this.host, () => {
console.log(`Listening on ${this.host}:${this.port}`);
});
}
}
const app = new Server();
app.run();
socket.js
var redis = require('redis');
var redisConnection = {
host: process.env.REDIS_HOST,
password: process.env.REDIS_PASS
};
var sub = redis.createClient(redisConnection);
var pub = redis.createClient(redisConnection);
class Socket {
constructor(rootSocket) {
this.rootIo = rootSocket;
}
socketEvents() {
/**
* Subscribe redis channel
*/
sub.subscribe('visitorBehaviorApiResponse');
//TODO: subscribe channel..
// Listen to redis channel that published from api
sub.on('message', (channel, data) => {
data = JSON.parse(data);
console.log(data);
const io = this.rootIo.of(data.namespace);
if (channel === 'visitorBehaviorApiResponse') {
io.to(data.thread_id).emit('receiveBehavior', data);
io.to('staff_room').emit('incomingBehavior', data);
}
})
sub.on('error', function (error) {
console.log('ERROR ' + error)
})
var clients = 0;
this.rootIo.on('connection', (rootSocket) => {
clients++;
console.log('root:' + rootSocket.id + ' connected (total ' + clients + ' clients connected)');
const ns = rootSocket.handshake['query'].namespace;
// Dynamic namespace for multiple tenants
if (typeof (ns) === 'string') {
const splitedUrl = ns.split("/");
const namespace = splitedUrl[splitedUrl.length - 1];
const nsio = this.rootIo.of(namespace);
this.io = nsio;
this.io.once('connection', (socket) => {
var visitors = [];
console.log('new ' + socket.id + ' connected');
// once a client has connected, we expect to get a ping from them saying what room they want to join
socket.on('createChatRoom', function (data) {
socket.join(data.thread_id);
if (typeof data.is_staff !== 'undefined' && data.is_staff == 1) {
socket.join('staff_room');
} else {
if (visitors.some(e => e.visitor_id === data.visitor_id)) {
visitors.forEach(function (visitor) {
if (visitor.visitor_id === data.visitor_id) {
visitor.socket_ids.push(socket.id);
}
})
} else {
data.socket_ids = [];
data.socket_ids.push(socket.id);
visitors.push(data);
}
socket.join('visitor_room');
}
//TODO: push to redis to check conversation type
});
socket.on('sendMessage', function (data) {
console.log(data);
pub.publish('chatMessage', JSON.stringify(data));
this.io.in(data.thread_id).emit('receiveMessage', data);
this.io.in('staff_room').emit('incomingMessage', data);
// Notify new message in room
data.notify_type = 'default';
socket.to(data.thread_id).emit('receiveNotify', data);
}.bind(this))
socket.on('disconnect', (reason) => {
sub.quit();
console.log('client ' + socket.id + ' left, ' + reason);
});
socket.on('error', (error) => {
console.log(error);
});
});
}
// Root disconnect
rootSocket.on('disconnect', function () {
clients--;
console.log('root:' + rootSocket.id + ' disconnected (total ' + clients + ' clients connected)');
});
});
}
socketConfig() {
this.socketEvents();
}
}
module.exports = Socket;
Client:
const server = 'https://socket-server'
const connect = function (namespace) {
return io.connect(namespace, {
query: 'namespace=' + namespace,
resource: 'socket.io',
transports: ['websocket'],
upgrade: false
})
}
const url_string = window.location.href
const url = new URL(url_string)
const parameters = Object.fromEntries(url.searchParams)
const socket = connect(`${server}/${parameters.shopify_domain}`)
var handleErrors = (err) => {
console.error(err);
}
socket.on('connect_error', err => handleErrors(err))
socket.on('connect_failed', err => handleErrors(err))
socket.on('disconnect', err => handleErrors(err))
The problem that I met is when socket server got a new connection, the existing connections will be stopped working util they make a page refreshing to reconnect a new socket.id.
And when a namespace's client emit data, it sends to other namespaces, seem my code is not work correctly in a namespace.
Could you take a look at my code and point me where I'm wrong?
Thanks
1) Get UserId or accessToken while handshaking(in case of accessToken decrypt it).
and store userID: socketId(in Redis or in local hashmap) depends upon the requirement .
2) When u are going to emit to particular user fetch the socketid to that userid from redis or local hashmap
and emit to it.
**io.to(socketId).emit('hey', 'I just met you');**
3) If you are using multiple servers use sticky sessions
4) Hope this will help you

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())

NodeJS + WS access currently running WS server instance

I have implemented a simple REST API using NodeJS, ExpressJS and routing-controllers. I have also implemented a basic WebSocket server running alongside the REST API and using WS.
const app = express();
app.use(bodyParser.json({limit: "50mb"}));
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));
useExpressServer(app, {
controllers: [
UserController
]
});
const server = app.listen(21443, (err: Error) => {
console.log("listening on port 21443");
});
const wss = new WebSocket.Server({server});
wss.on("connection", (ws: WebSocket) => {
ws.on("message", (message: string) => {
console.log("received: %s", message);
ws.send(`Hello, you sent -> ${message}`);
});
ws.send("Hi there, I am a WebSocket server");
});
My question is how to I get access to the currently running WS instance so that I am able to send or broadcast from my controller methods. I have a number of POST methods that run long processes and so return a HTTP 200 to the client, I then would like to either send or broadcast to all connected WS clients.
What is the correct way to access the WebSocket.Server instance from within my controller classes?
You can create the websocket earlier and pass the instance around:
const notifier = new NotifierService();
notifier.connect(http.createServer(app));
app.get("/somethingHappened", () => {
notifier.broadcast("new notification!!");
});
app.use(routes(notifier))
Full code:
app.js
Pass the websocket to the other routes:
const express = require("express");
const http = require("http");
const NotifierService = require("../server/NotifierService.js");
const routes = require("./routes");
const app = express();
const server = http.createServer(app);
const notifier = new NotifierService();
notifier.connect(server);
app.get("/somethingHappened", () => {
notifier.broadcast("new notification!!");
});
// to demonstrate how the notifier instance can be
// passed around to different routes
app.use(routes(notifier));
server
.listen(4000)
.on("listening", () =>
console.log("info", `HTTP server listening on port 4000`)
);
NotifierService.js class that handles the websocket
const url = require("url");
const { Server } = require("ws");
class NotifierService {
constructor() {
this.connections = new Map();
}
connect(server) {
this.server = new Server({ noServer: true });
this.interval = setInterval(this.checkAll.bind(this), 10000);
this.server.on("close", this.close.bind(this));
this.server.on("connection", this.add.bind(this));
server.on("upgrade", (request, socket, head) => {
console.log("ws upgrade");
const id = url.parse(request.url, true).query.storeId;
if (id) {
this.server.handleUpgrade(request, socket, head, (ws) =>
this.server.emit("connection", id, ws)
);
} else {
socket.destroy();
}
});
}
add(id, socket) {
console.log("ws add");
socket.isAlive = true;
socket.on("pong", () => (socket.isAlive = true));
socket.on("close", this.remove.bind(this, id));
this.connections.set(id, socket);
}
send(id, message) {
console.log("ws sending message");
const connection = this.connections.get(id);
connection.send(JSON.stringify(message));
}
broadcast(message) {
console.log("ws broadcast");
this.connections.forEach((connection) =>
connection.send(JSON.stringify(message))
);
}
isAlive(id) {
return !!this.connections.get(id);
}
checkAll() {
this.connections.forEach((connection) => {
if (!connection.isAlive) {
return connection.terminate();
}
connection.isAlive = false;
connection.ping("");
});
}
remove(id) {
this.connections.delete(id);
}
close() {
clearInterval(this.interval);
}
}
module.exports = NotifierService;
routes.js
const express = require("express");
const router = express.Router();
module.exports = (webSocketNotifier) => {
router.post("/newPurchase/:id", (req, res, next) => {
webSocketNotifier.send(req.params.id, "purchase made");
res.status(200).send();
});
return router;
};
List of connected clients are stored inside wss object. You can receive and loop through them like this:
wss.clients.forEach((client) => {
if (client.userId === current_user_id && client.readyState === WebSocket.OPEN) {
// this is the socket of your current user
}
})
Now you need to somehow identify your client. You can do it by assigning some id to this client on connection:
wss.on('connection', async (ws, req) => {
// req.url is the url that user connected with
// use a query parameter on connection, or an authorization token by which you can identify the user
// so your connection url will look like
// http://example.com/socket?token=your_token
ws.userId = your_user_identifier
....
})
To broadcast use:
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(data);
}
});
If your controller and socket will be in different files (and I am sure they will), you will have to export the wss object in your socket file and import it in controller.

Resources