I m actually facing a problem while developping my chat application between angularjs (BTFord library) and NodeJS Socket.IO...
In fact, I need a user A to send a message to a user B.
- If I log the message in node, it works perfectly, and it's sent only one time.
- In angular js the event on("message") is called 3 or 4 times, and I had the message to send 3 or 4 times
It's a problem for me ... I need to receive the message only one time.
Is there any problem with this ?
Thanks for advance
I had a similar situation, it ends up that the controller was executing more than once, so be aware of that, check if it's running more than once using a message with console.log, beign said that probably you are registering several listeners on the client side to avoid that put a condition in your client: (let's asume that you are registering the events on $rootscope)
if( $rootScope.$$listeners['socket:someListener']===null || $rootScope.$$listeners['socket:someListener']===undefined){
$rootScope.$on('socket:someListener', function(ev, data){
//Client code
}
}
that will register your listener only once!
cheers!
Events handlers are not destroyed when switching/reloading controllers. So you may have subscribed multiples times to your event.
See the docs : https://github.com/btford/angular-socket-io#socketremovelistener
Can you try this :
angular.module('Foo').controller('BarCtrl', function ($scope, socket) {
socket.on('message', function (msg) {
// ... do something
});
$scope.$on('$destroy', function() {
socket.removeListener('message');
});
});
Related
I am using Laravel 5.6.7, Socket.IO and vue.js. I am not using Pusher and redis. Below is my code to send message to user chatting with me one to one.
var url = "http://localhost:6001/apps/My_appId/events?auth_key=My_Key";
var socketId = Echo.socketId();
var request = {
"channel": "private-Send-Message-Channel.2",
"name": "MessengerEvent",
"data": {
"msg": message
},
"socket_id": socketId
};
axios.post(url, JSON.stringify(request)).then((response) => {
//Message Sent
});
I am trying to inform user who is chatting with me that I am still typing. Should I use the same above code which emits xhr on each char type? Is it the only way to inform user that the message typing is still in progress?
Update 1
Is there any better way to post xhr as mentioned above for each key press? I meant if user types 200 chars. will I post xhr 200 times?
or
Do we have an event called whisper and listenForWhisper as shown here https://laravel.com/docs/5.6/broadcasting#client-events ? I am using vue.js and laravel 5.6.7 without pusher and without redis
If you look at the broadcasting documentation you will see two code code snippets which you can use in your Vue.js application.
To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
While the user is typing, you can debounce the whisper method above.
If you don't wish to use another library like lodash, you can implement the debounce by simply wrapping whisper in a timeout. The following method would broadcast the whisper every 300ms:
isTyping() {
let channel = Echo.private('chat');
setTimeout(function() {
channel.whisper('typing', {
name: this.user.name,
typing: true
});
}, 300);
}
The app needs to trigger isTyping() when an onkeydown event occurs in the chat application's input field.
You also need to listen for the whisper once the app is created. The following method will set the typing variable to true for 600ms after the event has been received.
created() {
let _this = this;
Echo.private('chat')
.listenForWhisper('typing', (e) => {
this.user = e.name;
this.typing = e.typing;
// remove is typing indicator after 0.6s
setTimeout(function() {
_this.typing = false
}, 600);
});
},
I am no Laravel expert, but I've faced this problem before.
First, let's define what "typing" means. The simplest way to define it is to say that a user is typing if and only if the input field to send a message is not empty.
This is not perfect, because the user can go away from keyboard in the middle of typing a message then not returning to complete and/or send it, but it is good enough.
More importantly, we now don't need to care about key strokes to know if the user is typing. In fact, "user is typing" now becomes as easy as chat_input_box.length > 0 to represent in code.
This boolean value is what needs to be synced across users/servers, not the act of hitting a key on the keyboard by the user. However, to keep the value up to date, we need to catch input events on chat_input_box and if the boolean value has changed since before this current event has occurred, socket.io should be able send a signal signifying whether the user has stopped or started typing.
On the receiving side, this signal toggles appropriate views to appear or disappear to indicate the state of the app to the user in human terms.
For the shortcoming of a user typing something then leaving, a timeout can be set so that when it is finished the boolean value "is typing" resets to false, while the act of typing something resets the timeout then starts it again automatically.
You don't have to send an xhr request to your app. You can just
broadcast events directly to the chat users without hitting your app.
From the Laravel docs:
Sometimes you may wish to broadcast an event to other connected clients without hitting your Laravel application at all. This can be particularly useful for things like "typing" notifications, where you want to alert users of your application that another user is typing a message on a given screen. To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
Yes you are right, it should not be emitting on every character change instead you could use debouncing to wait for a small time and then fire the function.
I would recommend using lodash library's debounce method. It should be something like this.
Kindly have a look at the documentation: https://lodash.com/docs/4.17.5#debounce
Laravel's Echo also sounds good, as you'll be doing nothing with typing action on the back-end thus just emitting from client to client is better than involving the server.
I am working on a site that uses the great jsPlumb library to create a node interface.
jsPlumb has an event 'beforeDrop' that is triggered before a connection between two endpoints are connected, that I want to use to check a condition, and then decide to allow the connection or not.
It the connection is not allowed, I want to use ngToast to show a message to the user.
This is my 'beforeDrop' function
jsPlumb.bind('beforeDrop', function(info){
// Check that they property types match
var outNodeType = $('#'+info.sourceId).data( "ptype" );
var inNodeType = $('#'+info.targetId).data( "ptype" );
if(outNodeType !== inNodeType){
showMessage('warning', '<strong>Error:</strong> unable to connect '+outNodeType+' to '+inNodeType)
return false // false for not establishing new connection
}
return true; // true for establishing new connection
});
And this is the function that shows the ngToast message:
function showMessage(messageType, message){
ngToast.warning({
class: messageType,
content: message
});
}
The problem is that the ngToast message does not appear until I click anywhere on the page. Once I click, the message appears and everything works.
I don't know if this is an issue with jsPlumb and angularjs, or a problem with how I am calling the ngToast function.
I would really appreciate any suggestions as to how to resolve this. TIA!
jsPlumb event will be conaidered as event outside of angular context. Seems like you are calling angular code from outside of angular code. For make sync angular, you need to call $scope.apply() after calling the toaster message method. So that toast will get shown as soon as you clicked on it.
I am facing a rather strange issue with AngularJs.
I have an AngularJS (AJS henceforth) application which is listening to certain SignalR hubs.
When a SignalR hub receives a message (and the data object is passed in that call too), it fires an event in AJS which in turn shows a Toastr on the UI.
Now I have around 5 different types of SignalR hubs that initiate different Toasts on the UI.
I added another Toast (as per the new requirement) which is initiated by yet another SignalR hub.
Problem:
This new toast that I have implemented does show up when the SignalR hub receives a message, but the data is not rendered. What I mean is that the toast shows up with {{ }} these brackets.
Other toasts are following the same routine and are showing up just fine, except for the new one.
The strangest part is that I have another AJS method running at an interval of around 30 seconds that fetches some count values from the database (using WEB API in the background). And when this action completes the {{ }} disappear and the values show up just fine (I keep the toastr alive for that while by keeping my mouse cursor over it, or else it will disappear).
//SignalR hub initiates the following method
$scope.$on("on_remote_event", function (argEvent, argData) {
var toastrHtml = "<div class='my-toast-style'><div class='myLabel'>Client : {{ argData.ClientName }}</div><div class='myLabel'>Order Date: {{ argData.OrderedOn | date:'MM/dd/yyyy hh:mm a' }} EST</div></div>";
generateAlertToast(toastrHtml, "Order Info", argData);
});
//this method will generate the toast as per the information passed to it
function generateAlertToast(toastrHtml, toastrTitle, argData) {
toastr.options = {
"closeButton": true,
"newestOnTop": true,
"progressBar": true,
"preventDuplicates": true,
"timeOut": 60000,
"positionClass": "toast-bottom-right"
}
var scope = $rootScope.$new();
scope.argData = argData;
//console.info(scope.argData);
var compiledHtml = $compile(toastrHtml)(scope);
toastr.info(compiledHtml, toastrTitle);
};
Now I know that somehow AJS is not evaluating the html for the toastr. But how and where is the problem is what i am banging my head against!!
Please help.
So sorry guys!!
It was my stupid mistake that I made in the SignalR side code due to which the above mentioned code was not working.
Basically when I broadcast the message received from SignalR I forgot to enclose the broadcasting line in the rootscope apply.
$rootScope.$apply(function () {
//broadcast event here
});
And since I did not write the rootscope apply, AJS was not able to run the digest cycle and hence was waiting for me or some other method to initiate the digest cycle and evaluate the values on the UI.
Lesson Learnt.
Thank you :)
I am facing a strange issue with calling socket.on methods from the Javascript client. Consider below code:
for(var i=0;i<2;i++) {
var socket = io.connect('http://localhost:5000/');
socket.emit('getLoad');
socket.on('cpuUsage',function(data) {
document.write(data);
});
}
Here basically I am calling a cpuUsage event which is emitted by socket server, but for each iteration I am getting the same value. This is the output:
0.03549148310035006
0.03549148310035006
0.03549148310035006
0.03549148310035006
Edit: Server side code, basically I am using node-usage library to calculate CPU usage:
socket.on('getLoad', function (data) {
usage.lookup(pid, function(err, result) {
cpuUsage = result.cpu;
memUsage = result.memory;
console.log("Cpu Usage1: " + cpuUsage);
console.log("Cpu Usage2: " + memUsage);
/*socket.emit('cpuUsage',result.cpu);
socket.emit('memUsage',result.memory);*/
socket.emit('cpuUsage',cpuUsage);
socket.emit('memUsage',memUsage);
});
});
Where as in the server side, I am getting different values for each emit and socket.on. I am very much feeling strange why this is happening. I tried setting data = null after each socket.on call, but still it prints the same value. I don't know what phrase to search, so I posted. Can anyone please guide me?
Please note: I am basically Java developer and have a less experience in Javascript side.
You are making the assumption that when you use .emit(), a subsequent .on() will wait for a reply, but that's not how socket.io works.
Your code basically does this:
it emits two getLoad messages directly after each other (which is probably why the returning value is the same);
it installs two handlers for a returning cpuUsage message being sent by the server;
This also means that each time you run your loop, you're installing more and more handlers for the same message.
Now I'm not sure what exactly it is you want. If you want to periodically request the CPU load, use setInterval or setTimeout. If you want to send a message to the server and want to 'wait' for a response, you may want to use acknowledgement functions (not very well documented, but see this blog post).
But you should assume that for each type of message, you should only call socket.on('MESSAGETYPE', ) once during the runtime of your code.
EDIT: here's an example client-side setup for a periodic poll of the data:
var socket = io.connect(...);
socket.on('connect', function() {
// Handle the server response:
socket.on('cpuUsage', function(data) {
document.write(data);
});
// Start an interval to query the server for the load every 30 seconds:
setInterval(function() {
socket.emit('getLoad');
}, 30 * 1000); // milliseconds
});
Use this line instead:
var socket = io.connect('iptoserver', {'force new connection': true});
Replace iptoserver with the actual ip to the server of course, in this case localhost.
Edit.
That is, if you want to create multiple clients.
Else you have to place your initiation of the socket variable before the for loop.
I suspected the call returns average CPU usage at the time of startup, which seems to be the case here. Checking the node-usage documentation page (average-cpu-usage-vs-current-cpu-usage) I found:
By default CPU Percentage provided is an average from the starting
time of the process. It does not correctly reflect the current CPU
usage. (this is also a problem with linux ps utility)
But If you call usage.lookup() continuously for a given pid, you can
turn on keepHistory flag and you'll get the CPU usage since last time
you track the usage. This reflects the current CPU usage.
Also given the example how to use it.
var pid = process.pid;
var options = { keepHistory: true }
usage.lookup(pid, options, function(err, result) {
});
I'm working on an online, turned based game in order to teach myself Node.js and Socket.IO. Some aspects of the game are resolved serverside. At one point during one of these functions, the server may require input from the clients. Is there a way I can "pause" the resolution of the server's function in order to wait for the clients to respond (via a var x = window.prompt)?
Here's an idea of the code I'm working with:
Server:
for (some loop){
if (some condition){
request input via io.sockets.socket(userSocket[i]).emit('requestInput', data)
}
}
Client:
socket.on('requestInput', function (data) {
var input = window.prompt('What is your input regarding ' + data + '?');
//send input back to the server
socket.emit('refresh', input)
});
Any thoughts?
I don't think that is possible.
for (some loop){
if (some condition){
request input via io.sockets.socket(userSocket[i]).emit('requestInput', data)
/* Even if you were able to pause the execution here, there is no way to resume it when client emits the 'refresh' event with user input */
}
}
What you can do instead is emit all 'requestInput' events without pausing and save all responses you will get in socket.on('refresh',function(){}) event in an array, then you can process this array later. I don't know what your exact requirement is but let me know if that works.
Since you are emitting socket.emit('refresh', input) on the client side, you just need to set up a socket event listener on the server side as well. For example:
io.sockets.on('connection', function (socket) {
socket.on('refresh', function (data) {
console.log(data) //input
});
})
I will also point out, so that you don't run into trouble down the line, that indefinite loops are a big nono in node. Nodejs runs on a single thread so you are actually blocking ALL clients as long as your loop is running.