WebSocket in Node routes - node.js

I have a REST API backend. I'm trying to send WebSocket messages back to the client app when a site administrator invokes a route (i.e. updates user credentials);
let express = require("express");
let router = express.Router();
//update user credentials
router.put('/admin/user/:id', (req, res)=>{
// 1. Admin updates target user's profile/credentials
// 2. WebSocket message sent to target user client
// 3. Route sends res.status(200) to admin
});
I've seen a few WebSocket examples using 'ws', 'net', 'websocket' libraries, but they all show a simple event handling socket server that responds to socket messages outside of any express routes - let alone responds to a separate client.
Also, the event notification should be visible only to the target user and not all the other users connected to the socket server.

Figured it out. The WebSocket server is independent of the route.
const WebSocket = require("ws");
const wss = new WebSocket.Server({port: 5555});
// handle socket communications
wss.on('connection', (session)=> {
session.send("HELLO SESSION: "+ session.userid);
session.on('message', (message)=> {
console.log(`MSG: "${message}" recived.`);
session.send("Got it.");
});
});
// close
wss.on('close', function close() {
clearInterval(interval);
});
module.exports = wss;
Then, in the route, just include the service and use it to iterate through web socket connections like so:
let wss = require('./mysocketservice')
...
function sendAMessageTo(user, msg) {
wss.clients.forEach(function each(s) {
if(session.user = user)
session.send("still there?.. ");
}
}

Related

Node express + websockets with REST route to send/receive websocket messages

Using node, I'm trying to create a server in express with a route for websockets and a REST route to send and receive websocket messages. Basically, it should work like this:
A client connects to the server via http://localhost:3000/client.
The server returns a JS script
The script sets a cookie and establishes a websocket connection back to the server via ws://localhost:3000/websocket
The script is also set to reply to websocket messages with document.cookie
The websocket connection assigns a UID to the client
A GET request is made by a different system to http://localhost:3000/api?uid= (clientUID = someUID in this example)
The server loops through all websocket clients to find the correct client/UID
A message is sent to the client
The client responds with document.cookie
The server responds to the GET request with the message returned to the websocket server
The last part is what isn't working. How can I capture the message returned to the websocket server and send it as a response to requests to the /api route?
const express = require('express');
const WebSocketServer = require('ws');
const http = require("http");
const app = express();
const server = http.createServer(app);
const port = 3000;
server.listen(port);
const wss = new WebSocketServer.Server({ server: server, path: '/websocket' });
// Each new websocket will be given a UID. This example uses a static UID of
// `someUID` for convenience
wss.on("connection", function connection(ws, req) {
ws.uid = 'someUID';
ws.on("message", data => {
// I want to send this data as a response to calls to `/api`
console.log(`${data}`);
});
});
// This route sends a message to the client specified by the `uid` URL param.
// The client will respond (above) with the value of `document.cookie`
app.get('/api', (req, res) => {
wss.clients.forEach(function (client) {
if (client.uid == req.query.uid) {
// I want to send the response of this message via `res.send()`
client.send();
}
});
});
// Set up a client with a cookie and establish a websocket connection. When the
// client receives a message, it should return document.cookie.
app.get('/client', (req, res) => {
res.send(' \
<script> \
document.cookie = "cookieName=cookieValue;"; \
let url = "ws://localhost:3000/websocket"; \
let connection = new WebSocket(url); \
connection.onmessage = (e) => { \
connection.send(document.cookie); \
} \
</script> \
')
});

How to prevent people from connecting to any id with sockeIO?

I just set up SocketIO in my PHP project. I am completly new to websockets at all so bear with me.
I am defining the socketIO variable globally
let socketIO = io("http://localhost:3000");
When people are logging in to my application, they are connected to it with their ID comming from the database. The login script just gives back true which redirects the user in very simplified terms:
// get component
$.get(url, data, (data) => {
if (data.status) {
// connect with Node JS server
socketIO.emit("connected", data.user_id);
// redirect
load_new_page("/users/" + data.user_id);
}
});
My concern here now is that people could just go and change the data.user_id to anything they want and receive what ever the chosen id would receive.
My server.js:
// initialize express server
var express = require("express");
var app = express();
// create http server from express instance
var http = require("http").createServer(app);
// include socket IO
var socketIO = require("socket.io")(http, {
cors: {
origin: ["http://localhost"],
},
});
// start the HTTP server at port 3000
http.listen(process.env.PORT || 3000, function () {
console.log("Server started running...");
// an array to save all connected users IDs
var users = [];
// called when the io() is called from client
socketIO.on("connection", function (socket) {
// called manually from client to connect the user with server
socket.on("connected", function (id) {
users[id] = socket.id;
});
});
});
How can I prevent something like this?

Socket io as a service with Express REST API

I have a REST api using express server and I want to receive notifications from certain routes using Socket.io as a service.
Can I emit from withing an express route on a already running express server
and have a client listening to my api on it's root to log notifications?
What I'm trying to do:
in my route controller
exports.authenticate = (req, res) => {
... on success:
socket = io()
socket.emit('logged in', "new user logged in")
}
my socket io client:
let socket = io('localhost:3000', {cors: { origin: '*' }})
socket.on('logged in', () => {
console.log("login notification")
})
I'm running my api alone, and then I'm running my client script using node client.
but I'm not receiving anything

SocketIO send message to client via API route

I have a route in my express API where I want to emit messages using a websocket to a client. In this case, the client is another Node.js app. In this Node.js app, I try to connect to the socket and print messages received. Both the API and the Node app are on different ports. Can someone help me make this work?
Here's how I pass my socket to my express routes:
const server = app.listen(PORT, () => {
console.log(`Server on port ${PORT}`);
});
const io = require("socket.io")(server);
app.set("socketio", io);
Here's my REST API route:
exports.getAll = function(req,res){
var io = req.app.get('socketio');
io.emit('hi!');
}
Here's my socket io client, it uses socket.io-client
const socket = io('http://localhost:3000');
socket.on("message", data => {
console.log(data);
});
Unfortunately, I don't receive the 'hi' message from my API.
When I call /api/getAll I don't receive the message in my client app.
When emitting an event via socket.io you have you define the event name before the data.
Example:
exports.getAll = function(req, res){
var io = req.app.get("socketio");
io.emit("message", "hi!");
}
Now you'll be able to receive the message event from the client.
Reference:
https://socket.io/docs/v4/emitting-events/

Trigger `socket.io` event from server(`node.js`), not from client

I am trying to make a game server with node.js, socket.io.
The basic idea likes below.
Initialize socket.io instance when the server starts
Store instance in global scope, so controllers can access it
When API calls, we trigger some socket.io event in the controller or some other points
Here is the implementation I made ...
First, in server.js - entry point
let GlobalVars = require('./state/GlobalVars');
const apiRouters = require('./router');
...
app.use('/api', apiRouters);
app.get('/', (req, res) => {
res.sendFile(`${__dirname}/test/simpleClient.html`)
});
const httpServer = http.createServer(app);
let socketIOInstance = socketIO(httpServer);
socketIOInstance.on('connection', (socket) => {
console.log('SOCKET.IO A USER CONNECTED');
socket.on('create', (data) => {
console.log('SOCKET.IO create called', socket);
socket.join(data.room);
socketIOInstance.emit('message', 'New people joined');
});
socket.on('join', (data) => {
console.log('SOCKET.IO join called', data);
})
socket.emit('message', 'Hi');
});
GlobalVars.socketIO = socketIOInstance;
// Add to global, so the controllers can manage own actions like create, join ...
httpServer.listen(port, () => {
console.log(`Server Listening on the port ${port}`);
})
...
When I access from a client, I am able to see SOCKET.IO A USER CONNECTED and Hi in the browser console.
Second, In api controller.
let GlobalVars = require('../state/GlobalVars');
...
router.post('/create', (req, res) => {
console.log('GenerateGameSokect');
let game = new Game();
let gameId = game.gameId;
// console.log('Global vars ', GlobalVars.socketIO);
GlobalVars.socketIO.emit('create', {
room: gameId
});
res.json({
result : 'SUCCESS',
game : game
})
});
I imported GlobalVars which contains socketIO instance. So what I expected was, socket create event triggered from the statement GlobalVars.socketIO.emit('create', Object) but could not find message in the server logs.
I got no clue what I was missing.
The final form I pursue is something like...
When user call create API, I creates socket connection and room
API will called in HTTP protocol, but in the API, the server publishes some events. - pubsub like.
Thanks for reading my questions b. Here is full source code till now(bitbucket public)
================== EDIT ====================
I got understood (maybe...)
The user-flow I wanted was ...
The client call API
(In the server) Checking validation in API and if valid emit to socket.io
If event accepted send new status to all clients
However, creating socket.io connection in the server looks strange for me, the solution is up to the client.
New user-flow I will change
The client call a validation API
If return is valid, the client emit socket.io event. This time server only do validation, not emit socket.io
In socket event, send new status to all other users
================== EDIT #2 ====================
This is a kind of conclusion. It looks I just misunderstanding the concept of socket communication. Like answer and replies say, Socket and HTTP are totally different channels, there is no way to connect both. (At least, without open new connection from http server to socket)
If this is wrong, you could add reply, Thanks
Now I understand you. Or at least I think!
Let's put it this way: there are two (asymetric) sides on a socket, server and client. What I called, respectively, "global manager" and "socket" in my comment to your post.
const server = require('socket.io')(yourHttpServer);
// client is installed as well when `npm i socket.io`
const client = require('socket.io-client')('http://localhost:' + yourServerPort);
// `socket` is the server side of the socket
server.on('connection', (socket) => {
// this will be triggered by client sides emitting 'create'
socket.on('create', (data) => {
console.log('a client socket just fired a "create" event!');
});
});
// this will be triggered by server side emitting 'create'
client.on('create', (data) => {
server.emit('create', {content: 'this will result in an infinite loop of "create" events!'});
});
In your /create route, when you GlobalVars.socketIO.emit('create', ...), the server-side socket handler isn't triggered, however if you have clients connected through a browser (or, like I showed above, if you connect a client socket directly from the server) then these will trigger their 'create' listener, if any.
Hope this helps you get on the right tracks!

Resources