Goal: I want a user to trigger an event which sends a message to another specific user related to that event.
The app: Several users are online and logged in at the same time. User A writes a message to user B. Only user B should receive this message.
Question: How can I tell the event handler that only user B gets a message? And how does the websocket know who the message goes to?
Where i can get all Sockets ids in the beyondcode Package?
I use Laravel 9 and the websocket package https://github.com/beyondcode/laravel-websockets.
use App\Models\User;
class ReputationUpdate implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $message;
public $targetUser;
public function __construct($message, User $targetUser)
{
$this->message = $message;
$this->tragetUser = $targetUser;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
// How to handle it that only target User receive the message?
return new Channel('home');
}
}
Related
In my multi-server application I use laravel's Queueing system in order to run background jobs. Sometimes in my logic I want to make my job to throw an exception so I can log it via a sentry using the laravel library that offers.
So in my job:
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use App\Model\Etable\User;
use App\Model\User;
class MyJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
/**
* #var int
*/
private $user_id;
/**
* #param int $user The user that has opted or deopted for newsletter consent
*/
public function __construct(int $id)
{
$this->user_id = $user_id;
}
public function handle(): void
{
/**
* #var User
*/
$user=User::useWritePdo()->find($this->user_id);
if(empty($user)){
throw new \Exception("No such a user with user id: {$this->user_id}");
}
// Rest of logic here
}
}
Once the exception is thrown, I will to be logged into sentry but also it will keep on respawning as laravel's logic for jobs is supposed to do so.
In my case I think it as waste of resources to keep an respawning the MyJob in case that the user does not exist no value to keep on spawning because the logic itself cannot be performed in case that no user exists. On the other hand on any other error I want my job to keep on retrying till be able to sucessfully run again.
So how I can make my job not to respawn on specific errors? Even better would be as well if I can use the default logging method that laravel ofers in order to arbitary log an error into sentry as well via a sentry dedicated channel.
The best and easier approach is once the code failt to check how many times has already be executed:
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use App\Model\Etable\User;
use App\Model\User;
class MyJob implements ShouldQueue
{
use Dispatchable;
use InteractsWithQueue;
use Queueable;
/**
* #var int
*/
private $user_id;
/**
* #param int $user The user that has opted or deopted for newsletter consent
*/
public function __construct(int $id)
{
$this->user_id = $user_id;
}
public function handle(): void
{
/**
* #var User
*/
$user=User::useWritePdo()->find($this->user_id);
if(empty($user)){
if ($this->attempts() > 1) {
return;
}
throw new \Exception("No such a user with user id: {$this->user_id}");
}
// Rest of logic here
}
}
This is being achieved via the:
if ($this->attempts() > 1) {
return;
}
So you throw once the exception, the exception is being logged into the Sentry and then on the second time that will be executed it will just exit and never respawn.
Please note that failed jobs won't be re-run unless you explicitly run
php artisan queue:failed
You can throw an exception and handle it in boot method of AppServiceProvider, deleting the job to avoid its further re-tries. See documentation on how to do it.
Now I need to send FCM push notifications to all my users on every record add, but it's take so much time to loop through them all, how to make the loop work in the background?
I'm using brozot/Laravel-FCM package
You might be triggering an event for push notification. so, while triggering an event for push notification you can push the notifications to queue. This queue is basically implemented in class declared in Listeners folder. example:
namespace App\Listeners;
use App\Events\EventName;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use App\FcmAdapter;
class NotificationClassName implements ShouldQueue
{
/**
* Create the event listener.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param EventName $event
* #return void
*/
public function handle(EventName $event)
{
// code for sending FCM notification.
}
}
implements ShouldQueue is the way to implement queue.
I want to create chat application in a webapp where user can chat with different site users. This will be available on web and also on iOS.
Instead of using traditional polling technique (send ajax hit to server in 1 sec interval), i want to use websockets.
Gone through couple of tutorials, but in all of them they have made PUBLIC GROUP chat. (Sample URL : https://www.sanwebe.com/2013/05/chat-using-websocket-php-socket)
Can anyone have idea to how to develop private chat using PHP & Websockets.
I have basic idea of websockets but how to use them to publish data on specific channel? And if we have 40 users so do we need to create 40 different channels?
Thanks in advance.
There are not much different from doing one single global chat and multiple private channel. First, you need to design a protocol. Let create a simple protocol:
// client send to server
JOIN <channel_id>
LEAVE <channel_id>
MSG <channel_id> <message>
// server send to client
JOIN <channel_id> <username>
LEAVE <channel_id> <username>
MSG <channel_id> <username> <message>
So when a user connect to a server, you can randomly assign his username. You have an array to store all connection.
Create array of channel. Each channel hold an array of user inside the channel.
When client send JOIN <channel_id> to server. Broadcast JOIN <channel_id> <username> to all the connection in that channel.
When client send MSG <channel_id> <message> to server. Broadcast MSG <channel_id> <username> <message> to all connection in that channel.
so on and on ....
So basically, WebSocket provides a basic way of communicate, it is upto you to be creative to do thing.
For private (room) chat systems you really have to develop your own logics.
I would recommend you to use the following library:
http://socketo.me/
Go through their documentation at http://socketo.me/docs/ and start coding.
If you get stuck then post your code and the community is here to help
This is how I have done in Laravel, You need to install Predis , socket.io , ratchet and other dependencies . Please check https://laracasts.com/discuss/channels/general-discussion/step-by-step-guide-to-installing-socketio-and-broadcasting-events-with-laravel-51
Make one custom artisan command to run a websockets on some port using ratchet
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Ratchet\Server\IoServer;
class webSockets extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'run:socket {port?}';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Run websockets for specified port';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
$port = $this->argument('port');
$server = IoServer::factory(
new ChatController(),$port
$server->run();
}
}
Your controller should be like below
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatController implements MessageComponentInterface {
public function onOpen(ConnectionInterface $conn) {
}
public function onMessage(ConnectionInterface $from, $msg) {
//FIRE A BROADCAST EVENT HERE
event(new MessageBroadcast(
$message,
$datetime,
$user_id
)
);
}
public function onClose(ConnectionInterface $conn) {
}
public function onError(ConnectionInterface $conn, \Exception $e) {
}
}
THE BROADCAST CLASS SHOULD LOOK LIKE BELOW
namespace App\Events;
use App\Events\Event;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class MessageBroadcast extends Event implements ShouldBroadcast
{
use SerializesModels;
public $message,$datetime,$userid;
public function __construct($message,$datetime,$userid)
{
$this->message = $message;
$this->datetime = $datetime;
$this->userid = $userid;
}
public function broadcastOn()
{
return ['test-channel'.$this->user_id];
}
}
Javascript part to subscribe a channel
<script src="{ { asset('js/socket.io.js') } }"></script>
<script>
//var socket = io('http://localhost:3000');
var socket = io('http://yourip:5000');
socket.on("test-channel1:App\\Events\\EventName", function(message){
// get user on console
console.log(message);
});
</script>
You need to run following command in backgroud
1. php artisan run:socket <port_no>
2. Node yourjavascript.js
I've a problem that I'm trying to fix all day now. I've followed
this tutorial. The goal is to make a chat with Laravel echo, vue.js and pusher.
I've done everything exactly like the tutorial but for some reason I do not receive any events in my pusher console. Only the connection shows up:
But no events. The event that I fire looks like this:
<?php
namespace App\Events;
use App\Message;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* #var
*/
public $user;
/**
* #var
*/
public $message;
/**
* MessageSent constructor.
* #param User $user
* #param Message $message
*/
public function __construct(User $user, Message $message)
{
$this->user = $user;
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('chat');
}
}
I fire the event like this:
broadcast(new MessageSent($user, $message))->toOthers();
When I dd('test'); like this in my MessageSent event class:
public function broadcastOn()
{
dd('test');
return new PrivateChannel('chat');
}
The dd('test'); shows up in my network tab.
I'm using Laravel 5.4 and Vue.js 2.0 with Homestead. What could be going on here?!
It looks likes you are following this tutorial. I also had a hard time to figure it out. I already answered here. Can you please check it out?
I worked on typing feature in the chat system. Please take a look at the code on GitHub.
Let me know if you have any questions. Thanks :)
By the looks of your debug console screenshot you are never managing to subscribe to any channels, have you set up the necessary authentication for the private channel subscription?
The complete demo code for the tutorial that you've been following is on github so you might want to take a look at that and see where yours differs.
If you are on Laravel 5.4, make sure you have set-up the channel authentication.
For example, in your routes/channels.php file, there should be something like this:
Broadcast::channel('chat', function ($user) {
return true; // change this to your authentication logic
});
I have created a security.authentication.success event listener, which should send a line to the logs upon login success. Now every time I load a page which is behind a firewall, I get a successful login message in my logs. If I tried to use
if ($this->container->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY'))
{
$logger->info('Successful login by ' . $username);
}
I get into a recursive madness (xdebug complaining after 10000 nested calls, or whatever high I set it to).
Is there a way to check if the user has just logged in, or if (s)he is using an active session?
Note: I'm using Symfony 2.2 (dev-master)
You have to use the security.interactive_login:
namespace Acme\UserBundle\Listener;
use Symfony\Component\EventDispatcher\Event;
use Symfony\Component\Security\Core\SecurityContext;
use Doctrine\Bundle\DoctrineBundle\Registry as Doctrine; // for Symfony 2.1.x
// use Symfony\Bundle\DoctrineBundle\Registry as Doctrine; // for Symfony 2.0.x
/**
* Custom login listener.
*/
class LoginListener
{
/** #var \Symfony\Component\Security\Core\SecurityContext */
private $securityContext;
/** #var \Doctrine\ORM\EntityManager */
private $em;
/**
* Constructor
*
* #param SecurityContext $securityContext
* #param Doctrine $doctrine
*/
public function __construct(SecurityContext $securityContext, Doctrine $doctrine)
{
$this->securityContext = $securityContext;
$this->em = $doctrine->getEntityManager();
}
/**
* Do the magic.
*
* #param Event $event
*/
public function onSecurityInteractiveLogin(Event $event)
{
if ($this->securityContext->isGranted('IS_AUTHENTICATED_FULLY')) {
// user has just logged in
}
if ($this->securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
// user has logged in using remember_me cookie
}
// do some other magic here
$user = $this->securityContext->getToken()->getUser();
// ...
}
}
From the documentation:
The security.interactive_login event is triggered after a user has
actively logged into your website. It is important to distinguish this
action from non-interactive authentication methods, such as:
authentication based on a "remember me" cookie.
authentication based on your session.
authentication using a HTTP basic or HTTP digest header.
You could listen on the security.interactive_login event, for example,
in order to give your user a welcome flash message every time they log
in.
The security.switch_user event is triggered every time you activate
the switch_user firewall listener.
http://symfony.com/doc/current/components/security/authentication.html#security-events