socket.emit() fires twice - socket.io - node.js

I'm making a one-to-one chatroom.
Here is my basic algorithm:
accept user's input (nickname) by a form
fetch all the existing connections and rooms from server
check if this nickname already exists:
if exists, form submission return false ; if not exists, ......
Now I'm having a socket.emit() twice bug on step 2.
Here is my client side code:
$('#login').submit(function(e){
socket.emit('getRoomInfo');
socket.on('receiveRoomInfo',function(info){
//Here has some code to fetch person_room list from server.
//e.g, person_room[nickname]= roomNO
if(person_room[nickname]){
alert('User already exists!');
}else{
//show chat
//hide the form
//update person_room list
//do something
}
});
return false;
});
Here is my server side code:
socket.on('getRoomInfo',function(){
socket.emit('receiveRoomInfo', {'person_room': person_room, 'roomNO': roomNO});
});
Bug Description:
Suppose I open up one form (index.html), and input 'John Snow', submit the form. Everything works fine, form gets hidden, chat shows up. Waiting for
the other connection...
Then I open up another form (index.html), and input 'John Snow', submit the form. Window alerts 'User already exists!'. Form isn't submitted.
I erase 'John Snow' and input 'Arya Stark', submit the form. Window still alerts
'User already exists!', but this time, form gets hidden, chat shows
up.
What I've test:
Login form is only submitted once
On client side, socket.emit('getRoomInfo') is triggered twice under Bug Description 3, which means socket.on('receiveRoomInfo') is triggered twice as well. So what happens is, 'Arya Stark' gets pushed into the list by the first time, and in the second time, it turns out she already exists in the list, Hence window still alerts 'User already exists!'.
I've researched a lot of similar questions, but none of them is the same case as this one.
Thanks for help.

I can fix the code, but I'm not able to tell the reason behind this bug.
I discovered that under once form submission, socket.on('receiveRoomInfo') is triggered twice.
So I set a variable var userExists=false under form.submit(),
after detected if(person_room[nickname]), set userExists=true.
Under socket.on('receiveRoomInfo'), surround all the code implementation with if(!userExists), so that we can make sure socket.on('receiveRoomInfo') is only executed once under once form submission.
I will be waiting for few days for a better idea before I vote my own answer.

Related

Update a bot message after responding to a slack dialog

I'm having some issues to update an interactive message after responding to a slack dialog. I'm using botkit on a node.js server.
Here is my workflow:
User trigger an interactive message via a slash command
User click a button on that message
A dialog pops up, user fill the form and validate
Something is done on the server side
The first message should update
Now, here is the logic I'm using:
User trigger an interactive message via a slash command
Nothing fancy, I use:
controller.on('slash_command', function (bot, message)
Then I parse the command, and send the appropriate message, with the appropriate attachments (buttons)
User click a button on that message
Same, I use the event sent by botkit:
controller.on('interactive_message_callback', function (bot, message)
Then I create a dialog:
var dialog = bot.createDialog(
'Which book?',
JSON.stringify(callback),
'Ok'
)
Here I'm doing something really (really) dirty, and should not be done. But that's the only way I found to update the initial message after the dialog is filled.
The callback_id actually contains an object, with the response_urlof the initial message (and something to identify the form).
A dialog pops up, user fill the form and validate
Something is done on the server side
Here, I use once more the event provided by botkit:
controller.on('dialog_submission', function (bot, message)
then I parse the message.submission.callback_id and detect the response_url. With this, I can create an object I call originalMessage.
The first message should update
At the moment I use :
bot.replyInteractive(originalMessage, 'DONE, everything is saved.');
with originalMessagecontaining the response_url of the first message.
It does work. The first message is being replaced by the new one.
But I'm really not happy with that solution, and was wondering if I was missing something somewhere. I've seen couple apps having that type of workflow, so there must be a way.
Thank you for your help :)
I wrote to Slack to ask about this situation and got a great suggestion from Mark P:
Use the state dialog field to pass the original response_url to the dialog. Then when you receive the dialog data, you can use state instead of response_url.
I just tried it and it worked great. No need to store any state on your own server.
I don't know how that would work exactly with Node and botkit, since that's not what I use.
To flesh this out a bit more:
Someone clicks a button and Slack POSTs about that interaction to your configured "Request URL".
From Slack's payload, get the "response_url" value.
When you call dialog.open in the Slack API, pass along this response_url as the "state" value.
When the dialog is submitted, Slack again POSTs to your "Request URL".
From Slack's payload, get the "state" value and use it as a response_url.
Profit!
This only works if you hold the original message object somewhere on your server for future reference.
So on creating the interactive dialog store it somewhere and add a reference. I use uuids.
let newId = uuid();
messageStore[newId] = message;
var dialog = bot.createDialog(
'My Dialog',
'idPrefix_' + newId,
'Submit'
).addText('Sample Input', 'input', '');
bot.replyWithDialog(message, dialog.asObject());
Then once you get your interactive dialog response back disassemble the prefix and the uuid and get your original message object back from the servers memory. Then use ´replayInteractive` there.
controller.on('dialog_submission', function handler(bot, message) {
if (message.callback_id.indexOf('idPrefix') === 0) {
let id = message.callback_id.substr('idPrefix_'.length);
bot.dialogOk();
let originalMessage = messageStore[id];
bot.replyInteractive(originalMessage, {
text: 'replacing the original message with this.'
});
}
});
Be careful that you do not create a memory leak here. You have to find a way to clean up your messageStore over time.

Error: Can't set headers after they are sent only on page refresh

I have this problem only when I try refresh the page and I can not solve it, I tried everything but still happens the same. It began to happen when I add socket.io at the project. The project run in several servers which are connected one each other throught sockets.
TEST CASES: When I render the page, at the first time everything goes well but, if I refresh the same page, I get this error:
ERROR: "Error: Can't set headers after they are sent. at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:344:11)"
ATTENTION: when get in IF() and send "return res.end('The Activation Code is INVALID!');" it DOESN'T HAPPEND! I refresh it and refresh it and everything goes well. My problem is in the RENDER.
MY CODE BELOW:
activationUser = function(req,res,next){
var data = {
activationCode : req.params.activationCode,
now : new Date().valueOf(),
ip : req.connection.remoteAddress,
fId : frontalId
}
socketCore.emit('activationUser', data);
socketCore.on(frontalId + 'activationUserResp', function(data){
if(data.msg == "CHECKED!"){
next();
}else{
return res.end(data.msg);
}
});
}
router.get('/activationUser/:activationCode',activationUser,function(req,res){
var data = {
activationCode : req.params.activationCode,
fId : frontalId
}
socketCore.emit('step2', data);
socketCore.on(frontalId + 'step2Resp', function(data){
if(data.msg == 'err'){
return res.end('The Activation Code is INVALID!');
}else{
return res.render('registro2', {title: 'title | '+ data.name + ' ' + data.lastname, user:data});
}
});
});
Thank you!
The particular error you are getting happens when you try to send anything on the res object after the complete response has already been sent. This often occurs because of errors in asynchronous logic. In your particular case, it apepars to be because you are assigning a new event handler with socketCore.on() every single time the router is hit. Those event handlers will accumulate and after the first time the route is hit, they will execute multiple times triggering the sending of multiple responses on the same response object, thus trigger that error.
The main ways to fix your particular problem are:
Use .once() instead of .on() so the event handler automatically removes itself after being triggered.
Manually remove the .on() event handler after you get the response.
Move the event handler outside of the route so it's only ever installed once.
In your particular case, since socketCore is a shared object available to all requests, it appears that you also have a race condition. If multiple users trigger the '/activationUser/:activationCode' route in the same general time frame, then you will register two event handlers with socketCore.on() (one for each route that is hit) and you will do two socketCore.emit('step2', data);. But, you have no way of associating which response belongs with which request and the two responses could easily get mixed up - going to the wrong request.
This highlights how socket.io connections are not request/response. They are message/answer, but unless you manually code a correspondence between a specific message request and a specific answer, there is no way to correlate which goes with which. So, without assigning some particular responseID that lets you know which response belongs to which message, you can't use a socket.io connection like this in a multi-user environment. It will just cause race conditions. It's actually simpler to use an HTTP request/response for this type of data fetching because each response goes only with the request that made it in the HTTP architecture.
You can change your architecture for making the socketCore request, but you will have to manually assign an ID to each request and make sure the server is sending back that ID with the response that belongs to that request. Then, you can write a few lines of code on the receiving side of things that will make sure the right response gets fed to the code with the matching request.

Why won't my suitescript deploy scripts fire?

I don't know when this started, but I think it happened after I did some refactoring using the IDE with renaming.
Anyway, if I attach the script through the form, they fire. However, my user event, nor client scripts fire though there is a deployment record. That deployment record uses the same script that works IF it is attached via the form custom code area.
What happened?
EDIT:
For Instance:
Trying to add a button to opportunity:
function userEventBeforeLoad(type, form, request){
var list = form.getSubList("item");
list.addButton('custpage_customconfigurebutton', 'Configure', 'clientStartConfigurator()');
}
Upload Script
Add to "Script"
Deploy:
It never fires when I "Create Opportunity"?
NONE of my user event scripts are firing
EDIT 2 (NEW SCREENS as requested
Following lines of code working for me
function userEventBeforeLoad(type, form, request) {
//nlapiLogExecution('error', 'type', type);
var list = form.getSubList("item");
list.addButton('custpage_customconfigurebutton', 'Configure',"alert('Hello World')");
}
I suspect you might have an error in your clientStartConfigurator(). To verify, you can also use the browser console on click of your button to see whether you're successfully returning from your respective function or not.
Hope this will help you.

"immediate_failed" - Could not automatially log in the user

I have a problem when I developed my website with Google+ sign-in:
I did step by step that the doc told me but I always failed at step4:
https://developers.google.com/+/web/signin/
the result was always ""immediate_failed" - Could not automatially log in the user", I just don't kown why, can anyone help me, thanks very much! :-(
Note that in the sample code you pointed to, the "immediate_failed" check is commented out. This is intentional, since the first time a user encounters the Sign-in button on the page, it will fail.
The reason it fails is that when the page first loads, before the user even presses the button, a request is sent to Google to determine if the user has already logged in (via Google or another site, for example). If they are - there is no need for them to log in again, so the button never needs to be shown. But if they have not been logged in already, you will get the "immediate_failed" response, and will need to either show (or not clear) the button.
tl;dr - Don't worry aout getting immediate_failed when the page first loads. This is normal.
As a workaround I use gapi.auth.authorize method in the gapi.auth.signIn callback. Here is my code:
gapi.auth.signIn({
'callback': gPlusLoginCallback
});
function gPlusLoginCallback(authResult) {
if (authResult['status']['signed_in']) {
doSmth(authRes['access_token']);
} else if (authResult['error'] == "immediate_failed") {
gapi.auth.authorize({
client_id: gplusClientId,
scope: 'https://www.googleapis.com/auth/plus.login email',
immediate: true
}, function (authRes) {
if (authRes['status']['signed_in']) {
doSmth(authRes['access_token']);
}
});
}
}
function doSmth(accessToken){
//Do smth
}
Change this setting "immediate: true", to be false " immediate: false".
But if you like to make more complex implementation look at the first sample here https://developers.google.com/api-client-library/javascript/start/start-js. You have to calls to Google's "gapi.auth.authorize({...", the first one with "immediate: true", and the second one with "immediate: false".
The question is old but I faced this issue recently.
In my case, it was because I specified the URI parameter prompt to none. I guess Google doesn't like that if the user has never been logged to your platform before.
Whenever I changed that to consent or totally removed it, it worked great.
In my case, the error was because of explicitly specifying the authorization parameter prompt to 'none',similar to a previous answer.
It worked for me by specifying prompt=None or as per the official docs,you may skip this parameter.

Node.js res.send is not working

In my index.js, I have an exports function that is supposed to send data back to the client via ajax on pressing a submit button. However, when the user presses submit, the data seems to get sent over before it the data gets modified. When pressing submit one more time, it sends the data that was previously modified as if clicking the submit button only sends the 'previously' set data. This is my code:
var tabledata = getRecordFromDatabase(key);
if(tabledata.length === 0)
tabledata = 'There is no matched record in the database';
res.contentType('text/html');
res.send({'matched':tabledata});
So to illustrate the error: I click submit after filling out a form and receive back the message "There is no matched record in the database". I hit submit a second time without changing anything in the form I just filled. This time record data is actually sent to me. Why could this be?
If whatever you're doing in getRecordFromDatabase is asynchronous and non-blocking, then node.js is behaving as it should. Node.js is non-blocking - it doesn't stop and wait for processes to complete (unless those processes are intentionally written to block, which is usually avoided in node.js). This is beneficial, because it keeps the server free to accept new requests and process many requests at once.
If your database call is asynchronous, you're not waiting for it to return before you res.send(). That's why your first submit returns back empty. Most likely, by the time you hit submit a second time, your DB call has finally returned, and that's why you get a result.
It's hard to give you a code-based answer to your problem, because you abstracted away what is happening in your DB call method. But typically, an asynchronous call would go something like:
getRecordFromDatabase(key, function(err, data){
if(data.length === 0)
data = 'There is no matched record in the database';
res.contentType('text/html');
res.send({'matched':data});
});
This way, you are passing a function to execute as a callback to your asynchronous method - when the async call completes, it executes the callback, which then executes the res.send() with the appropriate data.

Resources