Simple Way to Implement Server Sent Events in Node.js? - javascript

I've looked around and it seems as if all the ways to implement SSEs in Node.js are through more complex code, but it seems like there should be an easier way to send and receive SSEs. Are there any APIs or modules that make this simpler?

Here is an express server that sends one Server-Sent Event (SSE) per second, counting down from 10 to 0:
const express = require('express')
const app = express()
app.use(express.static('public'))
app.get('/countdown', function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
})
countdown(res, 10)
})
function countdown(res, count) {
res.write("data: " + count + "\n\n")
if (count)
setTimeout(() => countdown(res, count-1), 1000)
else
res.end()
}
app.listen(3000, () => console.log('SSE app listening on port 3000!'))
Put the above code into a file (index.js) and run it: node index
Next, put the following HTML into a file (public/index.html):
<html>
<head>
<script>
if (!!window.EventSource) {
var source = new EventSource('/countdown')
source.addEventListener('message', function(e) {
document.getElementById('data').innerHTML = e.data
}, false)
source.addEventListener('open', function(e) {
document.getElementById('state').innerHTML = "Connected"
}, false)
source.addEventListener('error', function(e) {
const id_state = document.getElementById('state')
if (e.eventPhase == EventSource.CLOSED)
source.close()
if (e.target.readyState == EventSource.CLOSED) {
id_state.innerHTML = "Disconnected"
}
else if (e.target.readyState == EventSource.CONNECTING) {
id_state.innerHTML = "Connecting..."
}
}, false)
} else {
console.log("Your browser doesn't support SSE")
}
</script>
</head>
<body>
<h1>SSE: <span id="state"></span></h1>
<h3>Data: <span id="data"></span></h3>
</body>
</html>
In your browser, open localhost:3000 and watch the SSE countdown.

I'm adding a simple implementation of SSE here. It's just one Node.js file.
You can have a look at the result here: https://glossy-ox.glitch.me/
const http = require('http');
const port = process.env.PORT || 3000;
const server = http.createServer((req, res) => {
// Server-sent events endpoint
if (req.url === '/events') {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
...(req.httpVersionMajor === 1 && { 'Connection': 'keep-alive' })
});
const refreshRate = 1000; // in milliseconds
return setInterval(() => {
const id = Date.now();
const data = `Hello World ${id}`;
const message =
`retry: ${refreshRate}\nid:${id}\ndata: ${data}\n\n`;
res.write(message);
}, refreshRate);
}
// Client side
res.writeHead(200, {'Content-Type': 'text/html'});
res.end(`
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>SSE</title>
</head>
<body>
<pre id="log"></pre>
</body>
<script>
var eventSource = new EventSource('/events');
eventSource.onmessage = function(event) {
document.getElementById('log').innerHTML += event.data + '<br>';
};
</script>
</html>
`);
});
server.listen(port);
server.on('error', (err) => {
console.log(err);
process.exit(1);
});
server.on('listening', () => {
console.log(`Listening on port ${port}`);
});

If you're using express this is the easiest way https://www.npmjs.com/package/express-sse
on BE:
const SSE = require('express-sse');
const sse = new SSE();
...
app.get('/sse', sse.init);
...
sse.send('message', 'event-name');
on FE:
const EventSource = require('eventsource');
const es = new EventSource('http://localhost:3000/sse');
es.addEventListener('event-name', function (message) {
console.log('message:', message)
});

I found SSE implementation in node.js.
Github link: https://github.com/einaros/sse.js
NPM module:https://www.npmjs.com/package/sse
Will above link helps you ?

**client.js**
var eventSource = new EventSource("/route/events");
eventSource.addEventListner("ping", function(e){log(e.data)});
//if no events specified
eventSource.addEventListner("message", function(e){log(e.data)});
**server.js**
http.createServer((req, res)=>{
if(req.url.indexOf("/route/events")>=){
res.setHeader('Connection', 'keep-alive');
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Content-Type", "text/event-stream");
let event = "event: ping";
let id = `id: ${Date.now()}`;
let data = {
message:`hello #${new Date().toString()}`
}
data = "data: "+JSON.stringify(data);
res.end(`${event}\n${id}\n${data}\n\n`);
}
}).listen(PORT)

After looking at the other answers I finally got this working, but what I ended up having to do was a little different.
[package.json] Use express-sse:
The exact version of express-sse is very important. The latest tries to use res.flush(), but fails and crashes the http server.
"express-sse": "0.5.1",
[Terminal] Install express-sse:
npm install
[app.js] Use the router:
app.use(app.baseUri, require('./lib/server-sent-events').router);
[server-sent-events.js] Create sse library:
The call to pause() is the equivalent of flush(), which was removed from express. It ensures you'll keep getting messages as they are sent.
var express = require('express');
const SSE = require('express-sse');
const sse = new SSE();
var router = express.Router();
router.get('/sse', sse.init)
module.exports = {
send,
router
};
async function send(message) {
sse.send(message.toProperCase(), 'message');
await pause();
}
function pause() {
return new Promise((resolve, reject) => {
setImmediate(resolve)
})
}
[your-router.js] Use the sse library and call send:
var express = require('express');
var serverSentEvents = require('../lib/server-sent-events');
var router = express.Router();
router.get('/somepath', yourhandler);
module.exports = router;
async function yourhandler (req, res, next) {
await serverSentEvents.send('hello sse!'); // <<<<<
}
[your-client-side.js] Receive the sse updates:
I recommend you keep the event.data.replace(/"/g,'') because express-sse tacks on enclosing quotes and we don't want those.
const eventSource = new EventSource('http://yourserver/sse');
eventSource.onmessage = function(event) {
document.getElementById("result").innerHTML = event.data.replace(/"/g,'') + '...';
};

You should be able to do such a thing using Socket.io. First, you will need to install it with npm install socket.io. From there, in your code you will want to have var io = require(socket.io);
You can see more in-depth examples given by Socket.IO
You could use something like this on the server:
var express = require('express');
var app = express();
var server = require('http').createServer(app);
var io = require('../..')(server);
var port = process.env.PORT || 3000;
server.listen(port, function () {
console.log('Server listening at port ' + port);
});
app.use(express.static(__dirname + '/public'));
io.on('connection', function (socket) {
socket.emit('EVENT_NAME', {data});
});
And something like this on the client:
<script src="socket_src_file_path_here"></script>
<script>
var socket = io('http://localhost');
socket.on('EVENT_NAME', function (data) {
console.log(data);
//Do whatever you want with the data on the client
});
</script>

Related

How to send data from Node js server to client side?

I currently set up a node server which gets some data submitted from a html page and uses it to fetch data from an API. now I would like to display this data in a graphic format to a new html page (or even the same if possible).
In order to do this I think I should first send the data to the client side js. So that it gets the data to create the graph onto the new html page. But how would I do this? I tried to look for some examples unsuccessfully.
Here's a failing attempt at this (I omitted some code that I think wasn't influencial):
//server (Node JS)
const app = express();
app.use(express.json());
app.use(express.urlencoded( {extended: true} ));
const port = process.env.PORT || 8080;
let values;
app.get('/', function(req, res) {
res.sendFile(path.join(__dirname, '/index.html'));
});
async function fillArrays (from, to) {
...
}
const fetchData = async () => {
...
values = ...;
}
app.post('/input', async function(req,res){
await fillArrays(req.body.a, req.body.b);
console.log("End");
res.sendFile(path.join(__dirname, '/graph.html'));
res.json(await fetchData());
});
app.listen(port);
console.log('Server started at http://localhost:' + port);
graph.html:
<head>
<script src='https://cdn.plot.ly/plotly-2.14.0.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js'></script>
<script type="text/javascript" src="chart.js"></script>
</head>
<body>
<div id='myDiv'></div>
</body>
chart.js :
let dataset;
//attempt at getting data from server side
const promise = fetch('/input');
promise.then(response => {
if(!response.ok){
console.error(response)
} else {
return console.log(response);
}
}).then(result => {
dataset = result;
})
let range1 = Math.min(dataset[0]);
let range2 = Math.max(dataset[0]);
var trace = {
...
}
var data = trace;
var layout = {
...
};
Plotly.newPlot('myDiv', data, layout);

Why my socketio is not connecting with my socketio-client?

i am working on a chatapp project that needs a real time chatting so i have used socketio in my server side which is written in nodejs and than used socketio-client in my main chatapp react-native project.
But now a problem is coming my socket is not initializing. I'm not able to connect my server with my main app. I am using socketio and socketio client my both the socket version are same 4.5.1 but it's not even connecting. I have tried to use old version of socket but its also not working and I have also tried to change my localhost port to 4000 but it's also not working.
My server code:
const express = require('express');
var bodyParser = require('body-parser');
const app = express();
const http = require('http');
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
const port = process.env.PORT || 3000;
require('./src/config/database')
const user_routes = require('./src/user/users.routes');
app.use(bodyParser.urlencoded({extended: true}))
app.use(express.json())
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});
app.use('/User', user_routes)
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('send_message',(data)=>{
console.log("received message in server side",data)
io.emit('received_message',data)
})
socket.on('disconnect', () => {
console.log('user disconnected');
});
});
server.listen(port, () => {
console.log( `Server running at http://localhost:${port}/`);
});
My app socketservice file code:
import io from 'socket.io-client';
const SOCKET_URL = 'http://localhost:3000'
class WSService {
initializeSocket = async () => {
try {
this.socket = io(SOCKET_URL, {
transports: ['websocket']
})
console.log("initializing socket", this.socket)
this.socket.on('connect', (data) => {
console.log("=== socket connected ====")
})
this.socket.on('disconnect', (data) => {
console.log("=== socket disconnected ====")
})
this.socket.on('error', (data) => {
console.log("socekt error", data)
})
} catch (error) {
console.log("scoket is not inialized", error)
}
}
emit(event, data = {}) {
this.socket.emit(event, data)
}
on(event, cb) {
this.socket.on(event, cb)
}
removeListener(listenerName) {
this.socket.removeListener(listenerName)
}
}
const socketServcies = new WSService()
export default socketServcies
Where I have marked it should be connected = true but it's false in the dev console I have done console log so check that it's connecting or not and I can see that it's not connecting. How to make it connect?
There is no error in my app or server I have checked many times and my server is also running when I am running my app.
Answering my own question
The problem was i was using android emulator and android in an emulator can't connect to localhost you need to use the proxy ip so when i add http://10.0.2.2:3000 in const SOCKET_URL = 'http://10.0.2.2:3000' than its working fine
credit goes to gorbypark who told me this in discord
I'm assuming that your front and back runs in localhost. The documentation says that if the front-end is in the same domain as the back-end, you don't need to use the URL. Since you have the options parameter declared, you can use the default argument window.location in first place:
class WSService {
initializeSocket = async () => {
try {
this.socket = io(window.location, {
transports: ['websocket']
})
console.log("initializing socket", this.socket)
this.socket.on('connect', (data) => {
console.log("=== socket connected ====")
})
this.socket.on('disconnect', (data) => {
console.log("=== socket disconnected ====")
})
this.socket.on('error', (data) => {
console.log("socekt error", data)
})
} catch (error) {
console.log("scoket is not inialized", error)
}
}
emit(event, data = {}) {
this.socket.emit(event, data)
}
on(event, cb) {
this.socket.on(event, cb)
}
removeListener(listenerName) {
this.socket.removeListener(listenerName)
}
}
Don't specify the host/port for socket-io to connect to. It can figure it out on its own.
Per documentation, it tries to connect to window.location if no URL is specified as an argument.
So instead of
this.socket = io(SOCKET_URL, {
transports: ['websocket']
})
Just do
this.socket = io()
I am not sure it works with other arguments. You could try like this
this.socket = io(undefined, {
transports: ['websocket']
})

Socket io connection is not establishing in node js

I am writing server client application in node js.
Unfortunately Socket io connection is not being established.
My server side code is like this.
filename : MyServer.js
function MyServer(selectClient)
{
var express = require('express');
var bodyParser = require("body-parser");
this.app = express();
this.app.use(bodyParser.urlencoded({ extended: false }));
this.app.use(bodyParser.json());
var cookieParser = require('cookie-parser')
this.app.use(cookieParser())
if (undefined == selectClient)
{
selectClient = "default";
}
this.setStaticRoute("client/" + selectClient);
this.client = selectClient;
};
MyServer.prototype.setStaticRoute = function (staticPath)
{
var express = require('express');
var path = require('path');
this.app.use(express.static(staticPath));
};
MyServer.prototype.listen = function (portNumber)
{
this.server = this.app.listen(portNumber, '127.0.0.1', function ()
{
var MyServerSocketIo = require('MyServerSocketIo');
this.socketLink = new MyServerSocketIo(this.server,this.onDisconnect.bind(this),
this.onError.bind(this),this.onConnection.bind(this));
console.log("Inside listen function");
}.bind(this));
};
MyServer.prototype.onDisconnect = function()
{
console.log('On Socket IO Disconnect for MyServer');
};
MyServer.prototype.onError = function()
{
console.log('On Error');
};
MyServer.prototype.onConnection = function()
{
console.log('On Connection');
};
and MyServerSocketIo.js is like below
function MyServerSocketIo(server,onDisconnectCB,onErrorCB,onConnectionCB)
{
var SocketIo = require('socket.io');
this.onDisconnectCB = onDisconnectCB;
this.onErrorCB = onErrorCB;
this.onConnectionCB = onConnectionCB;
this.socket = null;
this.socketio = SocketIo(server);
this.socketio.on('connection',this.onConnection.bind(this));
};
MyServerSocketIo.prototype.onDisconnect = function ()
{
console.log('MyServer SocketIO Client Disconnected');
this.onDisconnectCB();
};
MyServerSocketIo.prototype.onError = function (error)
{
console.log('MyServer SocketIO Connection error' + error);
this.onErrorCB();
};
MyServerSocketIo.prototype.onConnection = function (socket)
{
console.log('MyServer SocketIO Connection with Client ID '+ socket.id + ' Established');
this.socket = socket;
socket.on('disconnect', this.onDisconnect.bind(this));
socket.on('error',this.onError.bind(this));
this.onConnectionCB();
};
Below is my client side code
filename: index.html
<!DOCTYPE html>
<html><head><meta charset="UTF-8">
<title>visual Display</title>
<link rel="preload" href="css/visual.css" as="style">
<link rel="stylesheet" href="css/visual.css">
<script type="text/javascript" src="/socket.io/socket.io.js"></script>
<script type="text/javascript" src="js/jquery-3.3.1.js"></script>
<script type="text/javascript" src="js/MyClientSocketIo.js"></script>
<script type="text/javascript" src="js/display.js"></script>
<script type="text/javascript" charset="utf-8">
display = new Display();
function OnDisconnect()
{
display.showError();
}
function OnError()
{
display.showError();
}
clientSocket = io.connect('http://localhost:39198', {
transports: ['websocket'],
'forceNew': true,
rejectUnauthorized: false,
reconnection: true,
reconnectionDelay: 1000,
reconnectionDelayMax : 1000,
reconnectionAttempts: 99999
});
var socketClient = new MyClientSocketIo(clientSocket,OnDisconnect,OnError);
</script>
</head>
<body>
</body>
</html>
MyClientSocketIo.js file code is like below
function MyClientSocketIo(client,onDisconnectCB,onErrorCB)
{
this.onDisconnectCB = onDisconnectCB;
this.onErrorCB = onErrorCB;
this.client = client;
this.client.on('connect',this.onConnection.bind(this));
};
MyClientSocketIo.prototype.onDisconnect = function ()
{
console.log('MyClient Disconnected');
this.onDisconnectCB();
};
MyClientSocketIo.prototype.onError = function (error)
{
console.log('MyClient ' + this.client.id + ' encountered Error ' + error);
this.onErrorCB();
};
MyClientSocketIo.prototype.onConnection = function ()
{
console.log('MyClient ' + this.client.id + ' connected to MyServer over SocketIO !!');
this.client.on('disconnect', this.onDisconnect.bind(this));
this.client.on('error',this.onError.bind(this));
};
I could able to see server and client are getting started and below console log "Inside listen function" getting printed as well which is MyServer.prototype.listen function.
But socketIO connection b/w server and client is not getting established.
I could not see console log lines which are there inside MyServerSocketIo.prototype.onConnection function.
I am waiting 30seconds for socketio connection. If not established restarting the server and client and after restarting also socketio connection is not getting established.
This is my personal script of working chat. I have done code with nodejs, axios and socket.
You can do with this script also.
Backend Server
require("dotenv").config();
const port = process.env.SOCKET_PORT || 3000;
const main_server_url = process.env.SERVER_URL;
var express = require("express");
var app = express();
var server = app.listen(port);
var connectionOptions = {
"force new connection": true,
"reconnection": true,
"reconnectionDelay": 2000, //starts with 2 secs delay, then 4, 6, 8, until 60 where it stays forever until it reconnects
"reconnectionDelayMax": 60000, //1 minute maximum delay between connections
"reconnectionAttempts": "Infinity", //to prevent dead clients, having the user to having to manually reconnect after a server restart.
"timeout": 10000, //before connect_error and connect_timeout are emitted.
"transports": ["websocket"] //forces the transport to be only websocket. Server needs to be setup as well/
}
var io = require("socket.io").listen(server, connectionOptions);
var axios = require("axios");
var users = [];
var connections = [];
console.log("Server connected done");
io.sockets.on("connection", function (socket) {
var server_url = main_server_url;
console.log(server_url);
console.log(people);
connections.push(socket);
console.log("Connected : total connections are " + connections.length);
// rest of events of socket
});
Front End JS for load IO for client
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js"></script>
<script type="text/javascript">
var base_url = YOUR_BASE_URL;
var port = YOUR_SOCKET_PORT;
var socket_port_url = base_url + ":" + port;
var socket = io(socket_port_url);
socket.on('done', (data) => {
console.log(data);
});
</script>

Parse request in my simple Node Js server

I'm new to Node and am trying to build a simple server in Node using Express. The requests are in the form of say /input00001/1/output00001. What I need to do is to parse this request and if the flag is 1 (middle value), I need to replace the file \home\inputfiles\input00001.txt with file \home\outputfiles\output00001.txt. How is it possible to do that?
Here is my simple server so far. I'm OK with not using the Express and pure NodeJs if that makes things easier.
const express = require('express');
const app = express();
const port = 8000;
app.get('/', (request, response) => {
response.send('Hello from Express!');
request.param
});
app.get('/*', (request, response) => {
response.send('Start!');
var url = request.originalUrl;
});
app.listen(port, (err) => {
if (err) {
return console.log('something bad happened', err);
}
console.log(`server is listening on ${port} for incoming messages`);
});
You should set up a route that expects these items as url parameters and then use those parameters to do what you want. For example if you're url is /input00001/1/output00001 then you could set up a route like this:
app.get('/:input/:flag/:output', (req, res) => {
var params = req.params
var input = params.input //input0001
var flag = params.flag // 1
var output = params.output //output0001
// now do what you need to with input, flag, and output
if(typeof flag!=='undefined' && flag==1){
var file_name_string = '\home\inputfiles\input00001.txt';
var res = file_name_string.replace("input", "output");
}
console.log(input, flag, output)
res.send("done")
})

Replacing fs.readFile with fs.createReadStream in Node.js

I have code for reading an image from directory and sending it to index.html.
I am trying to replace fs.readFile with fs.createReadStream but i have no idea how to implement this as i can not find a good example.
Here is what i got (index.js)
var app = require('express')();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
http.listen(3000, function () {
console.log('listening on *:3000');
});
app.get('/', function (req, res) {
res.sendFile(__dirname + '/public/views/index.html');
});
io.on('connection', function (socket) {
fs.readFile(__dirname + '/public/images/image.png', function (err, buf){
socket.emit('image', { image: true, buffer: buf.toString('base64') });
});
});
index.html
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="200" height="100">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script src="https://cdn.socket.io/socket.io-1.2.0.js"></script>
<script>
var socket = io();
var ctx = document.getElementById('canvas').getContext('2d');
socket.on("image", function (info) {
if (info.image) {
var img = new Image();
img.src = 'data:image/jpeg;base64,' + info.buffer;
ctx.drawImage(img, 0, 0);
}
});
</script>
</body >
</html >
The below approach only uses core modules and reads the chunks from the stream.Readable instance returned from fs.createReadStream() and returns those chunks back as a Buffer. This isn't that great of an approach if you're not going to stream the chunks back. You're going to hold the file within a Buffer which resides in memory, so its only a good solution for reasonably sized files.
io.on('connection', function (socket) {
fileToBuffer(__dirname + '/public/images/image.png', (err, imageBuffer) => {
if (err) {
socket.emit('error', err)
} else {
socket.emit('image', { image: true, buffer: imageBuffer.toString('base64') });
}
});
});
const fileToBuffer = (filename, cb) => {
let readStream = fs.createReadStream(filename);
let chunks = [];
// Handle any errors while reading
readStream.on('error', err => {
// handle error
// File could not be read
return cb(err);
});
// Listen for data
readStream.on('data', chunk => {
chunks.push(chunk);
});
// File is done being read
readStream.on('close', () => {
// Create a buffer of the image from the stream
return cb(null, Buffer.concat(chunks));
});
}
HTTP Response Stream Example
Its almost always a better idea to use HTTP for streaming data since its built into the protocol and you'd never need to load the data into memory all at once since you can pipe() the file stream directly to the response.
This is a very basic example without the bells and whistles and just is to demonstrate how to pipe() a stream.Readable to a http.ServerResponse. the example uses Express but it works the exact same way using http or https from the Node.js Core API.
const express = require('express');
const fs = require('fs');
const server = express();
const port = process.env.PORT || 1337;
server.get ('/image', (req, res) => {
let readStream = fs.createReadStream(__dirname + '/public/images/image.png')
// When the stream is done being read, end the response
readStream.on('close', () => {
res.end()
})
// Stream chunks to response
readStream.pipe(res)
});
server.listen(port, () => {
console.log(`Listening on ${port}`);
});

Categories