I use websocket-sharp ( https://github.com/sta/websocket-sharp ) (serveur side only).
I can have many clients applications connected to my websocket server. Each client send an ID (let's call it CLIENT_ID).
I want to know which websocket session is related to CLIENT_ID.
IWebSocketSession expose ID (let's call it SESSION_ID).
What I have tried:
Firstly I have a storage class which store for each SESSION_ID his CLIENT_ID (a simple dictionary).
When I receive a message from a client application, i store SESSION_ID and CLIENT_ID in my dictionary.
So when i want to send a message to all sessions having CLIENT_ID == XXX i can use this dictionary. This works fine ...
BUT sessions are only temporary. A client can use multiple sessions. So for a single client, I will soon have many inputs in my dictionary. When i send a message to all sessions in my dictionnary having CLIENT_ID == XXX, I will send the same message to the same client multiple time.
My question is : How to register unique clients with websocket-sharp ? Which property should I use ?
Edit : Even with a unique client, every 20s it changes ID of the session. It's probably a new session created for ping.
Ok as I find nothing, I kept SESSION_ID but when i register a new item in my dictionary (SESSION_ID => CLIENT_ID) I have to manually delete all "old sessions".
This can be done by checking if session.State == WebSocketState.Open.
It's not perfect but it works.
Related
So I want to send a message to a specific client via SignalR. That client is not Clients.Caller - currently I can only identify it by let's call it "ID", a property in the context: this.Context.Items["ID"]
So to find a client by its ID, how do I...access all clients or contexts? Or should I save this ID in a different way? This is not the connection ID, it is an ID that maps to something in the database.
Basically I'd like to go Clients.Single(c => c.Items["ID"] == specificId).SendMessage(msg);
In the OnConnectedAsync method in your hub, you can group a user by their "ID" each time they connect.
E.G:
public async override Task OnConnectedAsync()
{
var currentUserId = /*get your current users id here*/
await Groups.AddToGroupAsync(Context.ConnectionId, $"user-{currentUserId}");
await base.OnConnectedAsync();
}
Then when you need to send that person a message, you can just broadcast it to their 'personal' group. (One person may have multiple connections, so the group approach work perfectly for us).
E.G:
Clients.Group($"user-{userId}").SendAsync("UserSpecificMessage", message);
When users disconnect, SignalR will handle the removing of connections from the groups automatically.
In using this approach, we do not have to unnecessarily broadcast a message to every single client with the intention of only one client filtering out the message based on the id.
You can send the ID down to the clients using context.Clients.User(...) or context.Clients.All(). Then in JavaScript, read the ID and compare it to what's on the page. If it's a match, carry out some action; else ignore.
As an example, let's say your app's processing a specific record on an edit screen. The record ID is on the page as a form field. You send a SignalR message down from C# with the ID. You do a comparison in JavaScript between the ID and the form field value; if they match, you display a toaster message, perform other processing, etc.
We use Azure Notifications Hub to manage notifications registrations. Every time user launches application, we call PushNotificationChannelManager.CreatePushNotificationChannelForApplicationAsync and then RegisterNativeAsync of NotificationHub to register channel uri, returned by first with some tags like "Username" and "InstallId" - that is unique per app installation. Then from back-end we send notifications using these tags.
But we have noticed problem - when user hard-resets device, the previous channel registration stays active in notification hub. In that case user receives duplicate notifications by his "Username" tag. "InstallId" doesn't help in that case, as it is changing with new app installation.
We have thought about managing channels server-side. But that will not solve the problem.
Maybe anyone has some suggested work-around?
Also, we don't know what information does PushNotificationChannelManager use when creating new or returning existing channel? Does it use some device information?
I think you can send the backend the device unique Id along with the installation Id. The device id will not change upon hard reset.
private string GetDeviceUniqueID()
{
HardwareToken token = HardwareIdentification.GetPackageSpecificToken(null);
IBuffer hardwareId = token.Id;
HashAlgorithmProvider hasher = HashAlgorithmProvider.OpenAlgorithm("MD5");
IBuffer hashed = hasher.HashData(hardwareId);
string hashedString = CryptographicBuffer.EncodeToHexString(hashed);
return hashedString;
}
I'm building a fairly simple single page app. It's basically a list of items, where each item has some details, an activity log, and a current status along with some buttons to trigger actions on the server to advance the status along a workflow.
It was originally written using MVC and REST/Web API but I got stuck on the problem of keeping concurrent users up to date. For example, if User A adds an item, we want the list on User B's screen to now update to include it.
To solve this I looked into SignalR which works great. But I had a problem.
When adding an item (using POST) the callback adds the item on the requesting client. This is fine.
I then triggered a SignalR broadcast on the server to tell all clients about the new item. This worked fine except the local client, who now has 2 items.
I was looking into filtering the duplicate id client-side, or sending the connection id with the POST, then broadcast to all clients except the requester but it seems a bit needlessly complicated.
Instead I'm just doing this.
public class UpdateHub : Hub
{
public void AddNewItem(NewItem item)
{
// and some server-side stuff, persist in the data store, etc
item.trackingID = new Guid();
item.addLogEntry("new item");
// ...
dataStore.addItem(item);
// send message type and data payload
Clients.All.broadcastMessage("add", item);
}
}
It seems a lot simpler to just get rid of all the REST stuff altogether, so am I missing anything important?
It'll run on an intranet for a handful of users using IE11+ and I guess we do lose some commonly-understood semantics around HTTP response codes for error handling, but I don't think that's a huge deal in this situation.
In order to solve duplicate you can try to use Clients.Others inside Hub class, or AllExcept(id) if you not in the Hub class.
Clients.Others.broadcastMessage("add", item);
In your case using SignalR shouldn`t have any downsides.
When every I attempt to add a new server callback function I cannot seem to get the callback to show up in the $.connection server callback list.
What do I need to do to refresh the javascript that SignalR produces and sends to the client with the latest list of server callbacks.
I would think that I should be able to just add a server callback for the client to call and rebuild my app and fire up a new instance of Google Chrome and the newly added server callback would be in the list of available callbacks on the client.
For example here is exactly what I've done.
1.) A client joins a group and everyone is notified.
public override Task OnConnected()
{
string pid = this.Context.QueryString["pid"],
uid = this.Context.QueryString["uid"],
ispro = this.Context.QueryString["ispro"];
Groups.Add(this.Context.ConnectionId, pid);
return Clients.Group(pid).joined(new cmsg(Context.ConnectionId, UtilCommon.GetUserMini(new Guid(uid), bool.Parse(ispro))));
}
2.) On the client the joined function is called from the server
this.collaborateHub.client.joined = function (cmsg) {
//
that.chatBox.addAttendee(cmsg);
//let the new attendee know about you
if (cmsg.cnnid !== that.chatBox.getMeAttendee().cnnid) {
that.chatBox.getMeAttendee().newbieid = cmsg.cnnid;
debugger
this.server.addMeNewbie(that.chatBox.getMeAttendee());
};
};
3.) Now, if someone joined and it was not the user of the currently opened window, that means the someone how just joined is someone other than myself, so I need to call the server back and notify the newly joined user to add me to their user list. I stead of having to keep up with the currently signed on users to the given group in a database, I am just using every signed on client of the group as a distributed database and let them manage their own info.
So, I need to call the server callback named addMeNewbie; however, this callback function is not available in the list.
public Task addMeNewbie(cmsg cmsg) {
return Clients.Client(cmsg.newbieid).addCurrAttendee(cmsg);
}
Here is a snapshot of the client side in debug mode
4.) And finally here is the client side callback that i need the server to call so that the newly joined group member can update their list of current group members.
this.collaborateHub.client.addCurrAttendee = function (cmsg) {
debugger
that.chatBox.addAttendee(cmsg);
};
I would think that I should be able to add as many server callbacks as I want and they should show up on the next build and restart of a new instance of Google Chrome; however, this is not the case. What do I need to do to get newly added server callbacks to show up in the available server callbacks on the client side?
This doesn't make since to me, but I am thinking maybe this is a cashing issue or something?
The only callback that is available is the one shown in the snapshot provided. At this time I've only got two callbacks, the one that you see in the snapshot and the one that is not visible.
Somewhere along the way I created a signalr-hubs.js file from the generated JavaScript file that can be retrieved by http://localhost:3250/signalr/hubs and was adding new server functions to my hub but not updating the signalr-hugs.js file.
At this point every time I add a new server callback I retrieve the newly generated proxy by placing this url http://localhost:3250/signalr/hubs into the browser, coping the code, and then updating my signalr-hubs.js file.
I have a webshop running which contains parts of cars. The prices next to the parts are loaded from a webservice running else where. This webservice only contains one webmethod: GetArticleInformation.
In this webservice there is a link to another webservice WebshopServiceClient running elsewhere which contains the info about the cars and holds the prices.
Now when a user select a part of the vehicle he wants to buy the first webservice is called and the method GetArticleInformation is executed. In this method I want to create a session which hold the logon of the second webservice ( the database ). In this way I want to prevent that for every call a new logon is required.
[WebMethod(EnableSession = true)]
public GetBackItems GetArticleInformation(User user, Items items)
{
//Create session if needed
client = (WebshopServiceClient)Session["SphinxLogon"];
if (client == null)
{
client = new WebshopServiceClient();
bool done = client.Logon();
if (done)
{
Session["SphinxLogon"] = client;
}
}
//Get information and send it back
...
}
Now when the user in the webshop selects a part the session is created but the next time the user selects a part the session is null again.
What am I doing wrong or what am I missing?
I would consider 'talking' to the various web-services via an internal 'proxy'-procedure -- fired up on app-start for example -- which would handle all traffic etc with the services. That way the individual client sessions do not have to logon or maintain a session with the services but can still be managed via the proxy. Individual clients would get a 'ticket' from the proxy which then could be part of their session and could be used to manage it.