In play framework we can see websocket-chat application that shows us usage of Concurrent.broadcast to handle websocket messages.
But I want to use websockets to send messages to each connected websocket independently. Simpliest example is something like private messages, when user sends message like: {user: "First", to: "Second", message: "Hi"}.
I looked at object play.api.libs.iteratee.Concurrent, looks like most suitable there is Concurrent.unicast to do this. But when we have Concurrent.broadcast - we have channel where we can push messages. In case of Concurrent.unicast - we have just Enumerator.
So, how can I dispatch private messages between websockets with Play Framework 2.2 in Scala?
I found another way to archive the private messaging problem out from the source code of the play framework sample. By using a filtered enumerator for each user:
val filteredEnumerator = enumerator &> Enumeratee.filter[JsValue]( e => {
if ( (e \ "kind").as[String] == "talk") {
val isToAll = (e \ "recipient").as[String] == "all"
val isToRecipient = (e \ "recipient").as[String] == username
val isFromRecipient = (e \ "user").as[String] == username
isToAll || isToRecipient || isFromRecipient
} else {
true
}
})
sender ! Connected(filteredEnumerator)
So, the message is being passed to the enumerator if the kind is "talk" (we only want to filter the messages), recipient is "all", recipient is the username itself or if user is the username itself so the person sent the message also sees the message.
the reply in the Chat room application is sent to All users in the Room via:
// Send a Json event to all members
public void notifyAll(String kind, String user, String text) {
So if you would like to implement a private message then you will have to implement "notify" method that will send message only to one specific user. Say something like:
// Send a Json event to all members
public void notify(String kind, String user, String userTo, String text) {
for(WebSocket.Out<JsonNode> channel: members.values()) {
ObjectNode event = Json.newObject();
event.put("kind", kind);
event.put("user", user);
event.put("message", text);
ArrayNode m = event.putArray("members");
m.add(userTo);
channel.write(event);
}
Related
I'm following Vert.x 4's SockJS documentation and noticed that each SockJSSocket inside my handler has a .webSession() and a .webUser(). However, both these fields are empty aside from the .webSession().id()
I have an AuthHandler registered on the sub-router that this socket handler is on, but the SockJS Client in my frontend is not capable of sending credentials in the HTTP upgrade request.
How am I supposed to populate these fields for use?
I'm using the following straight-forward code to keep the session-like information available:
class SockJSBridge implements Handler<BridgeEvent> {
#Override
void handle( BridgeEvent event ) {
SockJSSocket socket = event.socket()
MultiMap headers = socket.headers()
switch( event.type() ){
case REGISTER:
def user = getUserFromJWTHeader event.rawMessage.headers.authorization
headers.add 'userId', user.id
break
case SEND:
String userId = headers.get 'userId'
socket.write new JsonObject( address:userId, body:event.rawMessage?.body ).toBuffer()
break
}
}
}
When device1 is sending the message to the conference room "del#conference.jabber.org"
the message is dispalyed in the chat list as well as a duplicated message is also displayed that is being send by the conference room "del#conference.jabber.org". I'm stuck, why i'm getting duplicate message.
public void setConnection(XMPPConnection connection) {
this.connection = connection;
if (connection != null) {
PacketFilter filter = new MessageTypeFilter(Message.Type.groupchat);
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
Message message = (Message) packet;
if (message.getBody() != null) {
String fromName = StringUtils.parseBareAddress(message.getFrom());
String[] parts = fromName.split("#");
String from = parts[0].trim();
messages.add(from + ":");
messages.add(message.getBody());
// Add the incoming message to the list view
mHandler.post(new Runnable() {
public void run() {
setListAdapter();
}
});
}
}
}, filter);
}
}
The send message is on button click, which is as follows
Button send = (Button) this.findViewById(R.id.sendBtn);
send.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Message mg = muc.createMessage();
String text = textMessage.getText().toString();
mg.setBody(text);
Log.i("XMPPChatDemoActivity ", "Sending text ");
if (connection != null) {
connection.sendPacket(mg);
messages.add("Me :");
messages.add(text);
setListAdapter();
}
textMessage.setText("");
}
});
and this is what i have written to connect the conference room
muc = new MultiUserChat(connection, "del#conference.jabber.org");
muc.join("alias name","password");
output what i'm getting when sending message
me: hello
del: hello
what i want i no duplicate message when i send the message i.e
me: hello
When you're in a MUC room you receive copies of all the messages, including your own.
http://xmpp.org/extensions/xep-0045.html#message - "and reflect the message out to the full JID of each occupant."
So for MUCs (not for direct messages) you will get a duplicate if you log both on send and on receive (assuming you have sufficient access to post, etc.). Your options are, largely, either to not log it on send (which is the option most clients go for) or to attempt to do smart message matching to detect when you receive your own message and elide it. The former option ensures that everyone sees a consistent view of message ordering, which some people find very useful.
Maybe your chat server sent your message to you also?
So you add one message manually in onClickListener and then the same message received from server.
I think, it will be right not to add messages from onClickListener - add only those that server sends.
my app lists hosts, and the list is dynamic and changing. it is based on Akka actors and Server Sent Events.
when a new client connects, they need to get the current list to display. but, i don't want to push the list to all clients every time a new one connects. so, followed the realtime elastic search example and emulated unicast by creating an (Enumerator, Channel) per Connect() and giving it an UUID. when i need to broadcast i will map over all and update them, with the intent of being able to do unicast to clients (and there should be very few of those).
my problem is - how do i get the new client its UUID so it can use it? the flow i am looking for is:
- client asks for EventStream
- server creates a new (Enumerator, channel) with a UUID, and returns Enumerator and UUID to client
- client asks for table using uuid
- server pushes table only on channel corresponding to the uuid
so, how would the client know about the UUID? had it been web socket, sending the request should have had the desired result, as it would have reached its own channel. but in SSE the client -> server is done on a different channel. any solutions to that?
code snippets:
case class Connected(uuid: UUID, enumerator: Enumerator[ JsValue ] )
trait MyActor extends Actor{
var channelMap = new HashMap[UUID,(Enumerator[JsValue], Channel[JsValue])]
def connect() = {
val con = Concurrent.broadcast[JsValue]
val uuid = UUID.randomUUID()
channelMap += (uuid -> con)
Connected(uuid, con._1)
}
...
}
object HostsActor extends MyActor {
...
override def receive = {
case Connect => {
sender ! connect
}
...
}
object Actors {
def hostsStream = {
getStream(getActor("hosts", Props (HostsActor)))
}
def getActor(actorPath: String, actorProps : Props): Future[ActorRef] = {
/* some regular code to create a new actor if the path does not exist, or return the existing one else */
}
def getStream(far: Future[ActorRef]) = {
far flatMap {ar =>
(ar ? Connect).mapTo[Connected].map { stream =>
stream
}
}
}
...
}
object AppController extends Controller {
def getHostsStream = Action.async {
Actors.hostsStream map { ac =>
************************************
** how do i use the UUID here?? **
************************************
Ok.feed(ac.enumerator &> EventSource()).as("text/event-stream")
}
}
I managed to solve it by asynchronously pushing the uuid after returning the channel, with some time in between:
override def receive = {
case Connect => {
val con = connect()
sender ! con
import scala.concurrent.ExecutionContext.Implicits.global
context.system.scheduler.scheduleOnce(0.1 seconds){
unicast(
con.uuid,
JsObject (
Seq (
"uuid" -> JsString(con.uuid.toString)
)
)
)
}
}
this achieved its goal - the client got the UUID and was able to cache and use it to push a getHostsList to the server:
#stream = new EventSource("/streams/hosts")
#stream.addEventListener "message", (event) =>
data = JSON.parse(event.data)
if data.uuid
#uuid = data.uuid
$.ajax
type: 'POST',
url: "/streams/hosts/" + #uuid + "/sendlist"
success: (data) ->
console.log("sent hosts request to server successfully")
error: () ->
console.log("failed sending hosts request to server")
else
****************************
* *
* handle parsing hosts *
* *
* *
****************************
#view.render()
while this works, i must say i don't like it. introducing an artificial delay so the client can get the channel and start listening (i tried with no delay, and the client didn't get the uuid) is dangerous, as it might still miss if the system get busier, but making it too long hurts the reactivity aspect.
if anyone has a solution in which this can be done synchronically - having the uuid returned as part of the original eventSource request - i would be more than happy to demote my solution.
I am developing a chat using quickblox but I am having some problems when I open a new chat. Suddenly I received all the messages that others users sends to me when I was desconnected. The problem is that when I start a chat with user A, I receive the chats from users B, C, D.. in user A chat room.
I have find the way to only show the A users. But the problem is that the server has already sent to me the "disconnected" messages, so when I start a chat to B I do not receive any text because the message that the user B sent to me has been delivered (and ommitted) while I was chating with user A.
How can I do to receive the pending messages (kind of history) or to just retreive the message of the chat I am logged in?
A piece of my code:
// Create Connection.
Connection.DEBUG_ENABLED = true;
config = new ConnectionConfiguration(CHAT_SERVER);
connection = new XMPPConnection(config);
try {
connection.connect();
connection.login(chatLogin, password);
// Create Chat Manager.
chatManager = connection.getChatManager();
// Create Chat.
chat = chatManager.createChat(friendLogin, null);
// // Set listener for outcoming messages.
// chatManager.addChatListener(chatManagerListener);
// Accept only messages from
String from_messages = Integer.toString(receiver_chat_id);
PacketFilter filter = new AndFilter(new PacketTypeFilter(Message.class),
new FromContainsFilter(from_messages+"-3758#chat.quickblox.com"));
// Collect these messages
PacketCollector collector = connection.createPacketCollector(filter);
Packet packet = collector.pollResult();
PacketListener myListener = new PacketListener() {
public void processPacket(Packet packet) {
if (packet instanceof Message) {
Message msg = (Message) packet;
if(msg!=null){
// Process message
System.out.println("Rebem missatge: " +msg.getBody());
}
}
}
};
// Register the listener.
connection.addPacketListener(myListener, filter);
// Set listener for detect Receiver Status
if (connection.isConnected()){
roster = connection.getRoster();
roster.addRosterListener(new RosterListener() {
public void entriesDeleted(Collection<String> addresses) {}
public void entriesUpdated(Collection<String> addresses) {}
public void presenceChanged(Presence presence) {}
public void entriesAdded(Collection<String> arg0) {}
});
}
Make a arraylist and store the receiving offline messages into that array-list. you could add a code in your processPacket(Packet packet) function to add incoming messages to the array-list. or you could use Multimaps(Google Guava) to store all the incoming messages with the key. and when you open the activity that displays the messages from the certain person you could match the name of that person with the key(stored in multimap) and displays the message of that certain person. And the better option is to store all the incomming and outgoing message chat with the certain person so that you could show all the previous chat then you open up the activity.
i am building a chat application with the help of GoogleCloudMessaging(GCM).i am able to send and receive messages. my problem is when i receive messages from multiple devices at the same time, all those messages are appended to the same list-view in my broadcast receiver class. how can i separate the message based on the senders and append the current chat message to the listview. and make separate notifications for other messages based on the senders and when i click on the notification it should open the same list view with messages according to sender.
can any one give me an efficient way of doing this.if you have any sample code to handle this situation please post the code.
My current code:
public class Serious extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action=intent.getAction();
if (action.equals("com.google.android.c2dm.intent.REGISTRATION"))
{
String registrationID=intent.getStringExtra("registration_id");
// Log.i("uo",registrationID);
String error=intent.getStringExtra("error");
String unregisterd=intent.getStringExtra("unregistered");
}
else if(action.equals("com.google.android.c2dm.intent.RECEIVE"))
{
String data1=intent.getStringExtra("data1");
String data2=intent.getStringExtra("data2");
addNewMessage(new Message(data2, false));
/* PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
new Intent(context, MainActivity.class), 0);
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("My Notification")
.setDefaults(Notification.DEFAULT_SOUND)
.setAutoCancel(true)
.setContentText(data1+data2);
mBuilder.setContentIntent(contentIntent);
NotificationManager mNotificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(1, mBuilder.build());*/
}
}
void addNewMessage(Message m)
{
MainActivity.messages.add(m);
MainActivity.adapter.notifyDataSetChanged();
//MainActivity.getListView().setSelection(MainActivity.messages.size()-1);
}
}
You should pass in your gcm message a parameter that contains the sender id. Then, when you handle the arrived message, use that sender id to decide where to add that message. In order to show multiple notifications, pass different int values to notify. Currently you always pass 1, so a new notification overrides the old one.