Trigger an event to private channel in react app - reactjs

I want to trigger an event to pusher private channel and my server side language is laravel I reviewed a lot of resources, but I did not find a comprehensive approach which covers both the server side and the front side Finally I got this solution
in the first step :
export const SendChat = () => {
try {
var pusher = new Pusher('YOUR_APP_KEY', {
cluster: 'ap2',
forceTLS: true,
authTransport: 'jsonp',
authEndpoint: `${baseUrl}pusher/auth`,
});
var channel = pusher.subscribe('private-channel');
channel.bind('pusher:subscription_succeeded', function() {
var triggered = channel.trigger('client-EVENT_NAME', { 'message': 'Hi ....' });
console.log(triggered)
});
} catch (error) {
console.error(error);
}
}
and call it somewhere
<Button onClick={this.props.SendChat} waves='light' >Send</Button>
you must Enable client events in pusher account setting
login to your pusher account -> select the channel ->App Settings -> select Enable client events -> update
add your app key, channel name and event name after that we need authorization in server side this is sample laravel code first add this route in web.php
Route::get('pusher/auth', 'PusherController#pusherAuth');
make PusherController.php like this :
public function pusherAuth()
{
$user = auth()->user();
if ($user) {
$pusher = new Pusher('auth_key', 'secret', 'app_id');
$auth= $pusher->socket_auth(Input::get('channel_name'), Input::get('socket_id'));
$callback = str_replace('\\', '', $_GET['callback']);
header('Content-Type: application/javascript');
echo($callback . '(' . $auth . ');');
return;
}else {
header('', true, 403);
echo "Forbidden";
return;
}
}
test it you should see something like this
Pusher : State changed : connecting -> connected with new socket ID 3953.****556
Pusher : Event sent : {"event":"pusher:subscribe","data":{"auth":"83045ed1350e63c912f5:328fb78165d01f7d6ef3bb6d4a30e07c9c0ad0283751fc2c34d484d4fd744be2","channel":"private-chat"}}
Pusher : Event sent : {"event":"client-MessageSent","data":{"message":"Hi ...."},"channel":"private-chat"}
true

It doesn't matter much which client-side language you are using. Angular, Vue, React they all are JS framework and libraries. And, you can consider using a generic JS code which you can place in all 3 apps.
Let me try to give you a detailed answer I can give as per my knowledge.
In order to get started, you should first complete try to complete Chat scenario without pusher. i.e: user should be able to send a message from front-end via the API and it should be stored inside the database.
Once you have done this it is very easy to include pusher in the flow. ( In simple words, you'll have to broadcast an event and that'll inform the Socket Server to broadcast a message to all/other user(s) on the channel )
For Pusher Authentication, you don't need to explicitly create a route and a method. Once you have uncommented BroadcastServiceProvider inside config/app.php. You can run:
php artisan route:list
and, you'll see a route for broadcast broadcasting/auth.
You can use this route to authenticate. Although, you can make few changes and prepend /api before this.
Go into BroadcastServiceProvider.php and replace your boot method with:
public function boot()
{
Broadcast::routes(
[
'prefix' => 'api',
'as' => 'api.broadcasting.auth',
'middleware' => ['auth:sanctum'],
]
);
require base_path('routes/channels.php');
}
I assume you're using Laravel Sanctum for Authentication. If not you need to change the authentication middleware to your provider.
Once done, you can authenticate from frontend using this auth route. So, what I have done is created a service in ReactJS and in the constructor I have created a Pusher instance :
this.pusher = new Pusher(PUSHER_APP_KEY, {
authEndpoint: 'http:localhost:8000/api/broadcasting/auth',
cluster: PUSHER_CLUSTER,
useTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + authHeader
}
}
});
You only need to instantiate your Pusher once and use this instance throughout the app. So, that's why I have created a service class for Pusher.
If you want things to be simple for now you need to execute this code on the page where you will use pusher. Once the Page load, you need to call this code. So, you'll do:
let pusher = null;
useEffect(() => {
pusher = new Pusher(PUSHER_APP_KEY, {
authEndpoint: 'http:localhost:8000/api/broadcasting/auth',
cluster: PUSHER_CLUSTER,
useTLS: true,
auth: {
headers: {
Authorization: 'Bearer ' + authHeader
}
}
});
}, []);
So, this way we have an instance of Pusher in our functional component or page.
Now, we need to subscribe to channel.
Using this instance of pusher we can subscribe to channels. If you have followed the useEffect approach on the same page then, right after getting the instance you can subscribe to channels and bind to events using this code:
const channel = pusher.subscribe('private-chat.' + channelName)
And, to bind to an event you can do:
channel.bind('event.name', function(data) {
console.log(data);
});
Make sure to replace "channelName" and "event.name" with your channel and event name respectively.
Now you'll be able to listen to your event once broadcasted from the backend.
So, you'll do something like this from the backend. You'll have a method that will store the message inside the database so, let's say that code is:
public function sendMessage (Request $request){
//.... Rest of the logic
$user = $request->user();
// Store the message
$chatMessage = $chat->messages()->create([
'message' => $message,
'sender_id' => $user->id
]);
broadcast(new NewMessage($user, $chatMessage))->toOthers();
//... Rest of the logic
}
This broadcast message will send this message to other user in the chat.
I hope this answer gives you a good idea and direction.

For work with WebSockets via Pusher on Laravel, I recommended using the package Laravel Echo for React part. And on the backend side in config/broadcasting.php setup configuration for Pusher.
See more detail on official documentation Laravel how to use Pusher on the backend side and frontend side.
https://laravel.com/docs/8.x/broadcasting#pusher-channels

Related

laravel reactjs pusher: presence channel response is 302

I'm trying to make a reactjs application where an user can only login to one device at the time with the same user credentials. Unfortunately it isn't working.
I'm trying to authenticate a presence channel with reactjs to laravel but I get a 302 response.
reactjs:
Pusher.logToConsole = true;
var pusher = new Pusher("9028d58568392772df59", {
cluster: "eu",
forceTLS: true,
authEndpoint: '/broadcasting/auth',
auth: {
headers: {
'X-CSRF-Token': csrf_token
}
}
});
var channel = pusher.subscribe("presence-HandleCredentials");
channel.bind("sameCredentials", function(data) {
console.log(data);
alert(JSON.stringify(data));
});
channel:
Broadcast::channel('App.User', function ($user, $id = 1) {
return (int) $user->id === (int) $id;
});
broadcast:
public function boot()
{
Broadcast::routes(['middleware' => ['auth:web']]);
require base_path('routes/channels.php');
}
When I added this ['middleware' => ['auth:web']] I got the error. Before I added that I got a 403 error.
in the config\app.php I uncommented App\Providers\BroadcastServiceProvider::class,
Are there any tutorials out there that are build with laravel and reactjs for a presence channel?
does anyone know how to get past this 302 redirect?
Recently had the same issue with my laravel-websockets and laravel echo.
In my case I was unable to solve the 302, as Broadcast was unable to authenticate my logged in user. I was trying to subscribe to my private channel. So the workaround i found was that i manually created a POST route in web.php as "/broadcasting/auth". This is what my front-end requests to. So The updated code in web.php is as follows.
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' => env('APP_URL'),
'port' => 6001,
'scheme' => 'http',
)
);
return $pusher->socket_auth($request->request->get('channel_name'),$request->request->get('socket_id'));
});
I was creating my own websocket that is why i had to mention the host & port within the options, you don't need to use it if you are Using Pusher. You can also add other middlewares to the routes if needed.
You have to comment out the following line in app/providers/BroadcastServiceProvider:
public function boot()
{
// Broadcast::routes();
require base_path('routes/channels.php');
}
so that the request can reach my broadcasting/auth route in web.php.
Try this. now this should return a 200 when the broadcasting/auth is requested by your client end with response of an auth code. Do let me know if this solves your problem.

How to connect to Laravel Websocket with React?

I'm building an ordering app, where I do the backend with Laravel and the front end with both ReactJS and React Native
I want real-time updates whenever a customer posts an order and whenever an order gets updated.
Currently, I managed to get WebSocket running that uses the pusher API using devmarketer his tutorial.
I'm successful in echoing the order in the console, but now I want to access the channel using my react app
And at this step is where I am facing difficulties.
As I'm unsure how to create a route that is accessible to both my apps and how to access the channel through this route.
The official laravel documentation gives an example of how to access pusher but not how to connect to it with for example an outside connection (example: my react native app)
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'rapio1',
host: 'http://backend.rapio',
authEndpoint: 'http://backend.rapio/broadcasting/auth',
auth: {
headers: {
// Authorization: `Bearer ${token}`,
Accept: 'application/json',
},
}
// encrypted: true
});
window.Echo.channel('rapio.1').listen('OrderPushed', (e) =>{
console.log(e.order)
})
So my question is how can I access a broadcasting channel on my React apps?
ADDED BACKEND EVENT
class OrderPushed implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $neworder;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Order $neworder)
{
$this->neworder = $neworder;
}
/**
* Get the channels the event should broadcast on.
*
* #return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
//return new Channel('Rapio.'.$this->neworder->id);
return new Channel('Rapio');
}
public function broadcastWith()
{
return [
'status' => $this->neworder->status,
'user' => $this->neworder->user->id,
];
}
}
Are you using the broadcastAs() method on the backend?
It's important to know this in order to answer your question properly because if you are, the Laravel echo client assumes that the namespace is App\OrderPushed.
When using broadcastAs() you need to prefix it with a dot, to tell echo not to use the namespacing so in your example, it would be:
.listen('.OrderPushed')
Also, you don't need to do any additional setup on the backend in order for each client application to connect to the socket server unless you want to have a multi-tenancy setup whereby different backend applications will make use of the WebSockets server.
I also use wsHost and wsPort instead of just host and port, not sure if that makes a difference though
If you can access the data on the frontend by simply console.log'ing to the console you should already be most of the way there.
The way you would actually get the data into your react components depends on if you're using a state management library (such as redux) or just pure react.
Basically, you would maintain a local copy of the data on the frontend and then use the Echo events to update that data. For example, you could have a list of orders in either redux, one of your react components, or somewhere else, that you could append to and modify based on creation, update, and deletion events.
I would personally create an OrderCreated, OrderUpdated, and OrderDeleted event on the backend that would contain the given order model.
class OrdersList extends React.Component {
componentDidMount() {
this.fetchInitialDataUsingHttp();
//Set up listeners when the component is being mounted
window.Echo.channel('rapio.1').listen('OrderCreated', (e) =>{
this.addNewOrder(e.order);
}).listen('OrderUpdated', (e) =>{
this.updateOrder(e.order);
}).listen('OrderDeleted', (e) =>{
this.removeOrder(e.order);
});
}
componentWillUnmount() {
//#TODO: Disconnect echo
}
}

Can't use "this" in stomp client subscribe - React

I have my Spring-Boot service setup so I can send messages through websocket to my browser and it works.
//#MessageMapping
#RequestMapping(value = "/notify")
#SubscribeMapping("/notification")
#SendTo("/topic/notification")
public String sendNotification() throws Exception {
sendMessage();
return "Request to update Tanks has been sent!";
}
public void sendMessage() {
this.messagingTemplate.convertAndSend("/topic/notification", "IT WORKS");
}
Here's the console log from chrome:
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-1519225601109-13
message-id:f2qodiqn-8
content-length:8
IT WORKS
I want to be able to receive a message from the service and update the state in react, so, that it refetches from the backend. This is what my client looks like:
var socket = new SockJS("http://localhost:6667/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
console.log(notification.body);
//this.showNotification(JSON.parse(notification.body).content);
//this.showNotification(notification.body);
})
}, function(err) {
console.log('err', err);
});
And the fetch in componentDidMount()
fetch(`http://localhost:6666/front/objects`)
.then(result=>result.json())
.then(fuelTanks=>this.setState({fuelTanks}))
.catch(function(err) {
console.log('Could not fetch: ' + err.message);
}
)
I can't use this.showNotification(notification.body), hence I can't set the state to be able to refetch my objects. I tried making methods outside the class but then I can't use anything from the main class.
Is there a way to make react run componentDidMount again, or better, just access the fetch method in my class when I get a message from spring through the websocket?
Like this:
componentDidMount(){
var socket = new SockJS("http://192.168.1.139:8610/refresh");
var stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('connected: ' + frame);
stompClient.subscribe('/topic/notification', function(notification){
refetchTanks(); // call fetch tanks -> can't use "this"
})
}, function(err) {
console.log('err', err);
});
Thanks!
I know, it is a bit old question, but since it pops every time when you search for stomp issue, i thought of answering it. The way to access this in callbacks is to bind callbacks with this first, then the whole of object can be accessed in the callback.
Example:
connectCallBack(){
this.setState({loading:false})
}
errorCallback=()=>{
}
componentDidMount() {
axios.post('http://localhost:8080/subscribe', null, { params: {
deviceId
}})
.then(response => response.status)
.catch(err => console.warn(err));
const socket = new SockJS('http://localhost:8080/test');
const stompClient = Stomp.over(socket);
//stompClient.connect();
stompClient.connect( {}, this.connectCallBack, this.errorCallback);
If see above code both callbacks can access this.
I tried everything to be able to use my class methods and the state in stompClient's .subscribe method. I was able to connect and reconnect if the service died, nevertheless it wasn't working.
I decided to use react-stomp, which worked. I could use a class method in onMessage=.... This is what my code looks like:
<SockJsClient
url = 'http://localhost:8610/refresh/'
topics={['/topic/notification']}
onConnect={console.log("Connection established!")}
onDisconnect={console.log("Disconnected!")}
onMessage={() => this.update()} <------ this method performs a new GET
request
debug= {true}
/>
I also had to send the message in a specific way on the server side, since I was getting a JSON error when sending a string.
this.messagingTemplate.send("/topic/notification", "{"text":"text"}");
<<< MESSAGE
destination:/topic/notification
content-type:text/plain;charset=UTF-8
subscription:sub-0
message-id:aaylfxl4-1
content-length:49
{
"text": "text"
}
It currently works, but I am curious if there are other, better solutions to this issue.
EDIT: a much better solution here! Use the code from the first post and create a variable before connect to be able to access this like this var self = this;, then just access is as self.update() after subscribe!

AngularJS - POST request reload

I am searching solution for this question more than 3 day and can't find anything..
I have ionic3 App and working width Http POST requests. I am sending requests to my php server and geting data..
My data-api.ts (provider)
public getNotifications(token){
return this.http.post(this.sharedVars.getApi(),"action=messages/notification&token="+token, this.options
).map(res => res.json());
}
profilePage.ts
notifications() {
this.api.getNotifications(this.user.token).subscribe(
data => {
if(data.err == 0){
this.notifications = data.data;
}
},
err => {
console.log(err);
}
);
}
This is working functions and I am getting right output (1) when click this function. but on x action on my server notification count will changed to 2, 3, 4 etc.. and I want load this function not on click, but on page load. so If this.notifications have new value I want change value live (like as firebase)
Example 2:
I have send message action in my data-api.ts (provider)
public sendMessage(token, to, message, attachment){
return this.http.post(this.sharedVars.getApi(),"action=messages/send&token="+token+"&to="+to+"&message="+message+"&attachment="+attachment, this.options
).map(res => res.json());
}
and also have function to get this messages.
public getActivity(token){
return this.http.post(this.sharedVars.getApi(),"action=messages/getActivity&token="+token, this.options
).map(res => res.json());
}
so if I am making post request to sendMessage then I want listen live getActivity action and load new message in my page but not reload.. like as firebase..
I hope this question is clear. because I am not english speaker and tryng to find solution. Tanks
Listening actively to live events is not possible with a single HTTP request in angular.
However you might look into eventSources.
Look at this question for using with angular 2+ :
Creating an RxJS Observable from a (server sent) EventSource

How to redirect to CodeIgniter controller using angular js

I am using CodeIgniter controller functions.
(example)
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Me extends CI_Controller {
public function __construct()
{
parent::__construct();
if (is_logged_in()){if (is_admin()) { redirect('login'); }}
else { redirect('login');}
}
public function change_password()
{
$id=$this->session->userdata['user_data']['id'];
$data = json_decode(file_get_contents("php://input"));
$my_data=array(
'pass'=>$data->pass,
'new_pass'=>$data->new_pass,
);
$result=$this->vanesh_model->change_pass($id,$my_data);
if($result==1)
{
$arr = array('msg' => "Password changed successfuly.", 'error' => '');
$jsn = json_encode($arr);
print_r($jsn);
}
else if($result==2)
{
$arr = array('msg' => "", 'error' => 'Old Password is Invalid');
$jsn = json_encode($arr);
print_r($jsn);
}
else if($result==3)
{
$arr = array('msg' => "", 'error' => 'Sorry, Password change failed');
$jsn = json_encode($arr);
print_r($jsn);
}
}
}
?>
I am afraid of using angular session services, so I want to maintain sessions with only CI. What I am doing in my application is add, update, delete only if he is logged in. And I am using information stored in session. Consider the situation, suppose, I am logged in and doing something, side by side: I destroy the session using browser tools. Now I am continuing with application (doing operations like: change password). I have/had maintained error messages, success messages, its ok. If session OFF, it gives error message. But instead of error messages, I want to redirect to LOGIN page(with page refresh).
Note: For CI Login controller, I didn't used angular js. I have used angularjs only after login.
If by opening new tab I destroy the session, and come back to application's tab: I am able to perform tasks(may be with errors,). If session is OFF I see this in Browser's console: http://localhost/ums/login
This is because of CI constructor(please look over the code).
You should separate angular and CI as much as possible, since both have view-controller it creates a mess. Instead you should have CI in a separate folder, call it api, for example, after that anything you will need from CI should be acessed from angular with ajax calls.
I made a small webapp a while ago and this seemed to be the best way to organize code.
Few updates have been made to angular since then so if there's a better way please let me know
Solved.
Used javascript function. Checking session by http request everytime. If response comes "1". Means redirect to login as:
/* function for checking logged-in and role */
function check_session()
{
$.get("servercontroller/check_session", function(data, status){
if(data=="1") /* error 1 => un-athorized user */
{
window.location.href="/login-page-url";
}
});
}

Resources