laravel and react pusher setup - php

I am working on a web project.
I have laravel as my back-end reactjs as my front-end
I have users, posts comments in my DB.
I wanted to implement pusher so i can have this real time posts showing up once any user posted a new post.
I am very close to achieve this behavior.
on laravel
i have this event
class NewPostsCast implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $post;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($post)
{
$this->post = $post;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new Channel('posts');
}
}
and once the post stored to my database I trigger this event
event(new NewPostsCast($output));
on react
let channel = pusher.subscribe("posts");
// i guess error is here because this function never called
// it's only called at startup
channel.bind("NewPostsCast", post => {
console.log("pusher::: ", post);
});
might be useful
from pusher logs
Pusher : No callbacks on posts for App\Events\NewPostsCast
however pusher returns this log also
Pusher : Event recd : {"event":"App\\Events\\NewPostsCast","channel":"posts","data":{"post":{"id":19,"title":"sd","body":"eksdl","tags":"[\"ds\",\"dsf\"]","user_id":1,"created_at":"2019-04-18 14:22:00","updated_at":"2019-04-18 14:22:00","votes":0,"user":{"id":1,"name":"user1","username":"user1","email":"user1#health.io","address":null,"state":null,"country":null,"gender":"female","phone":null,"avatar":"http://healthqo.api/public/profile_pics/default/female.png","created_at":"2019-04-09 08:30:12","updated_at":"2019-04-09 08:30:12"}}}}

in case anyone is still struggling with the same problem
from pusher logs it says (No Callbacks on posts for App\Events\NewPostsCast)
that quite solves the problem
since react and laravel are separated in my project (NewPostsCast) is not defined
so i had to change first parameter of the bind function
like so:
FROM
let channel = pusher.subscribe("posts");
channel.bind("NewPostsCast", post => {
console.log("pusher::: ", post);
});
TO
let channel = pusher.subscribe("posts");
channel.bind("App\\Events\\NewPostsCast", post => {
console.log("pusher::: ", post);
});
not sure why double double back slashes but it gives me a syntax error with single back slash

Related

Laravel - Sending customized Bugsnag error reports based on exception type?

I have a Laravel 7 app that uses a 3rd party PHP package to integrate with a service (Convirza). I bootstrapped and bound the service into the container via my own service provider App\Providers\ConvirzaServiceProvider. My problem is that I need to send error logs from the service to Bugsnag (error reporting service)... and depending on the Exception thrown from the service, I need to add metadata to the report. I do NOT want to filter exceptions from the base App\Exceptions\Handler class because I want everything to be wrapped into the Service Provider.
For example, when the Skidaatl\Convirza\Exceptions\BadRequestException gets thrown, I want to add the request parameters as metadata to the report. Another example would be if the Skidaatl\Convirza\Exceptions\InvalidTokenException gets thrown, I want to add the token as metadata to my report.
I would rather not add a try/catch block every time I call the service. How would I go about this?
Here is my Service Provider:
class ConvirzaServiceProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
Commands\UpdateCampaignsCommand::class,
Commands\UpdateGroupsCommand::class,
Commands\GenerateReportsCommand::class
]);
}
$this->app['events']->subscribe(EventHandler::class);
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->loadRoutesFrom(__DIR__.'/routes.php');
$this->loadMigrationsFrom(__DIR__.'/database/migrations');
$this->app->singleton('convirza', function(Container $app) {
$config = config('convirza.client');
$config = Config::create($config);
return new Convirza($config);
});
$this->app->bind(ReportBuilder::class, function(Container $app) {
return new ReportBuilder($app->get('convirza'));
});
$this->app->alias('convirza', Convirza::class);
}
}
I suggest using Bugsnag's registerCallback to inspect the report and add metadata based on specific parameters of the report.
For example, you can get the context of your exception and even the stacktrace information. Additionally, you can access the originalError to get information including the error message. Using that, you can create logic in a callback to add metadata.
E.g.
public function boot()
{
Bugsnag::registerCallback(function ($report) {
$stacktrace = $report->getStacktrace();
$frames = &$stacktrace->getFrames();
// Adds metadata for `log_error` frame if present
if ($frames[0]['method'] == 'log_error') {
$report->setMetaData([
'account' => [
'token' => '12345',
]
}
]);
}

Throttle Laravel Exception email notification per Exception type

Does anyone know of a way to throttle email notifications for a certain Exception in Laravel?
I made my app send me an email when there is a DB error by check for the QueryException. This is a rough example of what I did in Exception handler in Laravel:
class Handler{
/**
* Report or log an exception.
*
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
*
* #param \Exception $exception
* #return void
*/
public function report(Exception $e)
{
if($e instanceof QueryException){
if( App::environment(['production']) ){
Notification::route('mail', 'myemail#test.com')
->notify(new DbErrorNotification($e));
}
}
parent::report($e);
}
}
Short of tracking in DB, is there a way I could throttle DB errors by exception type so that I don't end up getting thousands of emails if there is a consistent DB error.
I looked at Swift Mailer's anti-flood and throttling plugins but those affect the system globally which I don't want to do.
Thank you in advance
There is a few way how you can achieve that. Right before dispatching a job you can add delay on it. Example:
use App\Http\Request;
use App\Jobs\SendEmail;
use App\Mail\VerifyEmail;
use Carbon\Carbon;
/**
* Store a newly created resource in storage.
*
* #param Request $request
* #return \Illuminate\Http\RedirectResponse
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function store(Request $request)
{
$baseDelay = json_encode(now());
$getDelay = json_encode(
cache('jobs.' . SendEmail::class, $baseDelay)
);
$setDelay = Carbon::parse(
$getDelay->date
)->addSeconds(10);
cache([
'jobs.' . SendEmail::class => json_encode($setDelay)
], 5);
SendEmail::dispatch($user, new VerifyEmail($user))
->delay($setDelayTime);
}
Or if you don't like the idea about a job you can also delay it via Mail. Example:
Mail::to($user)->later($setDelayTime);
And finally via Redis Rate Limiting. Example:
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Redis;
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
Redis::throttle('SendEmail')
->allow(1)
->every(10)
->then(function () {
Mail::to($this->user)->send($this->mail);
}, function () {
return $this->release(10);
});
}
Allowing one email to be sent every ten seconds. The string SendEmail passed to the throttle() method is a name that uniquely identifies the type of job being rate-limited. You can set this to whatever you want.
The release() method is an inherited member of your job class and
instructs Laravel to release the job back onto the queue, with an
optional delay in seconds, in the event a lock cannot be obtained.
When the job is dispatched to the queue, Redis is instructed to only
run one SendEmail job every ten seconds.
Keep in mind that for all of this you need a Redis
Source: https://medium.com/#bastones/a-simple-guide-to-queuing-mail-in-laravel-f4ff94cdaa59

Laravel Broadcasting, how to auth to a presence channel?

I'm trying to join a presence channel (Public channels work well), but I can't get this to work:
Vue code:
mounted(){
Echo.join('game.' + "0").here((users) => {
alert("In the channel!");
})
.joining((user) => {
console.log("Someone entered");
})
.leaving((user) => {
console.log(user.name);
})
.listen('GameEvent', (e) => {
console.log("Hey")
});
Echo.channel('NewSentence')
.listen('NewSentence',(sentence)=>{
alert("HOLA");
});
}
I'm trying to join the channel "game.0". As I'm using Laravel Passport I need to authenticate myself with a token, and that is working. Sending the auth request for Laravel Echo returns a key, but the JavaScript events are not triggering .here(), .listening() ....
BroadcastService provider boot function:
public function boot() {
Broadcast::routes(["middleware" => "auth:api"]);
require base_path('routes/channels.php');
}
channels.php
Broadcast::channel('game.0', function ($user,$id) {
return ['id' => $user->id];
});
The auth route:
Route::post('/broadcasting/auth', function(Request $request){
$pusher = new Pusher\Pusher(
env('PUSHER_APP_KEY'),
env('PUSHER_APP_SECRET'),
env('PUSHER_APP_ID'),
array(
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => false,
'host' => '127.0.0.1',
'port' => 6001,
'scheme' => 'http',
)
);
return $pusher->socket_auth($request->request->get('channel_name'),$request->request->get('socket_id'));
});
Do I need to do something extra to make it work? This is the auth request:
EDIT:
GameEvent event:
class GameEvent implements ShouldBroadcastNow {
use Dispatchable, InteractsWithSockets, SerializesModels;
public $gameEvent;
public $gameId;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($gameEvent, $gameId) {
//
$this->gameEvent = $gameEvent;
$this->gameId = $gameId;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\PresenceChannel|array
*/
public function broadcastOn() {
//return new PrivateChannel('channel-name');
return new PresenceChannel('game.0');
}
public function broadcastWith() {
return $this->gameEvent;
}
}
EDIT:
I've hardcoded the names: 'game.0' is now hardcoded in the routes/channels.php route, in the Echo connection and in the GameEvent. I also removed broadcastAs(). After entering the laravel-websockets debugging dashboard I found that the channel I want to subscribe doesn't even appear. It looks like it won't start a connection, but I can't figure out what it going on.
I hardcoded the
The problem here seems to be that the Echo is not listening on proper channel. First of all the Echo.join is using channel game.0 in which 0 is a user's id, and i don't think that there is actually a user with id 0. Secondly, you are broadcasting as
GameEvent
and Echo is connecting to channel named game.{id} I suggest that you either remove the broadcastAs() function from your event file or listen on GameEvent. Also use the websocket dashboard for testing this. The dashboard will be available at
/laravel-websockets
route automatically, which is available only for local environment so make sure that environment is local in your .env.
Use the debugging dashboard provided by laravel-websockets to send data to channels, first connect to your web socket within the dashboard then just enter the channel name, event name and data in JSON format and hit send on the dashboard.
Try finding out if that helps with resolving your problem.
I also recommend thoroughly reading laravel's official documentation on broadcasting as well as laravel-websockets debugging dashboard guide.
Also update what you got in result to this question.

Thruway manage subscriptions

I try to set up a websocket server via Thruway which can manage multiple groups. Something like a chat application where each client may subscribe to one or multiple ones at the same time and broadcast messages to an entire chat room. I managed to do that with an ancient version of Ratchet but since it doesn't run very smooth, I wanted to switch to Thruway. Sadly I can't find anything to manage groups. So far I have the following as the websocket-manager and the clients are using the current version of Autobahn|js (18.x).
Does anyone have any clue if it is possible to manage subscription groups with something like the following?
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Thruway\Peer\Router;
use Thruway\Transport\RatchetTransportProvider;
$router = new Router();
$router->addTransportProvider(new RatchetTransportProvider("0.0.0.0", 9090));
$router->start();
With ThruWay, things are a little different than old Ratchet. First of all Thruway is not a WAMP Server. It is just a router. So it doesn't have a server instance like old Rathcet has lets you wrap all your server side functionality wrapped up. But it will only get the message packet and will route them to other sessions in same realm depending on their subscriptions. If you ever used socket.io, realm idea is similar to different connections so you can limit your sessions or connections to a single namespace or split functionality of different socket instances like administration, visitors etc.
On client side with autobahn ( latest version ) once you subscribe to a topic, then publish in that topic, thruway will automatically detect topic subscribers and emit message to them in same realm. But in old ratchet you need to handle this manually by keeping an array of available channels, and add users to each channel when they subscribes as well as broadcast message to these users in topic by iterating over them. This was really painful.
If you want to use RPC calls in server side and don't want to include some of your stuff on client side, you can still use a class called internalClient on server side. Conceptually Internal Client is another session connects to your thruway client and handles some functions internally without exposing other clients. It receives message packages and does stuff in it then returns result back to requested client connection. It took a while for me to understand how it works but once I figured out the idea behind made more sense.
so little bit code to explain better,
In your router instance you will need to add a module, ( note that, in voxys/thruway package examples are little confusing about internal client )
server.php
require __DIR__ . "/../bootstrap.php";
require __DIR__ . '/InternalClient.php';
$port = 8080;
$output->writeln([
sprintf('Starting Sockets Service on Port [%s]', $port),
]);
$router = new Router();
$router->registerModule(new RatchetTransportProvider("127.0.0.1", $port)); // use 0.0.0.0 if you want to expose outside world
// common realm ( realm1 )
$router->registerModule(
new InternalClient() // instantiate the Socket class now
);
// administration realm (administration)
// $router->registerModule(new \AdminClient());
$router->start();
This will initialize Thruway router and will attach internalclient instance to it. Now in InternalClient.php file you will be able to access actual route as well as current connected clients. With the example they provided, router is not part of instance so you are stuck with only session id property of new connections.
InternalClient.php
<?php
use Thruway\Module\RouterModuleInterface;
use Thruway\Peer\Client;
use Thruway\Peer\Router;
use Thruway\Peer\RouterInterface;
use Thruway\Logging\Logger;
use React\EventLoop\LoopInterface;
class InternalClient extends Client implements RouterModuleInterface
{
protected $_router;
/**
* Contructor
*/
public function __construct()
{
parent::__construct("realm1");
}
/**
* #param RouterInterface $router
* #param LoopInterface $loop
*/
public function initModule(RouterInterface $router, LoopInterface $loop)
{
$this->_router = $router;
$this->setLoop($loop);
$this->_router->addInternalClient($this);
}
/**
* #param \Thruway\ClientSession $session
* #param \Thruway\Transport\TransportInterface $transport
*/
public function onSessionStart($session, $transport)
{
// TODO: now that the session has started, setup the stuff
echo "--------------- Hello from InternalClient ------------\n";
$session->register('com.example.getphpversion', [$this, 'getPhpVersion']);
$session->subscribe('wamp.metaevent.session.on_join', [$this, 'onSessionJoin']);
$session->subscribe('wamp.metaevent.session.on_leave', [$this, 'onSessionLeave']);
}
/**
* Handle on new session joined.
* This is where session is initially created and client is connected to socket server
*
* #param array $args
* #param array $kwArgs
* #param array $options
* #return void
*/
public function onSessionJoin($args, $kwArgs, $options) {
$sessionId = $args && $args[0];
$connectedClientSession = $this->_router->getSessionBySessionId($sessionId);
Logger::debug($this, 'Client '. $sessionId. ' connected');
}
/**
* Handle on session left.
*
* #param array $args
* #param array $kwArgs
* #param array $options
* #return void
*/
public function onSessionLeave($args, $kwArgs, $options) {
$sessionId = $args && $args[0];
Logger::debug($this, 'Client '. $sessionId. ' left');
// Below won't work because once this event is triggered, client session is already ended
// and cleared from router. If you need to access closed session, you may need to implement
// a cache service such as Redis to access data manually.
//$connectedClientSession = $this->_router->getSessionBySessionId($sessionId);
}
/**
* RPC Call messages
* These methods will run internally when it is called from another client.
*/
private function getPhpVersion() {
// You can emit or broadcast another message in this case
$this->emitMessage('com.example.commonTopic', 'phpVersion', array('msg'=> phpVersion()));
$this->broadcastMessage('com.example.anotherTopic', 'phpVersionRequested', array('msg'=> phpVersion()));
// and return result of your rpc call back to requester
return [phpversion()];
}
/**
* #return Router
*/
public function getRouter()
{
return $this->_router;
}
/**
* #param $topic
* #param $eventName
* #param $msg
* #param null $exclude
*/
protected function broadcastMessage($topic, $eventName, $msg)
{
$this->emitMessage($topic, $eventName, $msg, false);
}
/**
* #param $topic
* #param $eventName
* #param $msg
* #param null $exclude
*/
protected function emitMessage($topic, $eventName, $msg, $exclude = true)
{
$this->session->publish($topic, array($eventName), array('data' => $msg), array('exclude_me' => $exclude));
}
}
Few things to note in above example code,
- In order to receive a message in a topic, in client side you need to be subscribed to that topic.
- Internal client can publish/emit/broadcast any topic without any subscription in same realm.
- broadcast/emit functions are not part of original thruway, something I come up with to make publications little easier on my end. emit will will send message pack to everyone has subscribed to topic, except sender. Broadcast on the other hand won't exclude sender.
I hope this information would help a little to understand the concept.

Laravel listener only sends when I dd a variable in the class being called

I have a custom class in Laravel that tracks the analytics of my app through Segment (using this package for php: https://github.com/AltThree/Segment).
Here is a snippet of my class and a function I am calling through my listener to track a login:
class Tracking {
private function segmentTrack(User $user, string $event, array $properties = null) {
$segment = Segment::track([
"userId" => $user->id,
"event" => $event,
"properties" => $properties
]);
dd($segment);
}
/**
* Handle Login tracking
*
* #param User $user
* #return void
*/
public function login (User $user) {
$this->segmentTrack($user, "Login");
}
}
Notice the dd in the segmentTrack function. When I run the Laravel queue and I then trigger the Tracking->login() event through my app, the listener goes off fine and with the dd function, it will send that data to Segment and I can see it in their live debugger, all is well.
However, when I remove that dd, and the listener goes off and shows as successful - the data is never seen in Segment.
Can someone tell me what i'm missing? This is my first time using the Laravel queue system so a little confused why it might not be working.
For queued jobs, use:
Segment::track($payload);
Segment::flush();

Categories