I have tried several variations of trying to emit to all users connected to a particular /namespace, but have had no luck. I could be misunderstanding how sockets work.
But what I have right now is two browsers open on different pages. When a user connects to pageA, that user is now part of '/users' namespace. When a user connects to pageB, that user is now part of '/valets' namespace.
I have a .emit() on pageA that sends to server.js. I listen for it with .on(), and then try to run .emit() but to only the users in '/valets' namespace.
I am able to see in my terminal "listening for request valet" and the console.log(data) part.
I believe my problem is the usr_nsp.of('/valets').emit("incoming-request",{data:data}); portion. The other commented lines are what I have tried so far. They all give me an error: is not a function.
server.js
var app = require('http').createServer();
var io = require('socket.io')(app);
app.listen(3000, function(){
console.log('listening on port 3000');
});
var redis = require('socket.io/node_modules/redis');
// create custom namespace for Users
var room_number;
var usr_nsp = io.of('/users');
usr_nsp.on('connection', function(socket){
console.log('user has connected to /users namespace');
socket.on('request-valet', function(data){
console.log("listening for request valet");
console.log(data);
room_number = data.room_number;
socket.join(room_number);
// usr_nsp.broadcast.of('/valets').emit("incoming-request",{repark:data});
// usr_nsp.of('/valets').broadcast.emit("incoming-request",{repark:data});
// io.of('/valets').emit("incoming-request",{repark:data});
// socket.of('/valets').emit("incoming-request",{repark:data});
usr_nsp.of('/valets').emit("incoming-request",{repark:data});
});
});
var valet_nsp = io.of('/valets');
valet_nsp.on('connection', function(socket){
console.log('valet has connected to /valets namespace');
// var room_number;
socket.on('join-room', function(data){
// assign valet to room
room_number = data.room_number;
socket.join(room_number);
//valet_nsp.sockets.in(room_number).emit("request-accepted",{current_pos:current_pos})
});
socket.on('set-valet-starting-position', function(data){
//var valet_starting_pos = data.starting_position;
valet_nsp.sockets.in(room_number).emit('activate-directions-service', {repark:data});
})
socket.on('get-new-location', function(data){
// send the updated location only to User
// maybe use .broadcast??
valet_nsp.sockets.in(room_number).emit("update-valet-location", {current_pos:data});
});
});
pageB.html (sockets portion)
socket.on('incoming-request', function(data){
console.log("incoming request");
alert("incoming request");
// use data to display on html screen
});
The namespace handle you created is used to emit to users in that particular namespace. This should thus work:
var users = io.of('/users'),
valets = io.of('/valets');
users.on('connection', function(socket) {
socket.on('request-valet', function(data) {
valets.emit('incoming-request', { repark : data });
});
});
Related
I have a node js server witch is for a chat page. Now I want to make a private messiging part of the server. Currently i have the following code:
Client:
rooms.addEventListener('submit', function(e){
e.preventDefault();
if(roomName.value){
socket.emit('Join-Room', roomName.value);
roomName.value = '';
}
});
Server:
socket.on('Join-Room', (roomName) => {
socket.join(roomName);
});
When I try to join one room nothing happens and i am still in the main chat.
I'm trying to use server-side events (SSE) in Javascript and Node.JS to push updates to a web client.
To keep things simple, I have a function which will generate the time every second:
setTimeout(function time() {
sendEvent('time', + new Date);
setTimeout(time, uptimeTimeout);
}, 1000);
The sendEvent function puts together the event in the expected format and sends it to the client.
var clientRes;
var lastMessageId = 0;
function sendEvent(event, message) {
message = JSON.stringify(message);
++lastMessageId;
sendSSE(clientRes, lastMessageId, event, message);
}
The clientRes value comes from the server function to handle the route from the base URL.
app.use('/', function (req, res) {
clientRes = res;
...
}
What I want to achieve at the client UI is a simple page which shows:
> <h1>The current time is {event.data}</h1>
where I derive the current time from the latest message data received from the server.
I have created an index.html file to have the client listen for these server-sent messages:
<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
<div id="result"></div>
<script>
if(typeof(EventSource) !== "undefined") {
console.log("Event source is supported");
var source = new EventSource("localhost:3000");
source.onmessage = function(event) {
document.getElementById("result").innerHTML += "=>" + event.data + "<br>";
};
} else {
console.log("Event source not supported");
document.getElementById("result").innerHTML = "Sorry, your browser does not support server-sent events...";
}
evtSource.addEventListener("time", function(event) {
const newElement = document.createElement("li");
const time = JSON.parse(event.data).time;
console.log("Time listener found time " + time);
newElement.innerHTML = "ping at " + time;
eventList.appendChild(newElement);
});
</script>
</body>
</html>
If I respond to a GET request with this index.html, I don't see any of the time messages.
That is, this server code does not work:
app.use("/", function(request, response) {
response.sendFile(__dirname + "/index.html");
clientRes = response;
});
However if I don't respond with the index.html file and allow the server to push timestamps to the client, they to show up in the browser:
event: time
id: 104
data: 1587943717153
event: time
id: 105
data: 1587943727161
...
Here's is where I'm stuck.
It appears I have successfully gotten the server to push new timestamps every second.
And the browser is seeing them and displaying the text.
But the arrival of the message from the server is not triggering the listener and the message is not being rendered based on the index.html.
Most of the examples I've seen for use of SSE involves a PHP data source. I need for the server to both generate the data and to provide the HTML to display it.
I've been successful in one or the other, but not both at the same time.
I figured out what I was missing.
I did not specify the endpoints correctly.
For the root endpoint, the server code needs to deliver the index.html file.
app.use("/", function(request, response) {
console.log("In root handler");
response.sendFile(__dirname + "/index.html");
});
Index.html contains the script that creates the event source:
var source = new EventSource("http://localhost:3000/time");
But the URL that gets passed in as the input to the EventSource constructor must be a different endpoint (not root). It needs to be the endpoint that generates the timestamps.
So in the server, the handler for the /time endpoint is the one which pushes the data.
app.use('/time', function (req, res) {
res.writeHead(200, {
'content-type': 'text/event-stream',
'cache-control': 'no-cache',
'connection': 'keep-alive'
});
// Save the response
clientRes = res;
});
If this is a vague question, please let me know, so that I can specify. I really want to get around this stump.
I have read several cheat sheets regarding how to properly broadcast a message to a client room. My reference is: https://github.com/socketio/socket.io/blob/master/docs/emit.md.
To describe my problem:
The chat page comes up with the send button correctly, however as soon as I click send, nothing is ever sent.
The interesting thing, is that whenever I am not using rooms, and just use the default namespace, I can get messages.
Any idea on what is going on? Thank you!!
server.js
io.on('connection', function(socket) {
global.rooms = 'room1';
socket.on('subscribe', function(room) {
socket.join(rooms);
console.log(socket.id + 'joining room', rooms)
})
socket.on('chat', function(data) {
io.to(rooms).emit('chat', data);
})
})
chat.jade
extends layout
block content
h2.page-header Chat Page
div(id='the-chat')
div(id='chat-window')
div(id='output')
input(id='handle', type='text', value = user.name, style= 'width: 0px; visibility: hidden;')
input(id='message', type='text', placeholder='message')
button(id='send' value='Send') Send
//imports the socket.io functionality on the client side for the chat.jade application
script(src="/socket.io/socket.io.js")
script.
//variable created that mirrors connection made in the backend
//matches the connection made in the server side
var socket = io.connect('http://localhost:3000')
//Query dom
var message = document.getElementById('message')
var handle = document.getElementById('handle')
var btn = document.getElementById('send')
var output = document.getElementById('output')
//emit events
btn.addEventListener('click', function() {
socket.emit('chat', {
message: message.value,
handle: handle.value
})
})
socket.on('chat', function(data) {
output.innerHTML += '<p><strong>' + data.handle + ': </strong>' + data.message + '</p>';
document.getElementById('message').value = "";
})
Home.jade
extends layout
block content
h2.page-header(style = "text-align: center;").
Home Page
if (user.isTutor)
b(style = "text-align: center;")
form(method='post', action = '/home/available')
input.btn.btn-primary(type = 'submit',name='isAvailable', value = 'Available', id = 'button4')
form(method='post', action = '/home/unavailable')
input.btn.btn-primary(type = 'submit',name='isUnavailable', value = 'Unavailable', id = 'button5')
script(src="/socket.io/socket.io.js")
script.
var btn = document.getElementById('button4')
//var space = '#{user.room}'
var socket = io.connect()
btn.addEventListener('click', function() {
socket.emit('subscribe', 'room1')
})
div(id = 'magic')
form(method='get')
if (user.hasApplied)
input.btn.btn-primary(type = 'submit', onclick = "javascript: form.action = '/findatutor';" name='find', value = 'Find a Tutor', class = 'middle', id = 'button7')
else if (user.hasApplied == false)
input.btn.btn-primary(type = 'submit', onclick = "javascript: form.action = '/findatutor';" name='find', value = 'Find a Tutor', id = 'button1')
input.btn.btn-primary(type = 'submit', onclick = "javascript: form.action = '/apply';" name='become', value = 'Become a Tutor', id = 'button2')
Home.js
router.post('/available', ensureAuthenticated, (req,res,next) => {
var io = res.locals['socketio']
db.collection('DefaultUser').update({_id: req.user._id}, {$set: {isAvailable: true}});
res.redirect('../chat')
})
The problem is you did not specify where to render the messages. As I understood, you have no problem creating rooms, so I will explain step by step how to handle after that point.
According to your code this is the communication between server and client for sending messages
//server.js
socket.on('chat', function(data) {
io.to(rooms).emit('chat', data);
})
//chat.jade
btn.addEventListener('click', function() {
socket.emit('chat', {
message: message.value,
handle: handle.value
})
})
First client is sending the message (typing the input box) to the server and when server receives it, it has to send the same message to all other clients. once clients receive this message, clients have to figure out how to display it. But when server sends the received message, then you have to initiate a new event. let's call it display. so your code should be like this:
//server.js
//i always use arrow functions, but I will follow along your code
socket.on('chat', function(data) {
io.to(rooms).emit('display', data);
})
Now your client should be listening for this event and should be handling where to display it:
//chat.jade
socket.on('display', (data) => {
const displayedMessage = pug.render(messageTemplate, {
message: data.message,
})
$messages.insertAdjacentHTML('beforeend', displayedMessage)
})
Since Jade has been renamed to pug, i used pug here. So i will render {message: data.message} into html in a script tag then placed it into DOM ELEMENT $message.
I am not sure where you want to render handle:handle.value i will just show how to display message. similarly, you can handle it.
Now what are messageTemplate, $messages and insertAdjacentHTML()?
create a div tag and a script tag in your html
//this is where you are gonna display the message
<div id="messages" class="chat__messages"></div>
<!-- template messages -->
<script id="message-template" type="text/html">
<div>
<p>{{message}}</p>
</div>
</script>
//chat.jade
const $messages = document.getElementById("messages");
const messageTemplate = document.getElementById("message-template").innerHTML;
The insertAdjacentHTML() method inserts a text as HTML, into a specified position. you can get more explanation and examples here:
https://www.w3schools.com/jsref/met_node_insertadjacenthtml.asp
socket io code looks long and complicated but if you know the logic and move step by step it will easy to implement it.
Try
io.in(rooms).emit('chat', data);
Missing this under global.rooms = 'room1';
var rooms = global.rooms
Longer Answer: At least what I can see, you've added "rooms" as a property on the global object (idk where 'global' itself is defined, but if it's not throwing an error, I'm assuming you defined it above). While it's a property, the variable 'rooms' that you're using as a namespace isn't defined at the time you're calling it, so it doesn't know where to emit the message.
Even more answer: Also, if you're intending to add additional rooms to global.rooms, I think you might want to use a hashlist to store them, so that you can easily access them as global.rooms[room], as well as easily add new rooms to the list ie global.rooms[room.name] = room
I've created client and server of node with socket.io. server is executing 4 get requests of news feed and fetched the data. These data is sent to the client with socket.io.
client is displaying news feed on the occurrence of specific socket.io event.
This works well for once. Here is the code and working fiddle
server.js
var app = require('http').createServer(handler)
, io = require('socket.io').listen(app)
, fs = require('fs')
, redis = require("redis");
var http = require("http");
// initialize the container for our data
var data = "";
var nfs = [
"http://economictimes.feedsportal.com/c/33041/f/534037/index.rss",
"http://www.timesonline.co.uk/tol/feeds/rss/uknews.xml",
"http://www.independent.co.uk/news/business/rss",
"http://www.dailymail.co.uk/money/index.rss"
];
//setInterval(function() {
for(var i=0; i<nfs.length; i++){
//console.log(nfs[i]);
http.get(nfs[i], function (http_res) {
// this event fires many times, each time collecting another piece of the response
http_res.on("data", function (chunk) {
// append this chunk to our growing `data` var
data += chunk;
});
// this event fires *one* time, after all the `data` events/chunks have been gathered
http_res.on("end", function () {
// you can use res.send instead of console.log to output via express
console.log("data received");
});
});
}
//}, 30000);
app.listen(8080);
function handler (req, res) {
fs.readFile(__dirname + '/client.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
io.on('connection', function (socket) {
//setInterval(function() {
socket.emit('news', data);
/*socket.on('my other event', function (data) {
console.log(data);
});*/
//}, 5000);
});
client.html
<html>
<head>
<script src="https://cdn.socket.io/socket.io-1.2.1.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script>
//socket io client
var socket = io.connect('http://localhost:8080');
//on connetion, updates connection state and sends subscribe request
socket.on('connect', function(data){
setStatus('connected');
socket.emit('subscribe', {channel:'notif'});
});
//when reconnection is attempted, updates status
socket.on('reconnecting', function(data){
setStatus('reconnecting');
});
//on new message adds a new message to display
socket.on('news', function (data) {
console.log(data);
//socket.emit('my other event', { my: 'data' });
addMessage(data);
});
/*socket.on('news', function (data) {
debugger;
socket.emit('my other event', { my: 'data' }
var msg = "";
if (data) {
msg = data;
}
addMessage(msg);
});*/
//updates status to the status div
function setStatus(msg) {
$('#status').html('Connection Status : ' + msg);
}
//adds message to messages div
function addMessage(msg) {
//debugger;
var $xml = $(msg);
var html = '';
$xml.find("item").each(function() {
var $item = $(this);
html += '<li>' +
'<h3><a href ="' + $item.find("link").text() + '" target="_new">' +
$item.find("title").text() + '</a></h3> ' +
'<p>' + $item.find("description").text() + '</p>' +
// '<p>' + $item.attr("c:date") + '</p>' +
'</li>';
});
$('#result').prepend(html);
}
</script>
</head>
<body>
<div id="status"></div><br><br>
<ul id="result"></ul>
</body>
</html>
What I understand about socket.io is that we don't need long server polling and so how do server come to know that news is added to the respected news feed.
How do I update the client with newly added news when news is added to the news feed rss ???
Update
Ok so from all the responses I get the point that it is not possible for socket.io to know that new entry has been added. So, how do I know (which tools/libraries do require to know that new entry has beed added and update the client as well) ???
Retrieving the messages from the news feeds are completely independent of socket.io unless the news feeds implement sockets on their end and your server becomes their client. So you will have to continue to poll them with http requests to know whether they have updated data.
In order to notify your clients of the update you would just emit the news event. Presumably you would have logic on the server to make sure you are only sending events which have not previously be sent.
There is no way for "node" to know when a new entry is added to the news feed. You will have to poll the news service like you are doing now. This really has nothing to do with Node or Socket.io unless I completely misunderstand what you are asking.
I created a simple application as an attempt to integrate node, express, socket.io, and jade. The user enters some string ("tool ID") in a text field and clicks a submit button. That text is simply converted to all uppercase and the result is appended to the results section on the page. The results should be automatically updated for other clients viewing the page.
It mostly works. However the problem is that right after the user clicks the submit button on the page to submit the tool ID, the node console and browser javascript console both show the client disconnecting and then reconnecting.
To the user it looks like the results are updated correctly for a fraction of a second. Then the results go blank for another fraction of a second. Then the results are redisplayed. Since I am showing the user's session ID with the results, I can see that the session ID changes during the short time while the results go blank.
Note that if a different client is simply viewing the page, but not otherwise interacting, the results are updated smoothly (no brief time of results going blank) and that client doesn't seem to be disconnecting at all.
I don't want the client to disconnect and reconnect when they click the submit button on the form. Can someone tell me why this is happening and how I should be doing it properly?
My app.js (server)
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(app);
io = require('socket.io').listen(server); // without the var, this becomes available to other files like routes.
var path = require('path');
var routes = require('./routes/routes');
var process = require('./routes/process');
var _ = require("underscore");
// all environments
app.set('port', 3097);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
//app.use(express.logger('dev'));
app.use(express.bodyParser()); //Tells server to support JSON, urlencoded, and multipart requests
app.use(express.methodOverride());
app.use(express.cookieParser('i7iir5b76ir857bveklfgf'));
app.use(express.session());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
var toolIDs = [];
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
io.on("connection", function(socket) {
console.log("Client connected. Sending Update");
socket.on("toolsRequest", function() {
socket.emit('toolsReady', {toolIDs: toolIDs}); //This should go to the client that just connected.
});
socket.on("disconnect", function() {
console.log("Client Disconnected");
});
socket.on("toolsUpdate", function(data) {
processedToolID = process.process(data.toolID);
toolIDs.push({id: data.id, inputToolID: data.toolID, outputToolID: processedToolID});
io.sockets.emit("toolsUpdated", {toolIDs: toolIDs}); //This should go to all clients
console.log('Results Updated - notifying all clients');
});
});
// display main page
app.get('/', routes.home);
server.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
My routes.js
/*
* GET home page.
*/
exports.home = function(req, res){
res.render('home', { title: 'Tool'});
console.log("Just called route.home");
};
My home.jade
doctype 5
html
head
title= title
link(rel='stylesheet', href='/bootstrap/css/bootstrap.min.css')
link(rel='stylesheet', href='/bootstrap/css/bootstrap-responsive.min.css')
script(src='/socket.io/socket.io.js')
script(src="http://code.jquery.com/jquery.min.js")
script(src='/js/index.js')
block content
#wrapper
h1
a(href='/') TOOL
#display
div.row-fluid
div.inlineBlock
form#toolForm
label Tool ID
input(type="text", placeholder="e.g. abc123")#toolID
span.help-block You may enter a string.
button(class="btn")#submit
| Submit
br
div.inlineBlock.topAligned
h2 Results
br
div#results
br
My index.js (client)
function init() {
/*
On client init, try to connect to the socket.IO server.
*/
var socket = io.connect('http://example.com:3097/');
//We'll save our session ID in a variable for later
var sessionID = '';
//Helper function to update the results
function updateResults(toolIDs) {
$('#results').html('');
for (var i = 0; i < toolIDs.length; i++) {
$('#results').append('<span id="' + toolIDs[i].id + '">' + '<b>Creator ID:</b> ' + toolIDs[i].id + ' <b>Your ID:</b> ' + sessionID + ' <b>Input Tool:</b> ' + toolIDs[i].inputToolID + ' <b>Output Tool:</b> ' + toolIDs[i].outputToolID + (toolIDs[i].id === sessionID ? '<b>(You)</b>' : '') + '<br /></span>');
}
}
/*
When the client successfully connects to the server, an
event "connect" is emitted.
*/
socket.on('connect', function () {
sessionID = socket.socket.sessionid;
// Note this appears in the browser Javascript console, not node console
console.log('You are connected as: ' + sessionID);
socket.emit('toolsRequest'); //Request the tools data so we can update results
});
socket.on('toolsReady', function(data) {
updateResults(data.toolIDs);
console.log('Results have been updated from socket.on.toolsReady');
});
socket.on('toolsUpdated', function (data) {
updateResults(data.toolIDs);
console.log('Results updated from socket.on.toolsUpdated');
});
/*
Log an error if unable to connect to server
*/
socket.on('error', function (reason) {
console.log('Unable to connect to server', reason);
});
function getCitations() {
var toolID = $('#toolID').val()
socket.emit('toolsUpdate', {id: sessionID, toolID: toolID});
}
$('#submit').on('click', getCitations);
}
$(document).on('ready', init);
Here's what I see in the node console when a client clicks the submit button:
debug - websocket writing 5:::{"name":"toolsUpdated","args":[{"toolIDs":[{"id":"5a1dfX2dmxcogYT_11e8","inputToolID":"a123123","outputToolID":"A123123"},{"id":"OIuqao6TsTeddQm111e-","inputToolID":"1abcdefg","outputToolID":"1ABCDEFG"},{"id":"Qr_YQ2ZhQHbDpBlk11e_","inputToolID":"abcdefg","outputToolID":"ABCDEFG"}]}]}
Results Updated - notifying all clients
Just called route.home
info - transport end (socket end)
debug - set close timeout for client Qr_YQ2ZhQHbDpBlk11e_
debug - cleared close timeout for client Qr_YQ2ZhQHbDpBlk11e_
debug - cleared heartbeat interval for client Qr_YQ2ZhQHbDpBlk11e_
Client Disconnected
debug - discarding transport
debug - served static content /socket.io.js
debug - client authorized
info - handshake authorized 2bPKGgmLdD4fp-vz11fA
debug - setting request GET /socket.io/1/websocket/2bPKGgmLdD4fp-vz11fA
debug - set heartbeat interval for client 2bPKGgmLdD4fp-vz11fA
debug - client authorized for
debug - websocket writing 1::
Client connected. Sending Update
debug - websocket writing 5:::{"name":"toolsReady","args":[{"toolIDs":[{"id":"5a1dfX2dmxcogYT_11e8","inputToolID":"a123123","outputToolID":"A123123"},{"id":"OIuqao6TsTeddQm111e-","inputToolID":"1abcdefg","outputToolID":"1ABCDEFG"},{"id":"Qr_YQ2ZhQHbDpBlk11e_","inputToolID":"abcdefg","outputToolID":"ABCDEFG"}]}]}
Thanks, I appreciate the help.
Your submit button is actually reloading the page, which is why the socket is disconnecting, and why you see the socket response for such a short time. Just prevent the default action of the submit button. Change this:
$('#submit').on('click', getCitations);
To something similar:
$('#submit').click(function(event) {
event.preventDefault();
getCitations();
});