I have an asp.net mvc 4 app that uses SignalR.
When an user is connected I want to notify everyone of that (sending to "all" for the moment just to test it). In my Hub clas I have this:
public override System.Threading.Tasks.Task OnConnected()
{
NotifyAllOfUserLogin();
return base.OnConnected();
}
In the _layout.cshtml, I have this:
<script type="text/javascript">
$(document).ready(function () {
var proxy = $.connection.messagehub;
proxy.client.messageAll = function (message) {
$('#messages').prepend('<p style=\'white-space:pre;\'>' + message + '</p><br />');
};
$("#btnSubmitMessage").click(function () {
proxy.server.messageAll($("#txtMessage").val());
$("#txtMessage").val('');
});
$.connection.hub.start();
});
</script>
While this works, I think writing this in the masterpage is a mistake, since the hub connection will be reinitialized for every page that inherits the master, so OnConnected will be called a lot of times.
How should I deal with this properly, calling OnConnect only when the user logs into the application, and onDisconnected when the user logs out?
For every page-load, a new connection is made. When that page is closed, similarly that specific connection is closed.
What you will want to do is keep track of the user's connection ID's on the server-side. You will need to keep track of the active connections related to each specific user account, and also when they disconnect. Doing so, you can notify all users that a person connected if there are no pre-existing active connection ID's related to that user.
Related
How can I manage to have only one connection of signalR per user in different browsers/tabs? so if user is active in one tab/browser and wants to open another tab/browser I have to alert him to first close the previous tab/browser. like Whatsapp.
I have implemented this so far but I don't know how to fill the commented par?!
public override async Task OnConnectedAsync()
{
bool isUserExisted = _onlineUsers.TryAdd(Context.User.FindFirst("username").Value, Context.ConnectionId);
if (!isUserExisted)
{
//avoid to connect the already connected user
//and send alert to him
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
_onlineUsers.TryRemove(Context.User.FindFirst("username").Value, out _);
await base.OnDisconnectedAsync(exception);
}
Any help would be appreciated.
I'm not an expert on the client side, but I assume you cannot maintain the same SignalR connection from different tabs because each tab has its own context and establishes its own connection.
You can, however, on the server side create a user group, where you would pack all connections of the current user:
var user = GetUser(username); // Get your user from context
await this.Groups.AddToGroupAsync(this.Context.ConnectionId, GetUserGroupName(user)); // Generate unique group name per user and add all user connections to it
Than you can play with whom you send your events, e.g. send to all user connections but for current one (i.e. but for current tab):
this.ctx.Clients.GroupExcept(groupName, currentConnectionId);
I'm not sure when alerting should occur? Do you call a REST method on the server? Or directly a SignalR method? If it's an independent REST method, you could pass your current SignalR connection id in a custom header and read it in the hub.
I am looking to open a task module (Pop up - iframe with audio/video) in my bot that is connected to Teams channel. I am having issues following the sample code provided on the GitHub page.
I have tried to follow the sample and incorporate to my code by did not succeed.
In my bot.cs file I am creating card action of invoke type:
card.Buttons.Add(new CardAction("invoke", TaskModuleUIConstants.YouTube.ButtonTitle, null,null,null,
new Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>()
{
Data = TaskModuleUIConstants.YouTube.Id
}));
In my BotController.cs that inherits from Controller
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Invoke)
{
return HandleInvokeMessages(activity);
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
private HttpResponseMessage HandleInvokeMessages (Activity activity)
{
var activityValue = activity.Value.ToString();
if (activity.Name == "task/fetch")
{
var action = Newtonsoft.Json.JsonConvert.DeserializeObject<Teams.Samples.TaskModule.Web.Models.BotFrameworkCardValue<string>>(activityValue);
Teams.Samples.TaskModule.Web.Models.TaskInfo taskInfo = GetTaskInfo(action.Data);
Teams.Samples.TaskModule.Web.Models.TaskEnvelope taskEnvelope = new Teams.Samples.TaskModule.Web.Models.TaskEnvelope
{
Task = new Teams.Samples.TaskModule.Web.Models.Task()
{
Type = Teams.Samples.TaskModule.Web.Models.TaskType.Continue,
TaskInfo = taskInfo
}
};
return msg;
}
return new HttpResponseMessage(HttpStatusCode.Accepted);
}
There is more code as per the GitHub sample but I won't paste it here. Can someone point me into the correct direction ?
I have got to the stage that it is displaying a pop up window but the content and title comes from manifest file instead of creating actual iframe also no video is rendering. My goal is to render video within my teams using iframe container.
The important part from the sample:
This sample is deployed on Microsoft Azure and you can try it yourself by uploading Task Module CSharp.zip to one of your teams and/or as a personal app. (Sideloading must be enabled for your tenant; see step 6 here.) The app is running on the free Azure tier, so it may take a while to load if you haven't used it recently and it goes back to sleep quickly if it's not being used, but once it's loaded it's pretty snappy.
So,
Your Teams Admin MUST enable sideloading
Your bot MUST be sideloaded into Teams
The easiest way to do this would be download the sample manifest, open it in App Studio, then edit your bot information in. You then need to make sure Domains and permissions > Valid Domains are set for your bot. Also ensure you change the Tabs URLs to your own.
You also need to make sure that in your Tasks, the URLs they call ALL use https and not http. If anywhere in the chain is using http (like if you're using ngrok and http://localhost), it won't work.
I'm using SignalR 1 with MVC4 C# web application with form authentication.
I have a code in my layout page in JavaScript :
$(documnet).ready(function(){
connect to hub code ...
})
I want to disconnect a user form the hub and start connect again after he does a login and validate ok.
I want to do it from server side inside my account controller and method :
public ActionResult LogOn(LoginModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
....here , disconnect from hub
....to make the user reconnect
}
The reason I want to do it is because SignalR throws an error if user changed to authenticated after login and the connection remains . The error is:
The connection id is in the incorrect format.
You cannot stop and start SignalR connections from the server. You will need to call
$.connection.hub.stop(); //on the client before the user attempts to log on and then call
$.connection.hub.start(); //after the log on attempt has completed.
One way you could do what you ask is to write a disconnect event on your client that the server can call through SignalR. Maybe something somewhat like this:
myHub.client.serverOrderedDisconnect = function (value) {
$.connection.hub.stop();
};
Then, on the server, something like this:
Clients.Client(Context.ConnectionId).serverOrderedDisconnect();
If someone is still looking for solution(SignalR version 2.4.1):
GlobalHost.DependencyResolver.Resolve<ITransportHeartbeat>().GetConnections().First(c => c.ConnectionId == "YourId").Disconnect();
Try controlling everything from javascript. The following is a logout example, login would be similar.
From http://www.asp.net/signalr/overview/security/introduction-to-security:
If a user's authentication status changes while an active connection
exists, the user will receive an error that states, "The user identity
cannot change during an active SignalR connection." In that case, your
application should re-connect to the server to make sure the
connection id and username are coordinated. For example, if your
application allows the user to log out while an active connection
exists, the username for the connection will no longer match the name
that is passed in for the next request. You will want to stop the
connection before the user logs out, and then restart it.
However, it is important to note that most applications will not need
to manually stop and start the connection. If your application
redirects users to a separate page after logging out, such as the
default behavior in a Web Forms application or MVC application, or
refreshes the current page after logging out, the active connection is
automatically disconnected and does not require any additional action.
The following example shows how to stop and start a connection when
the user status has changed.
<script type="text/javascript">
$(function () {
var chat = $.connection.sampleHub;
$.connection.hub.start().done(function () {
$('#logoutbutton').click(function () {
chat.connection.stop();
$.ajax({
url: "Services/SampleWebService.svc/LogOut",
type: "POST"
}).done(function () {
chat.connection.start();
});
});
});
});
You can the hub context to abort() the connection directly in .Net Core
Context.Abort();
See the method below
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.signalr.hubcallercontext.abort
Try this:
public ActionResult LogOn(LoginModel model, string returnUrl) {
if (ModelState.IsValid) {
if (System.Web.Security.Membership.ValidateUser(model.UserName, model.Password)) {
FormsAuthentication.SetAuthCookie(model.UserName, false);
connection.Stop();
}
}
Assuming your connection handle is connection. The challenge is accessing a handle to your connection object in your Action Method.
Copy and paste the following function into your Hub
Use HttpContext.Current.Response.End();
to force the client to disconnect in your hub
The goal of my simple try is to display online user list. I mean display not socket.io ID but display user profile. When authorized user connects to my server, open socket.io channel, it is required to get his profile and send message to other connected user that new user (Name, email, etc) has being connected. I saw many examples how to do it within authorization, but it doesn't handle a disconnect. What i want to do and what i can't do in pseudocode:
var io = require("socket.io").listen(server);
io.set("authorization", function(data, callback){
// ... some code...
callback(null, true);
});
io.sockets.on('connection', function (socket) {
var UserProfile = passport.getUserProfile(socket.id)
io.sockets.emit('user_connected', {UserProfile: UserProfile, socketID: socket.id});
io.sockets.on('disconnect', function (socket) {
io.sockets.emit('user_disconnected', {socketID: socket.id});
});
});
This is a pseudocode!
My stack is overflowed. I just want to link socket.io ID and passport account together within connection. How can i do it?
I got the same problem, and my solution is the following (hopefully someone gets a better idea):
i add the username to the render call (using jade):
res.render('chatroom', {username: req.user.username});
right after connecting to socket on the client (io.connect), i emit a
message to the server, with the username as parameter, using the connect event on the client (socket.on('connect', function (data) { ... });
on the server, i store the username in an object (clients[socket.id]
= username)
after that, i get the username in every socket message by accessing
the clients object
I'm working on a small multiplayer game. I'd like to introduce authentication. I'm using Node.js and Socket.io.
When the user arrives that the main page - I want them to join the game whether they are logged in or not - but they will be unable to do anything within it (only watch).
How could I then go about authenticating the user on the already open socket?
Could I maintain the authentication still if they left the site and came back? Can you pass a cookie through a web socket?
EDIT
To further my question. One of the possible thoughts I've had is to provide the websocket connection, then when they try to login in, it passes username and password as a message to the websocket.
client.on('onLogin', loginfunction);
I could then take the username and password, check against the database, then take the session ID of the socket and pass it somewhere to say that session is authenticated to that user.
Is this secure? Could I still implement a cookie on the socket so they could come back? Is there any way within socket.io of stating that the socket is now authenticated instead of manually checking on each message received?
Cheers
This isn't actually too hard, but you're approaching it the wrong way. A couple things:
You cannot set a cookie with socket.io; you can, however, get the cookie values of any connected client at any time. In order to set a cookie, you will have to send a new http response, meaning the user must first send a new http request (aka refresh or go to a new page, which it sounds is not a possibility for you here).
Yes: socket.io is secure (to the extent that any transmitted data can be).
As such, you can do the following:
On the user's initial connection, create a cookie with a unique session ID, such as those generated from Express's session middleware. You will need to configure these not to expire on session end though (otherwise it will expire as soon as they close their browser).
Next you should create an object to store the cookie session IDs. Each time a new connect.sid cookie is set, store in your new object with a default value of false (meaning that the user has been authenticated by session, but not by logon)
On the user's login, send a socket emit to the server, where you can then authenticate the login credentials, and subsequently update the session id object you created to read true (logged in) for the current socket id.
Now, when receiving a new http request, read the cookie.sid, and check if its value in your object is true.
It should look something like the following:
var express = require('express'),
http = require('http'),
cookie = require('cookie');
var app = express();
var server = http.createServer(app);
var io = require('socket.io').listen(server);
app.use(express.cookieParser());
app.use(express.session({
secret: 'secret_pw',
store: sessionStore,
cookie: {
secure: true,
expires: new Date(Date.now() + 60 * 1000), //setting cookie to not expire on session end
maxAge: 60 * 1000,
key: 'connect.sid'
}
}));
var sessionobj = {}; //This is important; it will contain your connect.sid IDs.
//io.set('authorization'...etc. here to authorize socket connection and ensure legitimacy
app.get("/*", function(req, res, next){
if(sessionobj[req.cookies['connect.sid']]){
if(sessionobj[req.cookies['connect.sid']].login == true){
//Authenticated AND Logged in
}
else{
//authenticated but not logged in
}
}
else{
//not authenticated
}
});
io.sockets.on('connection', function(socket){
sessionobj[cookie.parse(socket.handshake.headers.cookie)['connect.sid'].login = false;
sessionobj[cookie.parse(socket.handshake.headers.cookie)['connect.sid'].socketid = socket.id;
socket.on('login', function(data){
//DB Call, where you authenticate login
//on callback (if login is successful):
sessionobj[cookie.parse(socket.handshake.headers.cookie)['connect.sid']] = true;
});
socket.on('disconnect', function(data){
//any cleanup actions you may want
});
});
Chris, I'm won't be able to answer since I'm not an expert on socket.io, but I can maybe try to point you in another direction that can help you - and take away some development time.
But first, a disclaimer: I work for Realtime.co and am not trying to do any sort of advertising. I work closely with developers and I'm just trying to help you by providing you an out-of-the-box solution for your problem. Also, being a gamer, I can't stay away from trying to help people getting their games out there!
Realtime uses an authentication/authorization layer in which you can provide user read/write permissions to channels. When users enters the website you can give them read only permissions to the game channel and once they login, you can then give them write permissions. This can be easily done by doing an authentication post and reconnecting to the server (it can all be done client side). I would do it server-side, though, to increase security.
Realtime has a Node.js API so you can easily integrate it with your server. Since it also has APIs for many other platforms (including mobile) and they all work the same way, you can actually have your game working in multiple platforms over the same communication layer, while having full control over channels.
Thanks for reading.
Edit:
You can read the documentation here for more info: http://docs.xrtml.org/
Socket.io has an option to pass extraHeaders. One can use that to pass a token from the client. The server would use the desired authentication algorithm to decrypt the token and get the user_id.
socket.js
import io from 'socket.io-client';
export const socket = io('https://remote-url');
export const socketAuth = () => {
socket.io.disconnect(); //This uses the same socket and disconnect with the server.
socket.io.opts.extraHeaders = {
'x-auth-token': JSON.parse(window.localStorage.getItem('auth-token')),
};
socket.io.opts.transportOptions = {
polling: {
extraHeaders: {
'x-auth-token': JSON.parse(window.localStorage.getItem('auth-token')),
},
},
};
socket.io.open(); //Opens up a new connection with `x-auth-token` in headers
};
client.js
import { socket, socketAuth } from 'utils/socket';
socket.on('unauthenticated-messages', () => {
someAction();
});
//After user logs in successfully
socketAuth();
socket.on('authenticated-messages', () => {
someAction();
});