Node.js +Vue App +Websockets, updating list of connected elements - node.js

I'm trying to make this app based on node+socket.io+Vue.js.
Referring to the back end I set the server connected to socket io and all referring to the different emitters :
Class Server
const express = require("express");
const cors = require("cors");
const corsObject = {
origin: ["http://localhost:8100"],
methods: ["GET", "POST", "DELETE", "PUT", "PATCH"],
allowedHeaders: ["Content-Type", "Authorization","token-response"],
credentials: true,
};
const { conectToMongo } = require("../database/database");
const {
socketController,
} = require("../sockets-controllers/socket-controller");
class Server {
constructor() {
this.allowedOrigins = "http://localhost:8100";
this.app = express();
this.port = process.env.PORT; arranque
this.connectingdatabase();
this.server = require("http").createServer(this.app);
this.io = require("socket.io")(this.server, {
cors: corsObject,
});
this.connectionSocketClient();
this.middlewares();
this.routes();
}
middlewares() {
this.app.use(express.json()); //lectura y parsoin del body a fomato json
this.app.use(express.static("public")); //directorio fornt
this.app.use(cors({ credentials: true, origin: "http://localhost:8100" })); //cors
this.app.get("/", (request, response, next) => {
response.status(200).json({
ok: true,
message: "request correct",
});
});
}
routes() {
this.app.use("/user", require("../routes/user-routes"));
this.app.use("/auth", require("../routes/user-login"));
// this.app.use(this.usuariosPath,require('../routes/user-routes'))
}
async connectingdatabase() {
await conectToMongo();
}
connectionSocketClient() {
this.io.on("connection",(socket)=> socketController(socket,this.io));
}
portListener() {
this.server.listen(this.port, () => {
console.log(`server running on port : ${this.port}`);
});
}
}
module.exports = Server; //Exportando la clase interface creada para su uso en los demas modulos
In order to initialize the controllers i first set a class which might be used on controllers in a easier way importing its instance
class Message {
constructor(userId, userMessage, message) {
this.userId = userId;
this.userMessage = userMessage;
this.message = message;
}
} //Class and its constructor Message
class ChatMessage {
constructor() {
this.messages = [];
this.usersOnConnection = {};
}
//constructor of this class with two components , first a array of message
//and second a object that would be stacking the connected users
get usersConnected() {
return Object.values(this.usersOnConnection);
}
//This getter would retrieve the object of users connected converting it to
//an array
addUserToChat(user) {
this.usersOnConnection[user.id] = user
}
//this method would add a user to a chat i this case adding the new consumer
//to the object of users connected usersOnConnection(HERE problem too)
}
//Class and its constructor Message
module.exports = { Message, ChatMessage };
Then the socket controller on charge of set the logic would be like this
const { Socket } = require("socket.io");
const { ChatMessage, Message } = require("../models/chat-model");//Interfaces imported
const socketController = async (socket = new Socket(), io) => {
const user = await jwtValidatorRenew(
socket.handshake.headers["token-response"]
);
if (!user || user == undefined) {
console.log("user disconnected");
return socket.disconnect();
}
//if for some reason the user requested thorugh a token i retrieve is null
//or some like , the controller ends here with a disconnection to the socket
//else
const chatMessage =await new ChatMessage(); //creating new isntance of class ChatMessage
//first i do create a new instance of the class Chatmessage in order to access from here
chatMessage.addUserToChat(user); //adding user to the chat (HERE )
//then any time the browser is recharged or user inits session , this user
//would be added, using the method addUserToChat from the class instance
io.emit("active-users", chatMessage.usersConnected); //sending users connected
//once the user is added i do proceed to emit through io , the state of all connected
//consumers aiming to the flag "active-users", accesing the getter usersConnected
//from the instance
socket.on("disconnect", () => {
console.log("Client DisconectedConected", socket.id); // desconexion
chatMessage.disconnectUserFromChat(user.id);
//diconnecting user
io.emit("active-users", chatMessage.usersConnected);
});
//On disconnection the instance of chatmessage class is called in order to disconnect
//user according to its id. Also accession io from socket I do emit for all consumers
//a new state under the flag "active-users", passing ass method the getter usersConnected
//from the instance chatMessage(but only receive one user)
};
module.exports = { socketController };
Then on my front after setting socket io client i set this on my Vue state manangement(vuex ) for the socket connection, being this action dispatched any time i need.
ACTIONS VUEX
conectingSocket() {
const socket= io("localhost:3006", {
extraHeaders: {
"token-response": localStorage.getItem("token"),
},
});
socket.on("connect",()=>{
console.log("socket online");
})
//flag on connection
socket.on("disconnect",()=>{
console.log("socket offline");
})
//flag on discconnection
socket.on("active-users",(usersPayload)=>{
console.log(usersPayload);
})
//Here i log the users connected updated once the back emit on this flag a action . But always
//brings me the user of the browser that I recharge not updating the other ones
},
Then any time i initialize my app this method on front is triggered in order to retrieve the users
connected, thus for that simply call the method and set it also in my mounted Vue life cycle:
export default {
name: "AllUsersComponent",
components: { IonCard, IonContent, IonItem, IonInput, IonButton, IonLabel },
// components: { IonLabel, IonInput, IonItem },
data() {
return {
allUsersFinal: [],
message: "",
state: false,
socket: io(process.env.VUE_APP_BACK_URL),
//client socket and its connection
};
},
methods: {
...mapActions(["getAllUsers", "validateToken", "conectingSocket"]),
socketConection() {
this.$store.dispatch("conectingSocket");
},
...some methods
},
},
computed: {
...mapGetters(["getterGetAllUsers"]),
...some computed methods
},
mounted() {
this.socketConection()
},
created() {
...some methods
},
};
</script>
But keeps showing me only the user of the browser I do recharge. For this purpose i initialize on the app two users from different browsers(Firefox and Chrome)
Any help on this would be amazing!!!!!

Related

How to send real time notification with socket.io in express

I want to send a real-time notification to the owner of the post when the post is liked. But, I don't really have an idea how to implement it into my React app and make it work. My notification function on the server side looks like this;
const Notification = require("../models/NotificationModel.js");
const { Server } = require("socket.io");
const io = new Server({
cors: "clientURL",
});
const sendNotification = async ({
sender,
receiver,
message,
project,
comment,
challenge,
}) => {
io.on("connection", (socket) => {
socket.on("sendNotification", ({sender, receiver, message}) => {
io.to(receiver.socketId).emit("getNotification", {
sender,
message
})
});
});
await Notification.create({
message,
sender,
receiver,
project,
comment,
challenge,
});
};
module.exports = sendNotification;
According to the OpenGPT client-side should look like this;
import React, { useEffect } from 'react';
import io from 'socket.io-client';
const socket = io('serverURL');
const MyComponent = () => {
useEffect(() => {
socket.on("getNotification", (data) => {
// Handle the notification with the data from the server
});
}, []);
return (
// Render your component
);
};
Could you please point out all the wrongs in this snippet and provide the correct way to make these actions in sequence;
user likes a post -> server sends a notification to the owner of the post

Socket IO Client not receiving events on reconnection

I have a file called socket_io.js where I created a single instance of a socket io client in my react app as shown below:
socket_io.js
import EndPoints from './http/endpoints';
import io from "socket.io-client";
const socketUrl = EndPoints.SOCKET_BASE;
let socketOptions = { transports: ["websocket"] }
let socket;
if (!socket) {
socket = io(socketUrl, socketOptions);
socket.on('connect', () => {
console.log(`Connected to Server`);
})
socket.on('disconnect', () => {
console.log(`Disconnected from Server`);
})
}
export default socket;
Then I imported the above singleton in many react components as shown below.
MessagePage.js
import socket from '../socket_io.js';
let messageHandler=(data)=>{
}
useEffect(()=>{
socket.on('message',messageHandler); //This event no longer fires When the singleton socket io instance is reconnected
return ()=>{
socket.off('message');
}
},[]);
which works well but the issue I'm facing now is that when the singleton instance reconnects, the components referencing it are no longer receiving events from their respective handlers.
Possible causes of reconnection are when I manually restart the server
How can this be resolved?
I just solved this after working on it for a project of my own. My method involves two parts: creating the socket in a useEffect hook and then managing it using useRef for reconnection situations.
In Summary:
I think there are two issues. One is that the socket is being initialized as a singleton and not using a hook/context. I've read other reports of strangeness in this case, so I suggest switching to using context and creating your socket in a hook. Secondly, we have to manually store reconnection logic (although by generating the socket properly, it seems as though the actual event listeners are kept through reconnect).
export const SocketContext = createContext();
export const SocketContextProvider = ({ children }) => {
const [socket, setSocket] = useState();
const reconnectEmits = useRef([]);
// Here's your basic socket init.
useEffect(()=>{
const newSocket = io(url);
setSocket(newSocket);
return () => {
newSocket.close();
}
}, []);
// Code used to rejoin rooms, etc., on reconnect.
newSocket.io.on('reconnect', e => {
console.log("it took " + e + " tries to reconnect.");
for (let action of reconnectEmits.current) {
newSocket.emit(action.event, action.data);
}
})
// Here I also define a setListener and removeListener function, which determine which listeners a socket listens to. I don't have the code in front of me now, but it's pretty simple:
const addListener = (event, function) => {
// I use socket.off(event) right here to make sure I only have one listener per event, but you may not want this. If you don't use it you will need to make sure you use hooks to remove the event listeners that your components add to your socket when they are removed from the DOM.
socket.on(event, function);
}
// I implement an emit function here that's a wrapper, but I'm not sure if it's necessary. You could just expose the socket itself in the context. I just choose not to.
return (
<SocketContext.Provider value={{ emit, setListener, removeListener, addReconnectEmit, removeReconnectEmit }}>
{children}
</SocketContext.Provider>
)
}
And then in my components, in addition to having the emits to join rooms or conduct actions, I also provide the add and remove ReconnectEmit functions:
const addReconnectEmit = (event, data) => {
reconnectEmits.current = ([...reconnectEmits.current, { event, data }]);
console.log(reconnectEmits.current);
}
const removeReconnectEmit = (event, data) => {
console.log('removing reconnect event');
reconnectEmits.current = reconnectEmits.current.filter(e =>
{ return e.event !== event && e.data !== data }
);
console.log(reconnectEmits.current);
};
With these, I can set it so that, after a reconnect, my socket knows to reconnect to a certain room, etc. See here:
const Chatroom = ({ convoId }) => {
console.log("RENDERED: Chatroom");
const { emit, addReconnectEmit, removeReconnectEmit } = useContext(SocketContext);
useEffect(() => {
emit('joinConvo', convoId);
console.log("Emitting joinConvo message.");
addReconnectEmit('joinConvo', convoId);
return () => {
emit('leaveConvo', convoId);
removeReconnectEmit('leaveConvo', convoId);
}
}, [convoId, emit, addReconnectEmit, removeReconnectEmit]);
return (
<div id="chatroom">
<ChatroomOutput />
<ChatroomStatus />
<ChatroomControls convoId={convoId} />
</div>
);
}
I hope that helps! Between useEffect and manual reconnection logic, I just fixed similar issues to the ones you were having, where I was losing data on reconnection.
Saw you just answered yourself but my approach might still be valuable for others or if you continue to build a socket-client.
You need to abstract the listening components away from the socket object. The socket object upon onMessage needs to retrieve the subscribers and publish the new message to them. You can of course add filtering based on id, type or other properties. Also each component can drop its subscription when un-mounting or based on another need.
In order to show case I used timers but would be easily converted to messages.
socket_io.js
let socket;
const subscribers = []
if (!socket) {
// socket initial connect
socket = true
setInterval(() => {
console.log('interval runs', { socket })
if (socket) {
subscribers.forEach((sub) => {
sub.onMessage()
})
}
}, 1000)
setTimeout(() => {
// socket disconnects
socket = false
setTimeout(() => {
// socket reconnects
socket = true
}, 4000)
}, 4000)
}
export default subscribers;
MessagePage.js
import React, { useEffect, useState } from 'react'
import subscribers from './socket_io.js'
const MessagePage = () => {
const [messageCount, setMessageCount] = useState(0)
let messageHandler = (data) => {
setMessageCount((current) => current + 1)
}
useEffect(() => {
subscribers.push({
id: '1',
onMessage: (data) => messageHandler(data)
})
return () => {
const subToRemove = subscribers.findIndex((sub) => sub.id === '1')
subscribers.splice(subToRemove, 1)
}
}, []);
return (
<div>
Messages received: {messageCount}
</div>
)
}
export default MessagePage
Hope I could help.
export default expects a Hoistable Declarative , i.e function,express
socket_oi.js
import EndPoints from './http/endpoints';
import io from "socket.io-client";
const socketUrl = EndPoints.SOCKET_BASE;
let socketOptions = { transports: ["websocket"] }
let socket;
class Socket {
constructor (){
if (!socket) {
socket = io(socketUrl, socketOptions);
socket.on('connect', () => {
console.log(`Connected to Server`);
})
socket.on('disconnect', () => {
console.log(`Disconnected from Server`);
})
}
socket = this
}
}
//Freeze the object , to avoid modification by other functions/modules
let newSocketInstance = Object.freeze(new Socket)
module.exports = newSocketInstance;
MessagePage.js
import socket from '../socket_io.js';
const MessagePage = (props){
const messageHandler=(data)=>{
}
useEffect(()=>{
socket.on('message',messageHandler); //This event no longer fires When the
singleton socket io instance is reconnected
return ()=>{
socket.off('message');
}
},[]);
}

Angular 5 socket.io not recieving update in other component

Context: I have an angular application with a backend in nodejs. I have a feed that will update when I recieve a message from the server. When new data is inserted the server is notified, but my other component does not recieve anything. I have implemented the socket in a service that is injected into both components.
My server is build like this:
const port = 3000;
const server = require('http').Server(app);
const io = require('socket.io')(server);
io.on('connection', (socket) => {
console.log('New Connection..')
socket.on('action', (data) => {
switch(data) {
case 'new_odds':
socket.emit('refresh_odds', 'UPDATE FEED! (FROM SERVER)')
break;
case 'new_results':
break;
}
});
});
//listen on port omitted
My service in angular:
const SERVER_URL = 'http://localhost:3000';
#Injectable()
export class SocketService {
constructor() {
}
private socket;
public initSocket(): void {
this.socket = socketIo(SERVER_URL);
}
public disconnectSocket(): void {
this.socket.disconnect();
}
public send(action: Action): void {
this.socket.emit('action', action);
}
public onOddsMessage(): Observable<string> {
return new Observable<string>(observer => {
this.socket.on('refresh_odds', (data:string) => {
observer.next(data)
});
});
}
public onEvent(event: Event): Observable<any> {
return new Observable<Event>(observer => {
this.socket.on(event, () => observer.next());
});
}
}
My feed component uses the socket service to listen for emits:
constructor(private _socket : SocketService) {
}
ngOnInit() {
this.initIoConnection();
}
private initIoConnection(): void {
this._socket.initSocket();
this.ioConnection = this._socket.onOddsMessage()
.subscribe((data: string) => {
console.log('Recieved data from oddsMessage')
//this.loadBetFeed();
});
}
In a different component also using the service I'm trying to emit to the socket on the server. It does recieve the message on the server and emit a new message but my feed component does NOT pick up on this
testSocket() {
//NOTIFY SERVER THAT IT SHOULD TELL CLIENTS TO REFRESH
console.log('Test Socket Clicked')
this._socket.initSocket();
this._socket.send(Action.ODDS);
}
I don't understand what I'm doing wrong - I am using a shared service. Even if the components use different socket connections it shouldn't matter since they're listening for the same emits? I've tested in 2 browser tabs and also in incognito. Any help is appreciated!
The issue was I was emitting to the socket instead of the server which means only the current connection could see it.
New server:
io.on('connect', (socket) => {
console.log('Connected client on port %s.', port);
socket.on('action', (data) => {
io.emit('refresh_odds', 'UPDATE FEED! (FROM SERVER)') <-- changed socket to io
});
}

socket io on sails js as API and node+react as Frontend

I have an API build using sailsjs and a react redux attach to a nodejs backend, and i am trying to implement socket.io for a realtime communication, how does this work?
is it
socket.io client on the react side that connects to a socket.io server on its nodejs backend that connects to a socket.io server on the API
socket.io client on the react side and on its nodejs backend that connects to a socket.io server on the API
i have tried looking around for some answers, but none seems to meet my requirements.
to try things out, i put the hello endpoint on my API, using the sailsjs realtime documentation, but when i do a sails lift i got this error Could not fetch session, since connecting socket has no cookie (is this a cross-origin socket?) i figure that i need to pass an auth code inside the request headers Authorization property.
Assuming i went for my #1 question, and by using redux-socket.io,
In my redux middleware i created a socketMiddleware
import createSocketIoMiddleware from 'redux-socket.io'
import io from 'socket.io-client'
import config from '../../../config'
const socket = io(config.host)
export default function socketMiddleware() {
return createSocketIoMiddleware(
socket,
() => next => (action) => {
const { nextAction, shuttle, ...rest } = action
if (!shuttle) {
return next(action)
}
const { socket_url: shuttleUrl = '' } = config
const apiParams = {
data: shuttle,
shuttleUrl,
}
const nextParams = {
...rest,
promise: api => api.post(apiParams),
nextAction,
}
return next(nextParams)
},
)
}
and in my redux store
import { createStore, applyMiddleware, compose } from 'redux'
import createSocketIoMiddleware from 'redux-socket.io'
...
import rootReducers from '../reducer'
import socketMiddleware from '../middleware/socketMiddleware'
import promiseMiddleware from '../middleware/promiseMiddleware'
...
import config from '../../../config'
export default function configStore(initialState) {
const socket = socketMiddleware()
...
const promise = promiseMiddleware(new ApiCall())
const middleware = [
applyMiddleware(socket),
...
applyMiddleware(promise),
]
if (config.env !== 'production') {
middleware.push(DevTools.instrument())
}
const createStoreWithMiddleware = compose(...middleware)
const store = createStoreWithMiddleware(createStore)(rootReducers, initialState)
...
return store
}
in my promiseMiddleware
export default function promiseMiddleware(api) {
return () => next => (action) => {
const { nextAction, promise, type, ...rest } = action
if (!promise) {
return next(action)
}
const [REQUEST, SUCCESS, FAILURE] = type
next({ ...rest, type: REQUEST })
function success(res) {
next({ ...rest, payload: res, type: SUCCESS })
if (nextAction) {
nextAction(res)
}
}
function error(err) {
next({ ...rest, payload: err, type: FAILURE })
if (nextAction) {
nextAction({}, err)
}
}
return promise(api)
.then(success, error)
.catch((err) => {
console.error('ERROR ON THE MIDDLEWARE: ', REQUEST, err) // eslint-disable-line no-console
next({ ...rest, payload: err, type: FAILURE })
})
}
}
my ApiCall
/* eslint-disable camelcase */
import superagent from 'superagent'
...
const methods = ['get', 'post', 'put', 'patch', 'del']
export default class ApiCall {
constructor() {
methods.forEach(method =>
this[method] = ({ params, data, shuttleUrl, savePath, mediaType, files } = {}) =>
new Promise((resolve, reject) => {
const request = superagent[method](shuttleUrl)
if (params) {
request.query(params)
}
...
if (data) {
request.send(data)
}
request.end((err, { body } = {}) => err ? reject(body || err) : resolve(body))
},
))
}
}
All this relation between the middlewares and the store works well on regular http api call. My question is, am i on the right path? if i am, then what should i write on this reactjs server part to communicate with the api socket? should i also use socket.io-client?
You need to add sails.io.js at your node server. Sails socket behavior it's quite tricky. Since, it's not using on method to listen the event.
Create sails endpoint which handle socket request. The documentation is here. The documentation is such a pain in the ass, but please bear with it.
On your node server. You can use it like
import socketIOClient from 'socket.io-client'
import sailsIOClient from 'sails.io.js'
const ioClient = sailsIOClient(socketIOClient)
ioClient.sails.url = "YOUR SOCKET SERVER URL"
ioClient.socket.get("SAILS ENDPOINT WHICH HANDLE SOCKET", function(data) {
console.log('Socket Data', data);
})

socket.io-client + react, where to connect to the server?

I'm writing a very simple Nodejs app. I use React + Socket.io.
There's a root element which immediately renders another react component (you may wonder why I have this root element. The reason is that I want to be able to mount one of the two components after receiving a message from server, but for the start I render a preselected component).
In this root component , I define a socket in componentDidMount . Now the problem is that I want to pass this socket to all of the children (so they can listen and communicate with the server messages.) But if I connect to the server in componentDidMount of the root, during the rendering there is no socket as it's not connected yet and null will be passed to the child components.
'use strict';
var React = require('react');
var ioClient = require('socket.io-client');
var UsersList = require('./usersList');
var Game = require('./game');
var socket;
var Snake = React.createClass({
displayName: 'Snake',
propTypes: {},
getDefaultProps: function() {
return {};
},
mixins: [],
getInitialState: function() {
return ({
usersList: true,
game: false
});
},
componentWillMount: function() {
},
componentWillUnmount: function() {
this.socket.close();
},
componentDidMount: function() {
socket = ioClient.connect(); // this happens after render
},
render: function() {
var result = null;
if (this.state.usersList) {
result = <UsersList socket={socket}/> // therefore this one is passed as null
} else { //game : true
result = <Game socket={socket}/>
}
return (<div>
{result}
</div>)
}
});
module.exports = Snake;
'use strict';
var React = require('react');
var UsersList = React.createClass({
displayName: 'UsersList',
propTypes: {},
getDefaultProps: function() {
return {};
},
mixins: [],
getInitialState: function() {
return ({
usersList:[]
});
},
componentWillReceiveProps: function(){
},
componentWillMount: function() {
},
componentWillUnmount: function() {
},
componentDidMount: function(){
var socket = this.props.socket; // this one was passed into the component as null
socket.on('usersList', function(data){ // so this one returns an error
this.setState({
usersList: data.usersList
});
});
},
render: function() {
var users = [];
for (var i = 0 ; i < this.state.usersList.length ; i++){
users.push(<span>{this.state.usersList[i]}</span>);
}
return(<div>{users}</div>);
}
});
module.exports = UsersList;
So , now you may ask why I don't put io.connect() in componentWillMount or at the top of the file. Well , it doesn't work ! it returns this error : Cannot find property "protocol" ....
I cannot put it in render , componentWillMount , top of the file ...
Any idea on how to do this ?
You could continue to connect in componentDidMount. It will not be immediately available to the component's children, but then you could do something like this in the children:
componentDidUpdate(prevProps, prevState) {
if ( this.props.socket ) {
// do your connection logic here
}
}
This will ensure that the children immediately connect when the socket is first connected and available to them. Inside the if statement you could also verify that this.props.socket is not equal to prevProps.socket to prevent a redundant connection attempt.

Resources