Unable to broadcast messages using Flask-socketio and Javascript - javascript

I'm trying to build a simple chatroom application using Flask and socket-io. When a user types a message and clicks on the submit button, the message should be broadcasted to all users. But in my case, after clicking the submit button the page refreshes and nothing appears. Please help.
I have tried running the Javascript code in the firefox browser console and it works fine there(without forms). But when I submit the form from webpage the problem arises.
here's is some code snippet:
python backend:
#socketio.on('send messages')
def vote(data):
messages = data['messages']
emit('announce messages', {"messages":messages}, broadcast=True)
javascript:
document.addEventListener('DOMContentLoaded', () => {
// Connect to websocket
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
// When connected, configure buttons
socket.on('connect', () => {
document.querySelector('#form').onsubmit = () => {
const messages = document.querySelector('#messages').value;
socket.emit('send messages', {'messages': messages});
}
});
socket.on('announce messages', data => {
const li = document.createElement('li');
li.innerHTML = `${data.messages}`;
document.querySelector('#push').append(li);
});
});
HTML form
<ul id="push">
<!--filled by socket.io-->
</ul>
<form id="form">
<label for="messages">Message:</label>
<input id="messages" type="text" name="messages">
<input type="submit" value="Submit">
</form>

When you submit your form, you have to prevent it from actually submitting by returning False so that the connection doesn't drop out. In your case, page is getting refreshed and therefore you are not able to see the result.
socket.on('connect', () => {
document.querySelector('#form').onsubmit = () => {
const messages = document.querySelector('#messages').value;
socket.emit('send messages', {'messages': messages});
return False
}
});
Maybe this will do the work.

Related

reCaptcha V3 fails validation on first form submission only

I am trying to set up reCaptcha v3 and it sort of works. For some reason the first time I submit the form it fails but from the second submit onwards it is fine. I can't figure out why this is happening?
<script src="https://www.google.com/recaptcha/api.js?render=MY_SITE_KEY"></script>
<script>
grecaptcha.ready(function () {
grecaptcha.execute('MY_SITE_KEY', { action: 'contact' }).then(function (token) {
var recaptchaResponse = document.getElementById('captcha-response');
recaptchaResponse.value = token;
});
});
</script>
<input type="hidden" name="captcha-response" id="captcha-response">
PHP
$verifyResponse = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret='.$secretKey.'&response='.$_POST['captcha-response']);
$responseData = json_decode($verifyResponse);
if(!$responseData->score < 0.5) {
$message .= "Verification failed " . $responseData->score;
}
When I submit the form the first time, I get the validation error but my score is 0.9.
Why you have added "!" with "$responseData->score"? you may need to replace your condition with the following:
Replace this:
if(!$responseData->score < 0.5) {
$message .= "Verification failed " . $responseData->score;
}
With this one:
if($responseData->score < 0.5) {
$message .= "Verification failed " . $responseData->score;
}
P.S: Following code takes few seconds to properly load and get a "captcha-reponse" code, so you may need to disable all submit button and wait till you got a "captcha-reponse" to enable the submit button in form or you needs to implementent another way to delay the submit to execute only once you got a "captcha-response" code otherwise you will keep getting "missing-input-response" error message
<script src="https://www.google.com/recaptcha/api.js?render=MY_SITE_KEY"></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('MY_SITE_KEY', {
action: 'contact'
}).then(function(token) {
var recaptchaResponse = document.getElementById('captcha-response');
recaptchaResponse.value = token;
});
});
</script>
You should re-generate the reCaptcha token after error form validation occured.
The token reCaptcha only valid for ONE TIME.
So, you have two options to fixes this issue.
1. Reload the page when error occured
This is the easiest way. You only need to reload the page whenever form validation error occured.
Of course, this will trigger the reCaptcha to generate new token.
2. Handle with AJAX (Non-reload page)
This is the best approach, since this will helps the user not losing the form data and continue to fill the form.
So, here's what you should do.
<!-- Put this hidden input inside of your form tag -->
<input name="_recaptcha" type="hidden">
<script src="https://www.google.com/recaptcha/api.js?render=YOUR_SITEKEY_HERE"></script>
<script>
// This will generate reCaptcha token and set to input hidden
const generateRecaptcha = function() {
grecaptcha.execute(
"YOUR_SITEKEY_HERE", {
action: "YOUR_ACTION_NAME"
}).then(function(token) {
if (token) {
document.querySelector("input[name='_recaptcha']").value = token;
}
});
}
// Call it when page successfully loaded
grecaptcha.ready(function() {
generateRecaptcha();
});
// Do your AJAX code here
$.ajax({
url: "https://example.com",
success: function(response) {
console.log(response);
},
error: function(error) {
// Call again the generator token reCaptcha whenever error occured
generateRecaptcha();
}
</script>
Don't forget to put your Site key and your action name. Make sure the action name matches with your Backend action name.
Medium Article

How to be sure if a callback function has called

How to make sure a callback function has called(fired)?
I am using socket.io and callback function please check out my code:
// server
socket.emit('message', message, function() {
// some code
});
// client
socket.on('message', function(data, callback) {
callback(); // confirm we received the message
// some code
});
I want to know in the server code, to detect whether function has called in the client side or no.
If I understood you well, to detect in your server whether your callback has been called in the client or not, you can use a timer in the server and emit a confirmation in the client.
Let's me explain it further.
1) Server
// Timer to wait for your confirmation
let timer
// Listen message from the Client
socket.on('message', msg => {
// Wait 5 s for confirmation
timer = setTimeout(() => noConfirmation(), 5000)
// Send message to the Clients
io.emit('message', msg)
})
2) Client
// Listen message from the Server
socket.on('message', msg => {
// Send confirmation (your callback)
socket.emit('confirmation', id)
})
3) Server
// Listen confirmation from the Client
socket.on('confirmation', id => {
// Avoid timer
clearTimeout(timer)
// Send back confirmation
io.emit('confirmation', id)
})
Here you are a full working example:
Server (index.js)
const app = require('express')()
const http = require('http').createServer(app)
const io = require('socket.io')(http)
app.get('/', function(req, res){
res.sendFile(__dirname + '/index.html')
})
// Timer to wait for your confirmation
let timer
io.on('connection', socket => {
console.log('a user connected')
socket.on('disconnect', () =>
console.log('user disconnected'))
// Listen message from the Client
socket.on('message', msg => {
console.log(`message: ${msg}`)
// Wait 5 s for confirmation
timer = setTimeout(() => console.log('☓'), 5000)
// Send message to the Clients
io.emit('message', msg)
})
socket.on('confirmation', id => {
console.log('✓')
// Avoid timer
clearTimeout(timer)
// Send back confirmation
io.emit('confirmation', id)
})
})
http.listen(3000, () =>
console.log('listening on *:3000'))
Client (index.html)
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io/socket.io.js"></script>
<script>
const socket = io()
// Submit message to the Server
const $input = document.querySelector('input');
document.querySelector('form').onsubmit = e => {
e.preventDefault() // prevents page reloading
socket.emit('message', $input.value)
$input.value = ''
}
// Listen message from the Server
const $messages = document.querySelector('#messages');
socket.on('message', msg => {
const id = new Date().getTime()
$messages.insertAdjacentHTML('beforeend', `<li id="m${id}">${msg}</li>`)
// Send confirmation
socket.emit('confirmation', id)
})
// Confirmation message recived
socket.on('confirmation', id =>
document.querySelector(`#m${id}`).insertAdjacentText('beforeend', '✓'))
</script>
</body>
If you don't receive the check symbol (✓) it means there was a problem with the messages and in the server after 5 seconds we show a cross symbol (☓), also you can use any function you want in both cases. We are covering both directions.
Hope this help or at least point you to the right direction : )
You can transform your function to make it async (See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)
It will look like this:
socket.on('message', async function(data, callback) {
const result = await callback(); // confirm we received the message
console.log(result); //returns whatever the callback function returned
});

Concise example of how to join and leave rooms using Flask and Socket.io?

I'm trying to use socket.io with a Flask server connecting to JS.. I'm struggling with basically everything, but my first step is to make it so that users can connect to different channels. My broadcast message function is working, but when I click on a different channel, the messages do not get sent to a different channel.. What am I doing wrong?
JS:
document.addEventListener('DOMContentLoaded', ()=>{
// Send user back to login page if they didn't sign in
const username = localStorage.getItem('username');
if (username == null){
window.location = "/";
}
// Switch button active class when clicked
$('.list-group .list-group-item.list-group-item-action').click(function(e) {
$('.list-group .list-group-item.list-group-item-action.active').removeClass('active');
var $this = $(this);
if (!$this.hasClass('active')) {
$this.addClass('active');
}
e.preventDefault();
});
// Connect to socket.io
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port);
socket.on('connect', () => {
// Automatically connect to general channel
socket.emit('join',{"channel": "general", "username":username});
// When a channel is clicked, connect to that channel
document.querySelectorAll('.list-group-item').forEach(function(channel){
channel.onclick = () =>{
socket.emit('join',{"channel":channel.innerHTML, "username":username});
return false;
}
});
// When a message is sent, call 'send message' function from server
document.querySelector('#send-message').onsubmit = () => {
const message = document.querySelector('#m').value
socket.emit('send message', {'message': message});
// Clear message form
document.querySelector('#m').value = "";
return false;
};
});
// Callback from server for sending messages
socket.on('broadcast message', data =>{
console.log(data);
// Append message to list of messages
const li = document.createElement('li');
li.innerHTML = `${data.message}`;
document.querySelector('#messages').append(li);
});
});
Python Flask:
import os
from flask import Flask, render_template, url_for
from flask_socketio import SocketIO, emit, join_room, leave_room
from collections import defaultdict
app = Flask(__name__)
app.config["SECRET_KEY"] = os.getenv("SECRET_KEY")
socketio = SocketIO(app)
messages = defaultdict(list)
channels = ["Programming"]
#app.route("/")
def index():
return render_template("login.html")
#app.route("/chatroom/")
def chatroom():
return render_template("chatroom.html", channels=channels, messages=messages)
#socketio.on("send message")
def message(data):
print(data)
emit("broadcast message", {"message": message}, broadcast=True)
#socketio.on('join')
def on_join(data):
username = data['username']
channel = data['channel']
join_room(channel)
#send(username + ' has entered the room.', channel=channel)
if __name__ == '__main__':
socketio.run(app)
Think of a room as an array of users that stays on the server. When you send your message in "send message", you set broadcast=True, so it sends it as a global message to all users, as long as they are connected. If you only want to send to users in specific rooms, you will need to specify which room you want to send the message to from the client, each time you send a message, like this:
// client.js
socket.emit('join', { 'channel': channel, ... });
socket.emit('send message', {'message': message, 'channel': channel});
// server.py
#socketio.on("send message")
def message(data):
room = data['channel']
emit('broadcast message', data['message'], room=room)
I believe the first answer is correct. Just as a note, avoid using 'innerhtml' where possible especially in this case. By setting the innerhtml, anything a user writes in a message will be treated as html at the other end. This includes script tags which means someone could remotely run javascript on someone else's machine by sending a malicious message.
I would suggest using innerText or textContent. This will treat the message as plain text not html. They are slightly different so it may be worth looking into which one you need.
I would have done this as a comment but my rep isn't high enough.
tl:dr Use textContent or innerText instead of innerhtml.

socket.io Disconnect certain client

I am working with socket.io example for chat project. I want to add password protection to the chat application. This is just simple authentication. If user input wrong password, then server will disconnect this user.
Client side code:
var socket = io();
socket.emit('user name', username);
socket.emit('password', password);
Server side code:
socket.on('password', function (msg) {
if (msg !== '123321') {
io.emit('system info', 'Wrong password... Disconnecting ' + username+'...');
io.close(true);
return false;
}
});
The close() method doesn't work. the disconnected user still able to receive message from other users. So how to do this properly? Thank you.
Assuming you are creating an id (server side) with something like:
io.engine.generateId = (req) => {
return custom_id++; // custom id must be unique
}
You could close an specific connection using .disconnect() and you could call the id with socket.id
socket.on('password', function (msg) {
if (msg !== '123321') {
io.emit('system info', 'Wrong password... Disconnecting ' + username+'...');
io.sockets.connected[socket.id].disconnect();//<-- in this case it's just a number
}
});

When namespacing I receive a "cannot GET error" displayed in browser

I'm using namespaces to differentiate between version of my socket.io chat app, and I'm having trouble with a "cannot GET error displayed in browser."
I plan on continually updating a chat app I made in a basic socket.io tutorial, and I want to be able to launch any version of it at any time. I'm going to do this by the use of namespaces. When I launch my app in browser at the location myserverlocation/v0.0.1 to access version 0.0.1 of my app, I get an error that states cannot GET '/v0.0.1'.
This is my server code:
var app = require('express')(),
server = require('http').Server(app),
io = require('socket.io').listen(server),
chat = io.of('/v0.0.1');
server.listen(80);
// routing
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
// usernames which are currently connected to the chat
var usernames = {};
chat.on('connection', function (socket) {
// when the client emits 'sendchat', this listens and executes
socket.on('sendchat', function (data) {
// we tell the client to execute 'updatechat' with 2 parameters
io.sockets.emit('updatechat', socket.username, data);
});
// when the client emits 'adduser', this listens and executes
socket.on('adduser', function(username) {
// we store the username in the socket session for this client
socket.username = username;
// add the client's username to the global list
usernames[username] = username;
// echo to client they've connected
socket.emit('updatechat', 'SERVER', 'you have connected');
// echo globally (all clients) that a person has connected
socket.broadcast.emit('updatechat', 'SERVER', username + ' has connected');
// update the list of users in chat, client-side
io.sockets.emit('updateusers', usernames);
});
// when the user disconnects.. perform this
socket.on('disconnect', function() {
// remove the username from global usernames list
delete usernames[socket.username];
// update list of users in chat, client-side
io.sockets.emit('updateusers', usernames);
// echo globally that this client has left
socket.broadcast.emit('updatechat', 'SERVER', socket.username + ' has disconnected');
});
});
And this is my client code:
<script src="/socket.io/socket.io.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script>
var socket = io.connect('myserverlocation');
var chat = socket.of('/v0.0.1');
// on connection to server, ask for user's name with an anonymous callback
chat.on('connect', function(){
// call the server-side function 'adduser' and send one parameter (value of prompt)
chat.emit('adduser', prompt("What's your name?"));
});
// listener, whenever the server emits 'updatechat', this updates the chat body
chat.on('updatechat', function(username, data) {
$('#conversation').append('<b>' + username + ':</b> ' + data + '<br>');
});
// listener, whenever the server emits 'updateusers', this updates the username list
chat.on('updateusers', function(data) {
$('#users').empty();
$.each(data, function(key, value) {
$('#users').append('<div>' + key + '</div>');
});
});
// on load of page
$(function(){
// when the client clicks SEND
$('#datasend').click( function() {
var message = $('#data').val();
$('#data').val('');
// tell server to execute 'sendchat' and send along one parameter
chat.emit('sendchat', message);
});
// when the client hits ENTER on their keyboard
$('#data').keypress(function(e) {
if(e.which == 13) {
$(this).blur();
$('#datasend').focus().click();
}
});
});
</script>
<div style="float:left;width:100px;border-right:1px solid black;height:300px;padding:10px;overflow:scroll-y;">
<b>USERS</b>
<div id="users"></div>
</div>
<div style="float:left;width:300px;height:250px;overflow:scroll-y;padding:10px;">
<div id="conversation"></div>
<input id="data" style="width:200px;" />
<input type="button" id="datasend" value="send" />
</div>
My chat app works fine without the use of namespaces, at myserverlocation/. I cannot figure out why I keep getting this error. After some investigation I think my usage of io.of() is incorrect, but I cannot seem to fix the problem. I'm not sure if my problem lies in the server code, the client code, or both.
Edit: After more investigation, I think my problem lies in the follow segment of code (though I could be mistaken):
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
Edit2: The problem did in fact lie in the code segment above. I should have been sending my whole /Chat directory as static content instead of using res.sendfile() to send one file. I will formally answer my own question when stackoverflow lets me (I have to wait 8 hours to answer my own question).
I managed to find what my problem was. The problem lied in the following section of code:
app.get('/', function (req, res) {
res.sendfile(__dirname + '/index.html');
});
I was sending one particular file upon connection to my server, when I should be sending my entire /Chat directory as static content. This way, I can chose what version of my chat app I would like to launch. I managed to do this by changing a few lines of code in my server code:
var express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io').listen(server);
server.listen(80);
// Chat directory
app.use(express.static('/home/david/Chat'));

Categories

Resources