SignalR javascript client not picking up event from hub - javascript

I have a method on the hub however I am not getting any messages from this. I get the connection ID's from my user to connectionid mapping dictionary and loop through these connectionid's and then invoke a method on the client side called deviceDiscovered I can also confirm that the browser does connect to the hub
Here is my hub method
public async Task DetectDevice(dynamic message)
{
//We will be searching via the userid
var connectionId = Context.ConnectionId;
//now we get the connectionID from the table.
var connectionObj = ConnectionTable[connectionId];
//we now check whether this is a pathfinder or browser client
if (connectionObj.clientType == "pathfinder")
{
/*The pathfinder initiated the request and so this means the message should be forwarded to the browser client
that requested this resource*/
//Grab the data out of the dictionary.
PathfinderDetection deviceDiscoveredNotification = JsonConvert.DeserializeObject<PathfinderDetection>(message);
var userId = deviceDiscoveredNotification.userId;
var sysInfo = deviceDiscoveredNotification.sysInfo;
var found = deviceDiscoveredNotification.data;
if (found == "FOUND")
{
var deviceId = deviceDiscoveredNotification.deviceID;
var connections = ConnectionTable.Where(val => val.Value.id == connectionObj.id).Select(key => key.Key).ToList();
foreach (string connection in connections)
{
Clients.Client(connection).deviceDiscovered(deviceId);
}
}
else
{
//call a method on client side.
Clients.Group(userId.ToString()).noDevice("NOTFOUND");
}
I can see that the deviceID variable is populated and that the connection exists, however not getting anything back from the hub when looking at the client
here is my client side code:
<script>
$(document).ready(function(){
var connection = $.hubConnection("http://localhost:59016");
var contosoChatHubProxy = connection.createHubProxy('metrics');
contosoChatHubProxy.on('taskAdded', function(data){
console.log(data);
});
contosoChatHubProxy.on('deviceDiscovered', function(data) {
console.log(data);
});
contosoChatHubProxy.on('taskUpdate', function(data){
console.log(data);
});
contosoChatHubProxy.on('noDevice', function(found) {
console.log(found);
});
});
</script>

Related

Server Sent Events with AJAX: How to resolve SSE GET with XHR POST?

I'm trying to resolve an issue between, what I perceive is, AJAX and Server Sent Events. I have an application that does a post with some instructions to the controller, and I would like the controller to send some commentary back as an event to let the user know that the action requested has been performed (can have errors or take a while).
The idea is that the user can send a package of different instructions through the client, and the server will report through SSE when each of these actions are completed.
The problem I see through Fiddler is that when the post is performed, the response that it gets back contains my eventsource message that I would like used. However, the eventsource code also appears to call a GET, in which it appears to want that eventsource message. Because it doesn't get that, the connection repeatedly closes.
I currently have some controller code like so:
[System.Web.Http.HttpPost]
public void Stop(ProjectViewModel model)
{
ProjectManager manager = new ProjectManager();
if (model.Servers != null && model.Servers.Count != 0)
{
string machine = model.Servers[0];
foreach (string service in model.Services)
{
manager.StopService(service, machine);
Message("stop", service);
}
}
}
and in my view, both Ajax/XHR and server sent events set up like so:
var form = document.getElementById("submitform");
form.onsubmit = function (e) {
// stop the regular form submission
e.preventDefault();
// collect the form data while iterating over the inputs
var data = {};
for (var i = 0, ii = 2; i < ii; ++i) {
var input = form[i];
if (input.name == "Servers") {
data[input.name] = document.getElementById("ServerSelect").options[document.getElementById("ServerSelect").selectedIndex].text;
}
else if (input.name == "Services")
data[input.name] = document.getElementById("ServiceSelect").options[document.getElementById("ServiceSelect").selectedIndex].text;
}
if (action) { data["action"] = action };
// construct an HTTP request
var xhr = new XMLHttpRequest();
if (action == "stop") {
xhr.open(form.method, '/tools/project/stop', true);
}
if (action == "start") {
xhr.open(form.method, '/tools/project/start', true)
}
xhr.setRequestHeader('Content-Type', 'application/json; charset=urf-8');
// send the collected data as JSON
xhr.send(JSON.stringify(data));
xhr.onloadend = function () {
// done
};
};
function events() {
if (window.EventSource == undefined) {
// If not supported
document.getElementById('eventlog').innerHTML = "Your browser doesn't support Server Sent Events.";
} else {
var source = new EventSource('../tools/project/Stop');
source.addEventListener("message", function (message) { console.log(message.data) });
source.onopen = function (event) {
document.getElementById('eventlog').innerHTML += 'Connection Opened.<br>';
console.log("Open");
};
source.onerror = function (event) {
if (event.eventPhase == EventSource.CLOSED) {
document.getElementById('eventlog').innerHTML += 'Connection Closed.<br>';
console.log("Close");
}
};
source.onmessage = function (event) {
//document.getElementById('eventlog').innerHTML += event.data + '<br>';
var newElement = document.createElement("li");
newElement.textContent = "message: " + event.data;
document.getElementById("eventlog").appendChild(newElement)
console.log("Message");
};
}
};
I'm somewhat new to web development, and I'm not sure how to resolve this issue. Is there a way I can have the eventsource message read from that POST? Or have it sent to the GET instead of being sent as a response to the POST? Overall, it seems that the most damning issue is that I can't seem to get the event messages sent to the GET that is requested by the eventsource api.
EDIT: Since posting this, I tried creating a new method in the controller that specifically handles eventsource requests, but it appears that the event response still somehow ends up in the POST response body.
public void Message(string action, string service)
{
Response.ContentType = "text/event-stream";
Response.CacheControl = "no-cache";
//Response.Write($"event: message\n");
if (action == "stop")
{
Response.Write($"data: <li> {service} has stopped </li>\n\n");
}
Response.Flush();
Thread.Sleep(1000);
Response.Close();
}
I ended up solving this. My original idea was to pass the viewmodel in each of my methods back and forth with a Dictionary<string,string> to key in each event that can be used, but the viewmodel is not persistent. I solved this issue further by implementing the events in a Dictionary saved in Session data, and the usage of Sessions for MVC can be found in the resource here that I used:
https://code.msdn.microsoft.com/How-to-create-and-access-447ada98
My final implementation looks like this:
public void Stop(ProjectViewModel model)
{
ProjectManager manager = new ProjectManager();
if (model.Servers != null && model.Servers.Count != 0)
{
string machine = model.Servers[0];
foreach (string service in model.Services)
{
manager.StopService(service, machine);
model.events.Add(service, "stopped");
this.Session["Events"] = model.events;
}
}
//return View(model);
}
public void Message(ProjectViewModel model)
{
Thread.Sleep(1000);
Response.ContentType = "text/event-stream";
Response.CacheControl = "no-cache";
Response.AddHeader("connection", "keep-alive");
var events = this.Session["Events"] as Dictionary<string, string>;
Response.Write($"event: message\n");
if (events != null && events.Count != 0)
{
foreach (KeyValuePair<string, string> message in events)
{
Response.Write($"data: {message.Key} has been {message.Value}\n\n");
}
}
Response.Flush();
Thread.Sleep(1000);
Response.Close();
}
Adding keep-alive as connection attribute in the HTTP Response header was also important to getting the SSEs to send, and the Thread.Sleep(1000)'s are used due to the stop action and message action happening simultaneously. I'm sure there's some optimizations that can go into this, but for now, this is functional and able to be further developed.

Delay is socket.io event?

I created an ajax call to the IMBd database
// API Key
key = "4dba72b2-7558-4c0f-bd18-9ffcb0999c4e";
// Url
mainUrl = "http://api.myapifilms.com/imdb/top?token="+ key +"&format=json&data=0&start=1&end=250";
// API Call
var request = require('request');
request(mainUrl, function (error, response, body) {
if (!error && response.statusCode == 200) {
// Storing data in an object
var obj = JSON.parse(body), //JSON Parser
movieArray = obj.data.movies, //Creating Array
item = movieArray[randomMovieRank]; //Setting random movie variable
itermArray = [item.ranking,item.title,item.year];
console.log(itermArray);
io.sockets.emit("serverAnswer", {ranking: itermArray[0], title: itermArray[1], year: itermArray});
}
});
return false;
Followed up by:
socket.on("serverAnswer", function(data){
console.log(data.title);
});
The socket.on is called on the client side. The problem I am having is that it is pulling through the data very slowly if at all. The API is working as it is console logging correctly in terminal. But client side it sometimes pulls through ad sometimes doesnt. Is there something I am doing wrong?
EDIT:
Added pastebin: http://pastebin.com/TYHsqBmK
When you invoke the emit method, your client is not guaranteed connected,you can trigger the ajax event after the client connected or emit specified messages,such as
the server:
io.on('connection',function(socket){
if(movies !== null)
{
socket.emit("serverAnswer", {movies:movies});
}
else{
//1.ajax request IMDB resource
//2.set movies variables
//3.emit message
}
});
the client:
socket.on("serverAnswer", function(data){
console.log(data);
});

Yii2 send event from php to clients using yii2-nod-socket

I am using yii2-node-socket to send event to clients in a concrete room. I followed all steps to install the extension in my project, the server was started successfully and the client was connected to socket but when I tried to send event from php code the client did not receive any data.
Javascript code:
var socket = new YiiNodeSocket();
socket.onConnect(function () {
socket.room('testRoom').join(function (success, numberOfRoomSubscribers) {
// success - boolean, numberOfRoomSubscribers - number of room members
// if error occurred then success = false, and numberOfRoomSubscribers - contains error message
if (success) {
console.log(numberOfRoomSubscribers + ' clients in room: testRoom');
// do something
// bind events
this.on('join', function (newMembersCount) {
// fire on client join
});
this.on('data', function (data) {
// fire when server send frame into this room with 'data' event
console.log('in data : ', data);
});
} else {
// numberOfRoomSubscribers - error message
alert(numberOfRoomSubscribers);
}
});
});
PHP code:
// create frame
$frame = Yii::$app->nodeSocket->getFrameFactory()->createEventFrame();
// set event name
$frame->setEventName('data');
// set room name
$frame->setRoom('testRoom');
// set data
$frame['key'] = 'hello';
// send
$frame->send();

Socket.io: How to uniquely identify a connection on client and server?

My app supports multiple socket.io clients from the same host (IP address). For diagnostics, I need to be able correlate client and server logs to identify which client the server is talking to. Does socket.io provide a way to uniquely identify a connection?
What I do is that within /routes/socket.js, I have these added to my requires:
var thisApp = require('../app');
var cookieSig = require('express/node_modules/cookie-signature');
var cookie = require('cookie');
var connect = require('express/node_modules/connect')
, parseSignedCookie = connect.utils.parseSignedCookie;
This answer assumes you have a session store of some kind that you can access via thisApp.thisStore. In my case, in the main app.js, I set up a session store using kcbanner's connect-mongo (available via npm and github.com) using a MongoDB back-end hosted on MongoLab for mine. In the session store, for each session, you can have a unique username, or some other identifier that goes along with that session. Really, you can tack any data you want to that session. That's how you'd tell them apart.
The code I use looks like this:
module.exports = function (socket) {
socket.on('taste:cookie', function (data, callback) {
console.log("taste:cookie function running");
//get the session ID
var sid = data.sid;
sid = parseSignedCookie(sid['connect.sid'], "mySecret");
console.log("sid: ",sid);
//get the handshake cookie
var hssid = cookie.parse(socket.handshake.headers.cookie);
hssid = parseSignedCookie(hssid['connect.sid'], "mySecret");
console.log("hssid: %s",hssid);
if(sid) {
if(sid['connect.sid']) {
sid = sid['connect.sid'].slice(2);
console.log("sliced the sid: %s",sid);
sid = cookieSig.unsign(sid, "mySecret");
hssid = sid;
}
if(hssid != sid) {
console.log("browser cookie not set right; rectifying...");
data.sid = hssid;
sid = hssid;
}
else console.log("browser cookie was set right");
}
thisApp.thisStore.get(sid, function(err, gotsession) {
if(err || !gotsession) {
//handle error
return;
} else {
if(gotsession.username) {
callback(0, {username:gotsession.username});
}
else callback(1, {username:""});
}
});
});
Maybe there's a more elegant way to do this, but this does work.
You can use session+cookies: Here's a library that you can use or learn from: session-socket.io.
You'll find plenty of examples on their README page.

Creating a map of ids to sockets and vice versa in Node.js

I'm trying to manage a bunch of socket connections. My app is basically an http server that receives posts and passes these along to a socket. When clients open a socket connection, they send a connect message with an id:
{"m":"connect","id":"1"}
The app then saves this id and socket in the id2socket and socket2id maps. On disconnect, the socket/id pair is deleted from the maps.
A post will also contain an id, which indicates the post data should be sent to the socket with that id.
That's great, and this works fine for a single open socket. However, when I have more than one socket open, and then I close a socket, that disconnect wipes everything from the map. I think my understanding of sockets in node is incomplete- is there only a single socket object that is used in the callback? Is there a better way to manage my open socket connections and ids?
start server:
>>node server.js
TCP server listening on 127.0.0.1:5280
HTTP server listening on 127.0.0.1:9002
telnet in:
>>telnet localhost 5280
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{"m":"connect","id":"123"}
{"m":"connect","id":"123","success":"true"}
server after connection:
>>Connection from 127.0.0.1:57572
received data: {"m":"connect","id":"123"}
id: 1
m: connect
associating uid 1 with socket [object Object]
do a post:
python post.py {"foo":"bar"}
So this works fine for several open sockets (as long as 1 device is id 123, server has this hardwired for now). However, as soon as you close one connection all the socket connections are removed from the map.
Here's my code:
python script to do post:
import sys
import json
import httplib, urllib, urllib2
values = json.loads('{"foo":"bar"}')
headers = {"Content-type": "application/json"}
conn = httplib.HTTPConnection('127.0.0.1', 9002)
headers = {"Content-type": "application/json"}
conn.request("POST", "", json.dumps(values), headers)
response = conn.getresponse()
print "response.status: "+response.status
print "response.reason: "+response.reason
print "response.read: "+response.read()
conn.close()
node server (http and tcp), hardwired to send data to device '123' on post:
var net = require('net'); // tcp-server
var http = require("http"); // http-server
var qs = require('querystring'); // http-post
// Map of sockets to devices
var id2socket = new Object;
var socket2id = new Object;
// Setup a tcp server
var server_plug = net.createServer(function(socket) {
// Event handlers
socket.addListener("connect", function(conn) {
console.log("Connection from " + socket.remoteAddress + ":" + socket.remotePort );
});
socket.addListener("data", function(data) {
console.log("received data: " + data);
try {
request = JSON.parse(data);
response = request;
if(request.m !== undefined && request['id'] !== undefined){ // hack on 'id', id is js obj property
console.log("id: "+request['id']);
console.log("m: "+request.m);
if(request.m == 'connect'){
console.log("associating uid " + request['id'] + " with socket " + socket);
id2socket[request['id']] = socket;
socket2id[socket] = request['id'];
response.success = 'true';
} else {
response.success = 'true';
}
}
socket.write(JSON.stringify(response));
} catch (SyntaxError) {
console.log('Invalid JSON:' + data);
socket.write('{"success":"false","response":"invalid JSON"}');
}
});
socket.on('end', function() {
id = socket2id[socket]
console.log("socket disconnect by id " + id);
// wipe out the stored info
console.log("removing from map socket:"+socket+" id:"+id);
delete id2socket[id];
delete socket2id[socket];
});
socket.on('timeout', function() {
console.log('socket timeout');
});
});
// Setup http server
var server_http = http.createServer(
// Function to handle http:post requests, need two parts to it
// http://jnjnjn.com/113/node-js-for-noobs-grabbing-post-content/
function onRequest(request, response) {
request.setEncoding("utf8");
request.addListener("data", function(chunk) {
request.content += chunk;
});
request.addListener("end", function() {
console.log("post received!");
//console.log("Request received: "+request.content);
if (request.method == 'POST') {
//var json = qs.parse(request.content);
//console.log("Post: "+json);
// HACK TO TEST STUFF:
// send a message to one of the open sockets
try {
var socket = id2socket['123']; //hardwired
socket.write('{"m":"post"}');
} catch (Error) {
console.log("Cannot find socket with id "+'123');
}
}
});
}
);
// Fire up the servers
var HOST = '127.0.0.1';
var PORT = 5280;
var PORT2 = 9002;
server_plug.listen(PORT, HOST);
console.log("TCP server listening on "+HOST+":"+PORT);
server_http.listen(PORT2);
console.log("HTTP server listening on "+HOST+":"+PORT2);
Objects only take strings as keys for their properties. As your log shows, a socket object is converted into the string "[object Object]". As a result, socket #2 overwrites the id from socket #1 in the object, because all sockets are converted into the same string key. So, there is only one property in the object at all times, because all sockets come down to the same key. When you try to remove the id for socket #2, the single property is deleted and the object is empty.
You seem to want a custom property for each separate socket when used as a key. You can use WeakMaps for this. WeakMaps do allow objects as keys (as opposed to string-only keys), but as they're relatively new they may contain bugs at the moment.
(Note that the id2socket map can just be a plain object, because numbers are converted into strings just fine, and each number has its own, distinct string representation*.)
Using WeakMaps is as follows:
var socket2id = new WeakMap; // as if you were doing: var socket2id = {};
socket2id.set(socket, id); // as if you were doing: socket2id[socket] = id;
socket2id.get(socket); // as if you were doing: socket2id[socket];
socket2id.delete(socket); // as if you were doing: delete socket2id[socket];
Make sure to run with node --harmony (>= 0.7) or node --harmony_weakmaps (<= 0.6).
* 0 and -0 are exceptions, but you shouldn't be using -0 anyway because 0 === -0, so it's difficult to differ between them.

Categories

Resources