Reputation: 219
Got another question. I am starting to get used to how the websocket works. I have even managed to implement communication cross domain. But now I have yet to hit another milestone.
Here is a snippet from my current implementation
public function onMessage(ConnectionInterface $conn, $msg)
{
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
global $users;
if($tag == "[msgsend]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[bye]")
{
foreach($this->clients as $client)
{
$client->send($msg);
}
foreach($users as $key => $user)
{
if($user->name == $msgjson->uname)
{
unset($users[$key]);
}
}
$this->clients->detach($conn);
}
else if($tag == "[connected]")
{
//store client information
$temp = new Users();
$temp->name = $msgjson->uname;
$temp->connection = $conn;
$temp->timestamp = new \DateTime();
$users[] = $temp;
usort($users, array($this, "cmp"));
//send out messages
foreach($this->clients as $client)
{
$client->send($msg);
}
}
else if($tag == "[imalive]")
{
//update user timestamp who sent [imalive]
global $users;
foreach($users as $user)
{
if($msgjson->uname == $user->name)
{
$user->timestamp = new \DateTime();
}
}
}
}
Now my question is. As we can see, in the onMessage() function, and tutorials that I have done, I know how to read and parse JSON data, make sense of messages, tell who the message is coming from ($conn).....
But say when I send a message in a JSON packet, I want to include the nicknames of who the message is from as well as who the message is going to. This will allow me to implement private instant messaging in both my social network that i am building and in chatrooms.
Instead of a for loop sending messages to all connected clients, I only want to send messages to specific ones. I know clients have a property ($this->$client->resourceID or something along those lines) but not sure how to incorporate it as a solution either. I also want users to maintain a connection as they jump to different pages on the website and even after a refresh, still be able to continue messaging. I assume each refresh disconnects the client. So I have to have a way where the server can tell each time who is who and where messages are coming from and where they are going to.
But yeah, private messaging. I dont want to have to send unnessecary messages to everyone or unintended targets. How can I accomplish this? I hope my question makes sense. thanks.
Upvotes: 1
Views: 2493
Reputation: 11943
Being able to uniquely identify users that connect to your WebSocket server and then being able to target those users, specifically, when sending out messages needs to begin at the onOpen
callback where the connection is actually negotiated with the server.
In your onOpen
method you should have some way of uniquely identifying the user on your system via some user id that's globally stored in your database, or persistence store. Since the connection is negotiated over HTTP you have access to the HTTP request via the $conn->WebSocket->request
, which is a GuzzleHttp
object containing the client's HTTP request information. You could use this to pull a cookie, for example, that contains some user id data or token that you can compare to your database to figure out who the user is and then store that as property of the $client
object.
For now, let's pretend you're writing a normal PHP script where you do user authentication over HTTP and store the user id in a session. This session sets a cookie on the client's machine (by default the cookie name is the session name, which is usually PHPSESSID
unless you change it) containing a session id. This session id can be used in your WebSocket server to accessed the session storage the same way you do normally in PHP.
Here's a simple example, where we expect a cookie named PHPSESSID
from the request to capture the session id from cookie.
public function onOpen(ConnectionInterface $conn) {
// extract the cookie header from the HTTP request as a string
$cookies = (string) $conn->WebSocket->request->getHeader('Cookie');
// look at each cookie to find the one you expect
$cookies = array_map('trim', explode(';', $cookies));
$sessionId = null;
foreach($cookies as $cookie) {
// If the string is empty keep going
if (!strlen($cookie)) {
continue;
}
// Otherwise, let's get the cookie name and value
list($cookieName, $cookieValue) = explode('=', $cookie, 2) + [null, null];
// If either are empty, something went wrong, we'll fail silently here
if (!strlen($cookieName) || !strlen($cookieValue)) {
continue;
}
// If it's not the cookie we're looking for keep going
if ($cookieName !== "PHPSESSID") {
continue;
}
// If we've gotten this far we have the session id
$sessionId = urldecode($cookieValue);
break;
}
// If we got here and $sessionId is still null, then the user isn't logged in
if (!$sessionId) {
return $conn->close(); // close the connection - no session!
}
}
Now that you actually have the $sessionId
you can use it to access the session storage for that session, pull the session information into your WebSocket server and store it as a property of the client connection object $conn
.
So continuing from the above example let's add this code to the onOpen
method.
public function onOpen(ConnectionInterface $conn) {
$conn->session = $this->methodToGetSessionData($sessionId);
// now you have access to things in the session
$this->clinets[] = $conn;
}
Now, let's go back to your example where you want to send a message specifically to just one user. Let's assume we have the following properties stored in the session, which are now accessible from the client connection object...
$conn->session->userName = 'Bob';
$conn->session->userId = 1;
So let's say Bob wants to send a message to Jane. A request comes in to your WS server with something like {"from":1,"to":2,tag:"[msgsend]"}
where the to
and from
properties of that JSON are basically the user ids of the user the message is from and the user the message is intended to be sent to, respectively. Let's assume Jane is userId = 2
for this example.
public function onMessage(ConnectionInterface $conn, $msg) {
$msgjson = json_decode($msg);
$tag = $msgjson->tag;
if ($tag == '[msgsend]') {
foreach($this->clients as $client) {
// only send to the designated recipient user id
if ($msgjson->to == $client->session->userId) {
$client->send($msg);
}
}
}
}
Obviously you might want to have more elaborate validation in there, but you should be able to expand on this from here.
Upvotes: 7