I'm sending by ajax post request to a method in the controller a string 'userName' that I should kick.
Is it possible to remove the user from current hub calling the method in the controller?
public ActionResult Kick(string userName)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
var user = userService.GetUserByName(userName);
var room = chatRoomService.GetRoomById(user.ChatRoomId.Value);
user.IsKicked = true;
userService.LeaveRoom(user);
hubContext.Groups.Remove(user.ConnectionIdInHub, room.Name);
return Json(new {success = true});
}
Could i somewhere in this method disconnect user from hub?
Server Side-
You should store user's connection ID at the time of his connection.
Like this in server side-
public override Task OnConnected()
{
Boolean isFoundAnydevice = false;
if(receivedClientId.Length>0) //With Param
{
int noOfSelectedDevice = _context.TargatedDevice.Where(x => x.PhoneId == receivedClientId).Count();
if (noOfSelectedDevice > 0)
isFoundAnydevice = true;
}
else //With no Param
{
String deviceId = _context.Device.Where(d => d.ConnectionId == this.Context.ConnectionId).Select(d => d.ClientId).SingleOrDefault();
int noOfSelectedDevice = _context.TargatedDevice.Where(x => x.PhoneId == deviceId).Count();
if (noOfSelectedDevice > 0)
isFoundAnydevice = true;
}
if (isFoundAnydevice)
{
_logger.LogWarning(
receivedClientId + " added to Test group"
);
Groups.Add(this.Context.ConnectionId, testGroupName);
}
return base.OnConnected();
}
Then you can easily find the user's connection ID from DB.
Now you can easily stop the hub connection like this-
public Task Disconnect(string connectionId)
{
try
{
lock (_lock)
{
var connections = _registeredClients.Where(c => c.Value.Any(connection => connection == connectionId)).FirstOrDefault();
// if we are tracking a client with this connection
// remove it
if (!CollectionUtil.IsNullOrEmpty(connections.Value))
{
connections.Value.Remove(connectionId);
// if there are no connections for the client, remove the client from the tracking dictionary
if (CollectionUtil.IsNullOrEmpty(connections.Value))
{
_registeredClients.Remove(connections.Key);
}
}
}
}
catch (Exception ex)
{
Log.Error(this, "Error on disconnect in hub", ex);
}
return null;
}
More can be found in here.
Client Side-
If you like to do it from client side, you can do this-
$.connection.hub.stop();
Hope you have your answer
Related
Question:
Why am I being able to send messages to all users but not to a specific user? Am I missing something obvious here?
Problem:
My POC uses the following resources:
The official Spring Boot tutorial for sending messages to all users
This Baeldung tutorial for sending messages to specific users
I am currently using two endpoints:
The first one sends messages to all users and works fine.
Send to all users - working
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public StompResponse greeting(StompRequest message) throws Exception {
Thread.sleep(1000); // simulated delay
return new StompResponse("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
The second one doesn't work. The method sendSpecific never gets called and I'm stuck trying to understand why.
Send to specific user - NOT working
/**
* Example of sending message to specific user using 'convertAndSendToUser()' and '/queue'
*/
#MessageMapping(Constants.SECURED_CHAT_ROOM)
public void sendSpecific(#Payload StompRequest message, Principal user, #Header("simpSessionId") String sessionId) throws Exception {
System.out.println("METHOD CALLED"); // This method never gets called
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
StompResponse out = new StompResponse(HtmlUtils.htmlEscape(message.getName()));
simpMessagingTemplate.convertAndSendToUser("hardcoded_username", Constants.SECURED_CHAT_SPECIFIC_USER, out, headerAccessor.getMessageHeaders());
}
Socket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", Constants.SECURED_CHAT_SPECIFIC_USER);
config.setUserDestinationPrefix("/secured/user");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/connection-uri").withSockJS();
registry.addEndpoint(SECURED_CHAT_ROOM).withSockJS();
}
}
Client code:
const SocketConnection = function(l) {
const listener = l;
let sessionId;
const parseSessionId = (url) => {
console.log(stompClient.ws._transport.url);
url = url.replace("ws://localhost:8080" + Uri.CONNECTION_URI + "/", "");
url = url.replace("/websocket", "");
url = url.replace(/^[0-9]+\//, "");
console.log("Your current session is: " + url);
return url;
};
this.connect = () => {
const socket = new SockJS(Uri.CONNECTION_URI);
stompClient = Stomp.over(socket);
stompClient.connect({}, (frame) => {
listener.onSocketConnect(true);
console.log('Connected: ' + frame);
stompClient.subscribe(Uri.ALL_USERS, function (greeting) {
listener.onResponseReceived(JSON.parse(greeting.body).content);
});
// WIP
const url = stompClient.ws._transport.url
sessionId = parseSessionId(url);
console.log('SUBSCRIBING FOR SPECIFIC USER: ', Uri.SECURED_CHAT_SPECIFIC_USER)
// stompClient.subscribe(Uri.SECURED_CHAT_SPECIFIC_USER + sessionId, function (msgOut) {
stompClient.subscribe(Uri.SECURED_CHAT_SPECIFIC_USER + "-user" + sessionId, function (msgOut) {
// that.messageOut(JSON.parse(msgOut.body), opts);
console.log('SINGLE USER WORKS!!!!!!!',JSON.parse(msgOut.body))
});
});
}
this.disconnect = () => {
if (stompClient !== null) {
stompClient.disconnect();
}
listener.onSocketConnect(false);
console.log("Disconnected");
}
this.sendRequest = (uri) => {
stompClient.send(uri, {}, JSON.stringify({'name': $("#name").val()}));
}
};
Client initialization:
$(function () {
const socketClient = new SocketConnection(new FormUI());
$("form").on('submit', function (e) {
e.preventDefault();
});
$( "#connect" ).click(function() { socketClient.connect(); });
$( "#disconnect" ).click(function() { socketClient.disconnect(); });
$( "#send" ).click(function() {
socketClient.sendRequest(Uri.ALL_USERS);
socketClient.sendRequest(Uri.SECURED_CHAT_ROOM);
});
});
I am working on a very simple project where a user sends a private message to another through the server using the SignalR library. I used this code as a base https://www.codeproject.com/Articles/562023/Asp-Net-SignalR-Chat-Room
I started with an easy functionality test but my recipient is not receiving the message and It doesn't work properly, can you help me understand why?
In my program usernames are generated dynamically in the session, those in the code below are not real data, I just used them for the example
Client side
</script>
<script type="text/javascript" src="/Scripts/jquery-1.6.4.min.js"></script>
<script type="text/javascript" src="/Scripts/jquery.signalR-1.2.2.js"></script>
<script type="text/javascript" src="/signalr/hubs"></script>
<script type="text/javascript">
$(function () {
var chatHub = $.connection.chatHub;
console.log('connected')
chatHub.client.messageReceived = function (userName, message) {
alert("You have a new message");
}
$.connection.hub.start().done(function () {
chatHub.server.connect('FromUsername');
$('#btnSend').click(function () {
var userId='ToUsername'
var msg = 'Test';
chatHub.server.sendPrivateMessage(userId, msg);
});
});
});
</script>
Server side
public class ChatHub : Hub
{
#region Data Members
static List<UserDetail> ConnectedUsers = new List<UserDetail>();
static List<MessageDetail> CurrentMessage = new List<MessageDetail>();
#endregion
#region Methods
public void Connect(string userName)
{
var id = Context.ConnectionId;
if (ConnectedUsers.Count(x => x.ConnectionId == id) == 0)
{
ConnectedUsers.Add(new UserDetail { ConnectionId = id, UserName = userName });
// send to caller
Clients.Caller.onConnected(id, userName, ConnectedUsers, CurrentMessage);
// send to all except caller client
Clients.AllExcept(id).onNewUserConnected(id, userName);
}
}
public void SendMessageToAll(string userName, string message)
{
// store last 100 messages in cache
AddMessageinCache(userName, message);
// Broad cast message
Clients.All.messageReceived(userName, message);
}
public void SendPrivateMessage(string toUserId, string message)
{
string fromUserId = Context.ConnectionId;
var toUser = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == toUserId) ;
var fromUser = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == fromUserId);
if (toUser != null && fromUser!=null)
{
// send to
Clients.Client(toUserId).sendPrivateMessage(fromUserId, fromUser.UserName, message);
// send to caller user
Clients.Caller.sendPrivateMessage(toUserId, fromUser.UserName, message);
}
}
public override System.Threading.Tasks.Task OnDisconnected()
{
var item = ConnectedUsers.FirstOrDefault(x => x.ConnectionId == Context.ConnectionId);
if (item != null)
{
ConnectedUsers.Remove(item);
var id = Context.ConnectionId;
Clients.All.onUserDisconnected(id, item.UserName);
}
return base.OnDisconnected();
}
#endregion
#region private Messages
private void AddMessageinCache(string userName, string message)
{
CurrentMessage.Add(new MessageDetail { UserName = userName, Message = message });
if (CurrentMessage.Count > 100)
CurrentMessage.RemoveAt(0);
}
#endregion
}
When I execute the program it shows log "connected" on the console and the event fires when button is pressed but for some reason the message is not sent or not being received
What are you missing in your code is to start to listen to your server, what that method send to you like
.on("YourMethodName")
after the connection is made. Also is recommended to resolve users with connectionId, you you can call a specific user like:
Clients.Client(Context.ConnectionId).sendPrivateMessage(fromUserId, fromUser.UserName, message);
I'm making a simple money transaction app where user1 transfer money to user2 using parse.com and cloud code to give user1 permission using masterkey to override all other permissions.
final ParseQuery<ParseObject> parseQuery = ParseQuery.getQuery(user.getClassName());
Log.d("class found : ",String.valueOf(user.getClassName()));
parseQuery.whereMatches("AccountNumber", mAccountNumber.getText().toString().trim());
parseQuery.getFirstInBackground(new GetCallback<ParseObject>() {
#Override
public void done(final ParseObject parseObject, ParseException e) {
if (parseObject != null) {
Log.d("userID",String.valueOf(parseObject.getObjectId()));
balance = Integer.parseInt(mbalance.getText().toString());
q = parseObject.getInt("balance");
parseObject.put("balance", balance + q);
parseObject.saveInBackground(new SaveCallback() {
#Override
public void done(ParseException e) {
if (e == null) {
Toast.makeText(TransferToAccount.this, "DOne!!", Toast.LENGTH_SHORT).show();
} else {
Log.d("Exception", "1");
e.printStackTrace();
}
}
});
} else {
Log.d("Exception", "2");
Toast.makeText(TransferToAccount.this, "No user found ", Toast.LENGTH_SHORT).show();
e.printStackTrace();
and this is the could code :
Parse.Cloud.beforeSave(Parse.User, function(request, response) {
Parse.Cloud.useMasterKey();
var user = request.user;
if (user.existed()) { return; }
user.setACL(new Parse.ACL(user));
user.save();
// add user to role
var roleName = "member";
var roleQuery = new Parse.Query(Parse.Role);
roleQuery.equalTo("name", roleName);
roleQuery.first().then(function(role) {
role.getUsers().add(user);
// save role
return role.save();
});
when I try to transfer I get an Exception :
W/System.err: com.parse.ParseException: java.lang.IllegalArgumentException: Cannot save a ParseUser that is not authenticated.
and
W/System.err: Caused by: java.lang.IllegalArgumentException: Cannot save a ParseUser that is not authenticated.
sorry if I made myself unclear.
You cant assign a role to a user before he is saved. Try to change it to afterSave trigger.
after reading this , I could solve my problem by creating a second class with a pointer to _User without the need to cloud code .
I am using ServiceStack.Client to consume, the data pushed by my server(which is an aspx page).
Below is the code which i use to consume the data using ServiceStack Client:
using System;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.Threading;
using ServiceStack;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
ServerEventConnect connectMsg = null;
var msgs = new List<ServerEventMessage>();
var commands = new List<ServerEventMessage>();
var errors = new List<Exception>();
var client = new ServerEventsClient("https://testing.leadsquared.com/ReferralCampaign/Demo")
{
OnConnect = e => PrintMsg(e),
OnCommand = e => PrintCmdMsg(e),
OnMessage = e => PrintCmMsg(e),
OnException = e => PrintExMsg(e)
}.Start();
Console.Read();
}
private static void PrintCmMsg(ServerEventMessage e)
{
if (e != null)
{
PrintMsg(e.Data);
}
}
private static void PrintExMsg(Exception e)
{
if (e != null)
{
PrintMsg(e.Message);
}
}
private static void PrintCmdMsg(ServerEventMessage e)
{
if (e != null)
{
PrintMsg(e.Data);
}
}
private static void PrintMsg(ServerEventConnect e)
{
if (e!=null)
{
PrintMsg(e.Data);
}
}
private static void PrintMsg(string x)
{
Console.WriteLine(x);
}
}
}
When I run my code , the client does print any message on the console.
The ConnectionDisplayName property is "(not connected)".
If i subscribe to the same URL using javascript EventSource, i get the notifications.
My requirement is that I would want to consume the data by my server in C#.
How can I achieve this?
Firstly the url needs to be the BaseUri where ServiceStack is hosted, i.e. the same url used in JavaScript ServerEvents Client, e.g:
var client = new ServerEventsClient(BaseUrl).Start();
It's not clear if /ReferralCampaign/Demo is the BaseUri or not.
You will also want to call Connect() to wait for the client to make a connection, e.g:
await client.Connect();
Then to see message events you'll need to call a ServiceStack Service that publishes a Notify* Event on IServerEvents API which you can use with a separate JsonServiceClient or the ServiceClient available in ServerEventsClient, e.g:
client.ServiceClient.Post(new PostRawToChannel {
From = client.SubscriptionId,
Message = "Test Message",
Channel = channel ?? "*",
Selector = "cmd.announce",
});
This is an example calling the Chat PostRawToChannel ServiceStack Service:
public class ServerEventsServices : Service
{
public IServerEvents ServerEvents { get; set; }
public void Any(PostRawToChannel request)
{
// Ensure the subscription sending this notification is still active
var sub = ServerEvents.GetSubscriptionInfo(request.From);
if (sub == null)
throw HttpError.NotFound("Subscription {0} does not exist".Fmt(request.From));
// Check to see if this is a private message to a specific user
if (request.ToUserId != null)
{
// Only notify that specific user
ServerEvents.NotifyUserId(request.ToUserId, request.Selector, request.Message);
}
else
{
// Notify everyone in the channel for public messages
ServerEvents.NotifyChannel(request.Channel, request.Selector, request.Message);
}
}
}
I also recommend looking at the C# ServerEventTests for complete stand-alone examples using C# ServerEventClient.
I'm developing a game on cordova that uses facebook integration. I have a facebook game canvas running on a secure site.
The friend request works fine on the web site version (returns more than 25 results, as I'm iterating the paging.next url that is also returned).
However, on the cordova build (android) it only ever returns the first result set of 25. It does still have the page.next url JSON field but it just returns a response object with a type=website.
Has anyone else come across this?
After quite a lot of digging I found an issue with the way requests are handled in the FacebookLib for Android. The current version of the com.phonegap.plugins.facebookconnect plugin uses Android FacebookSDK 3.21.1 so I'm not sure if this will still be an issue with v4.
A graph result with a paging url is used to request the next page however using the entire url, which includes the https://graph.facebook.com/ as well as the usual graphAction causes an incorrect result set to be returned. However I determined that if you remove the schema and host parts it will be correct.
I modified the ConnectPlugin.java to check that any schema and host is removed from the graphAction. Seems to work well now.
ConnectPlugin.java before:
private void makeGraphCall() {
Session session = Session.getActiveSession();
Request.Callback graphCallback = new Request.Callback() {
#Override
public void onCompleted(Response response) {
if (graphContext != null) {
if (response.getError() != null) {
graphContext.error(getFacebookRequestErrorResponse(response.getError()));
} else {
GraphObject graphObject = response.getGraphObject();
JSONObject innerObject = graphObject.getInnerJSONObject();
graphContext.success(innerObject);
}
graphPath = null;
graphContext = null;
}
}
};
//If you're using the paging URLs they will be URLEncoded, let's decode them.
try {
graphPath = URLDecoder.decode(graphPath, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] urlParts = graphPath.split("\\?");
String graphAction = urlParts[0];
Request graphRequest = Request.newGraphPathRequest(null, graphAction, graphCallback);
Bundle params = graphRequest.getParameters();
if (urlParts.length > 1) {
String[] queries = urlParts[1].split("&");
for (String query : queries) {
int splitPoint = query.indexOf("=");
if (splitPoint > 0) {
String key = query.substring(0, splitPoint);
String value = query.substring(splitPoint + 1, query.length());
params.putString(key, value);
if (key.equals("access_token")) {
if (value.equals(session.getAccessToken())) {
Log.d(TAG, "access_token URL: " + value);
Log.d(TAG, "access_token SESSION: " + session.getAccessToken());
}
}
}
}
}
params.putString("access_token", session.getAccessToken());
graphRequest.setParameters(params);
graphRequest.executeAsync();
}
ConnectPlugin.java after:
private void makeGraphCall() {
Session session = Session.getActiveSession();
Request.Callback graphCallback = new Request.Callback() {
#Override
public void onCompleted(Response response) {
if (graphContext != null) {
if (response.getError() != null) {
graphContext.error(getFacebookRequestErrorResponse(response.getError()));
} else {
GraphObject graphObject = response.getGraphObject();
JSONObject innerObject = graphObject.getInnerJSONObject();
graphContext.success(innerObject);
}
graphPath = null;
graphContext = null;
}
}
};
//If you're using the paging URLs they will be URLEncoded, let's decode them.
try {
graphPath = URLDecoder.decode(graphPath, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
String[] urlParts = graphPath.split("\\?");
String graphAction = urlParts[0];
///////////////////////
// SECTION ADDED
///////////////////////
final String GRAPH_BASE_URL = "https://graph.facebook.com/";
if(graphAction.indexOf(GRAPH_BASE_URL)==0) {
URL graphUrl = null;
try {
graphUrl = new URL(graphAction);
} catch (MalformedURLException e) {
e.printStackTrace();
}
graphAction = graphUrl.getPath();
}
///////////////////////
// END SECTION ADDED
///////////////////////
Request graphRequest = Request.newGraphPathRequest(null, graphAction, graphCallback);
Bundle params = graphRequest.getParameters();
if (urlParts.length > 1) {
String[] queries = urlParts[1].split("&");
for (String query : queries) {
int splitPoint = query.indexOf("=");
if (splitPoint > 0) {
String key = query.substring(0, splitPoint);
String value = query.substring(splitPoint + 1, query.length());
params.putString(key, value);
if (key.equals("access_token")) {
if (value.equals(session.getAccessToken())) {
Log.d(TAG, "access_token URL: " + value);
Log.d(TAG, "access_token SESSION: " + session.getAccessToken());
}
}
}
}
}
params.putString("access_token", session.getAccessToken());
graphRequest.setParameters(params);
graphRequest.executeAsync();
}
There's no way to know that you call their api from cordova vs website, so it's some problem on your side, maybe you use some different implementation of the api on corodva and website, so that cordova sends a pagination request or send to other api version which does pagination.