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);
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'm trying to make a web socket connection between js and java, but I get this answer:
Uncaught DOMException: An attempt was made to use an object that is not, or is no longer, usable
I did it based in some samples on internet. Someone have some idea what can it be?
Project Name is Teste-1
JS Code
var socket = null;
function init(){
socket = new WebSocket("wss://localhost:8080/Teste-1/task");
socket.onmessage = onMessage;
}
function onMessage(event){
var task = JSON.parse(event.data);
if(task.action === "add"){
document.getElementById("point").innerHTML += event.data;
}
}
function teste() {
var action = {
action: "add",
name: "Test",
description: "This is just a test"
};
socket.send(JSON.stringify(action));
}
window.onload = init;
HTML Code
<html>
<head>
<title>Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
<button onclick="teste()">Teste</button>
<div id="point"></div>
<script src="websocket.js"></script>
</body>
</html>
JAVA Codes
public class Task implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private String description;
public Task(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
#ServerEndpoint(value="/task")
public class TaskSocket {
#Inject
private TaskSessionHandler handler;
#OnOpen
public void open(Session session){
handler.addSession(session);
}
#OnClose
public void close(Session session){
handler.removeSession(session);
}
#OnError
public void onError(Throwable error){
Logger.getLogger(TaskSocket.class.getName()).log(Level.SEVERE, null, error);
}
#OnMessage
public void handleMessage(String message, Session session) {
try (JsonReader reader = Json.createReader(new StringReader(message))) {
JsonObject jsonMessage = reader.readObject();
if ("add".equals(jsonMessage.getString("action"))) {
Task task = new Task();
task.setName(jsonMessage.getString("name"));
task.setDescription(jsonMessage.getString("description"));
handler.addTask(task);
}
}
}
}
#ApplicationScoped
public class TaskSessionHandler {
//Each client connected to the application has its own session.
private final Set<Session> sessions = new HashSet<>();
private final Set<Task> tasks = new HashSet<>();
public void addSession(Session session) {
sessions.add(session);
for(Task task : tasks){
JsonObject addMessage = createJSON(task);
sendToSession(session, addMessage);
}
}
public void removeSession(Session session) {
sessions.remove(session);
}
public List<Task> getTasks(){
List<Task> list = new ArrayList<Task>(tasks);
return list;
}
public void addTask(Task e){
tasks.add(e);
JsonObject message = this.createJSON(e);
sendToAllConnectedSessions(message);
}
private JsonObject createJSON(Task task){
JsonProvider provider = JsonProvider.provider();
JsonObject message = provider.createObjectBuilder()
.add("action", "add")
.add("name",task.getName())
.add("description",task.getDescription()).build();
return message;
}
private void sendToAllConnectedSessions(JsonObject message) {
for (Session session : sessions) {
sendToSession(session, message);
}
}
private void sendToSession(Session session, JsonObject message) {
try {
session.getBasicRemote().sendText(message.toString());
} catch (IOException ex) {
sessions.remove(session);
Logger.getLogger(TaskSessionHandler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
I got the same error message due to the websocket.send() being executed before the connection to the webSocket server was established and open.
My contribution would be the advice to make sure messages aren't sent until the connection is open, so that the server actually can receive them.
I cannot tell whether that was the problem in the question asked though. However I faced a very similar problem and ended up here, and wanted to share what worked for me.
In my case the code that didn't work looked like this:
const exampleSocket = new WebSocket('wws://example')
exampleSocket.onopen = function (event) {
console.log('connection is open!')
}
exampleSocket.send(JSON.stringify(msg))
exampleSocket.onmessage = function (event) {
console.log('message event data looks like this: ', event.data)
}
What worked was moving the code that sent a message into the onopen callback function.
const exampleSocket = new WebSocket('wws://example')
exampleSocket.onopen = function (event) {
console.log('connection is open!')
exampleSocket.send(JSON.stringify(msg)) // <- this is the change
}
exampleSocket.onmessage = function (event) {
console.log('message event data looks like this: ', event.data)
}
Sidenote: the readyState property of websocket could be useful when dealing with making sure the server is ready to receive messages.
If it is 1, it means connection is open, so it could be used like this:
if (exampleSocket.readyState === 1) {
console.log("It is safe to send messages now")
}
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
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.