Laravel Notification multiple users with pusherjs - javascript

What I'm doing in sending a Notification to multiple users with Laravel Facade
Notification::send($users, new PartenaireSendCRNotification($data));
This is the PartenaireSendCRNotification class:
// I need the Database and Broadcast on Pusher channel
public function via($notifiable)
{
return ['database', 'broadcast'];
}
// Name of the Pusher channel
public function broadcastOn()
{
return ["new-cr-from-part"];
}
// Send data
public function toArray($notifiable)
{
return [
'data' => $this->data
];
}
On the Front End (blade and js):
#if(Auth::user()->isAdmin || Auth::user()->isConfirmation)
<script>
// ... some pusher configuration
// subscribe to the channel 'new-cr-from-part'
let channel = pusher.subscribe('new-cr-from-part');
// listen for event BroadcastNotificationCreated
channel.bind(
'Illuminate\\Notifications\\Events\\BroadcastNotificationCreated',
function(data) {
console.log(data);
// Some javascript
receiveCRNotification();
});
</script>
#endif
Only 6 users match the condition to execute the script for listening to the channel.
When I test it, every thing works fine (I have 6 notifications in the Database) but the js after the binding to the channel is executed 6 times for every user.
What I'm missing?

Related

Laravel websocket wont trigger event from vue front end

I already have setup my websocket connection with pusher. I can fire events at the websocket admin and i can show the output of it via console.log. Now i created a new event that if the user adds new product, the table will be updated whenever who is viewing it. But it seems I can add data successfully but the table wont update to other user. Can someone know why my event is not working?
ProductsEvent.php
namespace App\Events;
//show only important imports
use Illuminate\Broadcasting\Channel;
use App\Product; //Import my model
class ProductsEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $product;
public function __construct(Product $product)
{
$this->product = $product;
}
public function broadcastOn()
{
return new Channel('Products');
}
}
ProductsControlller (store)
public function store(Request $request)
{
$product = new Product;
//some validation...
//broadcast(new ProductsEvent($product)); if i put it here i got No query results for model [App\Product].
$product->barcode = $request->barcode;
$product->product_name = $request->product_name;
$product->price = $request->price;
$product->quantity = 0;
$product->category = $request->category;
$product->supplier_id = $request->supplier;
$product->save();
broadcast(new ProductsEvent($product));
}
channels.php
Broadcast::channel('Products',function(){
return true;
});
and my vue component
created(){
//when i successfully created the data,
i will call getProducts to listen in the channel for DOM updates
Echo.join('Products')
.listen('ProductsEvent',(event)=>{
this.getProducts()
})
}
If i call broadcast before save in my controller, I got something like this
No query results for model [App\Product].
I uncomented the App\Providers\BroadcastServiceProvider::class, line in config.php in order for the broadcast to work.
I dont know why .join wont work but I used window.Echo.channel i doubt this is the right thing to do.
created(){
this.getProducts()
this.getSuppliers()
// Echo.join('Products')
// .listen('ProductsEvent',(event)=>{
// // this.products.push(event.products)
// this.getProducts()
// })
// .here(()=>{
// console.log('here')
// })
window.Echo.channel('Products').listen('ProductsEvent',(e)=>{
this.getProducts()
toastr.success('Product Updated')
})
}

Observable emits all values on subcribe

I've got some code that looks like this (this is an excerpt of a much larger project):
this.oldMessagesSubject = new Subject<Message[]>();
this.oldMessages = this.oldMessagesSubject.asObservable();
getOldMessages(): Observable<Message[]> {
this.chatHub.server.getMessages();
return this.oldMessages;
}
I then subscribe to it like this:
this.chatService.getOldMessages()
.subscribe(res => this.messages = res);
I'm not very experienced with ReactiveX, but when I subscribe to the observable returned by getOldMessages(), it iterates through each of the values that it has received since the app started. So if I get the old messages for 'chat 1' that works fine. But if I then navigate to a different page and get the old messages for 'chat 2' the observable emits the messages for 'chat 1' and then the messages for 'chat 2'.
I have a feeling I'm using the Observables in the wrong way and I'd really appreciate any help.
EDIT:
This is where next is called:
this.chatHub.client.showMessages = (messages: Message[]) => this.oldMessagesSubject.next(messages);
getMessages is an RPC to the server. Here is the relevant method on the server (C#):
public Task GetMessages()
{
try
{
// Get the current user
var user = repo.GetUser(Context.User.Identity.Name);
// Associate the current user with a connection
var connection = chatContext.Find(x => x.UserId == user.UserId);
if (connection != null)
{
// Get all the messages in the user's chat (encrypted)
List<EncryptedMessage> encryptedMessages = repo.GetMessages(connection.ChatId);
List<Message> messages = new List<Message>();
// Get the decrpytion key
byte[] key = Encoding.Default.GetBytes(ConfigurationManager.AppSettings["secret"]).Take(16).ToArray();
// Decrypt the messages
foreach (var encryptedMessage in encryptedMessages)
{
Message message = new Message();
message.Id = encryptedMessage.Id;
message.GroupId = encryptedMessage.GroupId;
message.Owner = encryptedMessage.Owner;
message.Sent = encryptedMessage.Sent;
message.Body = cryptoProvider.DecryptMessage(encryptedMessage.Body, key);
messages.Add(message);
}
// Return the messages to the client
return Clients.Caller.ShowMessages(messages);
}
return Clients.Caller.LoginError();
}
catch (Exception ex)
{
return Clients.Caller.Exception(ex);
}
}
When I'm back at my workstation, I'll debug and check that the problem isn't server side. I should be able to tell if next is being called multiple times on each subscription.

Wait for callback to finish on during pubnub history() call on client reconnect

I have two channels for my subscribers: Broadcast and Unique channel. On the Broadcast channel I have all the Subscribers listening to. The Unique channel is for One-To-One communication between the Publisher and the Subscriber.
I need to achieve the following solution: If the Subscriber goes offline/loses connection, after he comes back online he needs to poll the two channels for the single latest message on each of them and determine if the message is still valid based on the property in the message object:
//HERE IS THE MESSAGE OBJECT THAT THE PUBLISHER SENDS ON THE BROADCAST AND THE UNIQUE
//CHANNELS TO THE SUBSCRIBERS.
message = {
alarm: null, //BOOLEAN: DESIGNATES IF THE ALARM IS ON/OFF
body: null, //STRING: SOME MESSAGE/ALARM TEXT
image: null, //STRING: SOME IMAGE IF YOU WANT TO APPEAR WITH THE ALARM TEXT
createdAt: null, //UNIX TIMESTAMP OF WHEN THE MESSAGE WAS CREATED/SENT
validUntil: null //UNIX TIMESTAMP - AFTER THIS PERIOD THE MESSAGE IS CONSIDERED INACTIVE AND THE SUBSCRIBER SHOULD IGNORE THIS MESSAGE ON RECONNECT
};
Here is my sample code for the Subscriber(The problem is marked in the comments in the code):
$(document).ready(function(){
var uuid = PUBNUB.uuid(),
id = 'vlatkorun-' + uuid,
controlChannel = 'vlatkorun-control',
broadcastChannel = 'vlatkorun-broadcast',
uniqueChannel = 'vlatkorun-unique-';
var state = {
id: id,
uniqueChannel: uniqueChannel
};
//INIT PUBNUB
var pubnub = PUBNUB.init({
subscribe_key : 'YYY',
keepalive: 30
});
//SUBSCRIBE TO THE CONTROL CHANNEL FIRST
//THE CONTROL CHANNEL IS FOR MAINTENANCE BETWEEN PUBLISHER
//AND THE SUBSCRIBERS
pubnub.subscribe ({
channel: controlChannel,
message: function(m){console.log(m)},
state: state,
heartbeat: 30,
connect: function(m){/*console.log(JSON.stringify(m))*/},
reconnect: function(m){console.log(JSON.stringify(m))}
});
//NOW SUBSCRIBE TO THE BROADCAST AND THE UNIQUE CHANNELS
pubnub.subscribe ({
channel: [broadcastChannel, uniqueChannel],
state: state,
message: function(data){
//SHOW THE ALARM IN THE BROWSER WHEN MESSAGE ARRIVES
//OUTPUT OMMITED
},
connect: function(m){
//THIS ARRAY IS GOING TO HOLD THE LATEST MESSAGES FROT THE BOTH CHANNELS
var channelLatestMessages = [];
//GET THE MOST RECENT MESSAGE ON THE BROACAST CHANNEL
pubnub.history({
channel: broadcastChannel,
count: 1,
callback: function (m) {
if(m[0].length > 0)
{
//GO OVER THE RETURNED MESSAGES AND PUT THEM IN THE ARRAY FOR COMPARING LATER
$.each(m[0], function(index, value){
channelLatestMessages.push(value);
});
}
//I HAVE THE VARIABLE POPULATED WITH THE MESSAGES FROM THE CHANNEL IN THE CALLBACK
console.info(channelLatestMessages);
},
});
//GET THE MOST RECENT MESSAGE ON THE UNIQUE CHANNEL
pubnub.history({
channel: uniqueChannel,
count: 1,
callback: function (m) {
if(m[0].length > 0)
{
//GO OVER THE RETURNED MESSAGES AND PUT THEM IN THE ARRAY FOR COMPARING LATER
$.each(m[0], function(index, value){
channelLatestMessages.push(value);
});
}
//I HAVE THE VARIABLE POPULATED WITH THE MESSAGES FROM THE CHANNEL IN THE CALLBACK
console.info(channelLatestMessages);
},
});
//I HAVE THE VARIABLE POPULATED WITH THE MESSAGES FROM THE CHANNEL IN THE CALLBACK, BUT HERE THE ARRAY IS EMPTY BECAUSE THE CALLBACKS ARENT
//FISHED.
//HERE IS MY QUESTION: HOW CAN I WAIT FOR THE CALLBACKS TO FINISH SO I CAN CONTINUE WITH MY CODE BELLOW???
console.info(channelLatestMessages);
//IF THERE ARE NO MESSAGES DO NOTHING
if(channelLatestMessages.length == 0) return;
//ASSUME THAT THE FIRST MESSAGE IN THE ARRAY IS THE MOST RECENT
latestMessage = channelLatestMessages[0];
//NOW FIGURE OUT THE MOST RECENT MESSAGE
$.each(channelLatestMessages, function(index, message){
if(latestMessage.createdAt < message.createdAt)
{
latestMessage = message;
}
});
//GET THE CURRENT DATE IN UNIX TIMESTAMP
var currentDate = parseInt(moment().format('X'));
//CHECK IF THE MESSAGE VALIDITY IS EXPIRED
if(currentDate > latestMessage.validUntil) return;
//HERE WE CAN SHOW THE LATEST MESSAGE IN THE BROWSER
//OUTPUT OMMITED
},
reconnect: function(m){//THE SAME LOGIN LIKE IN THE CONNECT METHOD APPLIES HERE}
});
});
How to wait for the callbacks to finish so I can have the channelLatestMessages filled with the latest messages from the Broadcast and Unique channels so I can further determine which message is more recent and if the more recent message is still active?
Can your logic run like this:
historyCall-1's callback calls historyCall-2.
historyCall-2's callback calls "Message Age Detection" logic.
This way, we make the async act synchronously, and the only way "Message Age Detection" logic runs is if we know for a fact that history calls 1 and 2 are complete.
?
geremy

SignalR join/leave group doesn't work correctly

I have a simple application using SignalR, where I wan't to display different data for different machines, depending on which machine has been chosen by the user
My Hub class looks like this:
readonly ISprayBroadcaster _sprayBroadcaster;
readonly IWorkRecordRepository _workRecordRepository;
public SprayHub(ISprayBroadcaster sprayBroadcaster, IWorkRecordRepository workRecordRepository)
{
_sprayBroadcaster = sprayBroadcaster;
_workRecordRepository = workRecordRepository;
}
public void Broadcast(string name)
{
Process.DataProcess(_workRecordRepository, Clients, name).Wait();
}
public void SwapGroup(string previousGroup, string newGroup)
{
Groups.Remove(Context.ConnectionId, previousGroup);
Groups.Add(Context.ConnectionId, newGroup);
}
public void JoinGroup(string groupName)
{
Groups.Add(Context.ConnectionId, groupName);
}
This is how I initialize the hub on the client side and call Broadcast method on it:
$(function () {
hub = $.connection.sprayHub;
function init() {
hub.server.joinGroup("machine1");
hub.server.broadcast("machine1");
};
// Client-side hub method that the server will call
hub.client.updateData = function (shifts) {
ViewModel.Measurements(recreateArray(shifts));
}
$.connection.hub.start().done(init);
});
Broadcast method goes to the DataProcess method which populates data to the clients from the assigned group:
public static async Task DataProcess(IWorkRecordRepository workRecordRepository, IHubConnectionContext hubClients, string machine)
{
var shiftRecords = await workRecordRepository.Records(machine, DateTime.Now).ToList();
var result = SLGT.Sentinel.Core.Calculations.Shifts(shiftRecords);
hubClients.Group(machine).updateData(result);
}
At the same time I setup a broadcaster which runs in the loop and feeds clients with appropriate data. This is a broadcast method from the broadcaster which calls the same DataProcess method to populate data for each machine found in the system:
void Broadcast(object state)
{
lock (_updateLock)
{
if (_updating)
return;
_updating = true;
var machines = _workRecordRepository.Machines();
machines.Subscribe(async machine =>
{
await Process.DataProcess(_workRecordRepository, Clients, machine);
});
_updating = false;
}
}
Finally when user clicks a button for different machine on the client side I swap the groups for the appropriate data to be displayed for this client:
$(".machineButton").click(function () {
var name = $(this).attr("id");
hub.server.swapGroup(previousGroup, name);
previousGroup = name;
}
Now, when I run this application in my test environment, everything works fine. When I run it on the server, swap between the groups doesn't work correctly, and the client is constantly fed with the same set of data. Why might it be happening? As I said local version works fine so I do not know how to debug it?
The group management methods (add and remove) are async. If you don't await the returned task then send to the group immediately after you have a race condition such that the client you just added might not receive the message. Also, you should never call .Wait() from in a hub method. Make the hub method async and await it instead.
readonly ISprayBroadcaster _sprayBroadcaster;
readonly IWorkRecordRepository _workRecordRepository;
public SprayHub(ISprayBroadcaster sprayBroadcaster, IWorkRecordRepository workRecordRepository)
{
_sprayBroadcaster = sprayBroadcaster;
_workRecordRepository = workRecordRepository;
}
public async Task Broadcast(string name)
{
await Process.DataProcess(_workRecordRepository, Clients, name);
}
public async Task SwapGroup(string previousGroup, string newGroup)
{
await Groups.Remove(Context.ConnectionId, previousGroup);
await Groups.Add(Context.ConnectionId, newGroup);
}
public async Task JoinGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
}
Also, is your production environment a single web server or is it a load-balanced farm? If it's a farm you'll need to configure SignalR scale-out.

How to get a list of connected clients on SignalR

I am quite new to SignalR.
My first assignment is to make simple chat app.
I have been browsing and reading and finally made a page where you come and chat and it works fine.
Now I need to show a list of connected clients. To achieve this I have wrote the following code.
This is my HUB.
public class ChatHub: Hub
{
chatEntities dc = new chatEntities();
public void Send(string message,string clientName)
{
Clients.addMessage(message,clientName);
}
// I want to save the user into my database, when they join
public void Joined(string userId,string userName)
{
CurrentChattingUsers cu = new CurrentChattingUsers();
cu.ConnectionId = userId;
cu.UserName = userName;
dc.CurrentChattingUsers.AddObject(cu);
dc.SaveChanges();
Clients.joins(userId, userName);
}
// This will return a list of connected user from my db table.
public List<ClientModel> GetConnectedUsers()
{
var query = (from k in dc.CurrentChattingUsers
select new ClientModel()
{
FullName = k.UserName,
UserId = k.ConnectionId
}).ToList();
return query;
}
}
And thats it...Now what??
Am I going to the right direction? If, I am then how to call this methods from the view?
Some good suggestions will really help me out.
cheers
EDIT:
I have added the following script when the hub start
$.connection.hub.start(function () {
chat.getConnectedUsers();
});
This is the method that returns the client names in my Hub
public List<ClientModel> GetConnectedUsers()
{
var data = (from k in dc.Users
select new ClientModel()
{
FullName = k.UserName
}).ToList();
Clients.loadUsers(data);
return data;
}
in firebug i can see it returns something as follows;
{"State":{},"Result":[{"FullName":"mokarom","UserId":null}, {"FullName":"aka8000","UserId":null},{"FullName":"johnnyno5","UserId":null},{"FullName":"reza","UserId":null},{"FullName":"amyo","UserId":null},{"FullName":"rezatech","UserId":null}],"Id":"0","Error":null,"StackTrace":null}
But, how would I display that in my view??
EDIT:
this the complete view so far
<script type="text/javascript">
var chat;
var myClientName
$(document).ready(function(){
myClientName = '#Request.Cookies["ChatterName"].Value';
// Created proxy
chat = $.connection.chatHub;
// Assign a function to be called by the server
chat.addMessage = onAddMessage;
// Register a function with the button click
$("#broadcast").click(onBroadcast);
$('#message').keydown(function (e) {
if (e.which == 13) { //Enter
e.preventDefault();
onBroadcast();
}
});
// Start the connection
$.connection.hub.start(function () {
chat.getConnectedUsers();
});
chat.loadUsers = function (data) {
loadUsers(data);
};
});
function onAddMessage(message,clientName) {
// Add the message to the list
$('#messages').append('<div class="chatterName">' + clientName + ' </div><div class="chatterMessage"> ' + message + '</div><div class="clear">');
}
function onBroadcast() {
// Call the chat method on the server
chat.send($('#message').val(), myClientName);
$('#message').val('');
}
function loadUsers(data) {
$('#clientList').html(data.Result[0].FullName);
}
</script>
Problem: don't see anything here: $('#clientList').html(data.Result[0].FullName);
firebug says 'data is not defined'
JavaScript
var chats = $.connection.chatHub;
chats.loadUsers = function (data) { loadUsers(data); };
var connectedUserCount = 0;
$.connection.hub.start(function ()
{ chats.getConnectedUsers(); });
function loadUsers = = function (data) {
console.log(data); //so you can see your data in firebug.etc
//which signal r will have converted to json so you could try
var numberOfUsers = data.length;
}
Once hub is started chats would have all the public functions of your hub available as javascript functions. This is what the signalr/hubs creates using the best available connection method between client and server.
In reverse your C# hub will have access to any javascripts functions you setup, e.g.
Clients.loadUsers(query);
//add this to you server side just before you return query
ps - you might also consider using OnConnectedAsync, though of course you might still persist these. I'm also waiting for full support for web farm support using sql, which is in the pipeline.

Categories