I am working on a chat-app with Socket.IO (server using flask-SocketIO). Users can create new channels (rooms) and switch between them. In my code below for some reason, every time I switch (back) to a room (even having a single room and "switching" back to it), the "broadcast message"-handler function gets executed an additional time. I.e. if I send "Hello" on "channel_1", switch back to another channel and then back to "channel_1", then send "Hello again", it gets broadcasted (console.log in my example) TWICE. And next time I switch back to "channel_1", 3 TIMES, etc. I figured it must have something to do with the JS-code, maybe the way connectSocket() is called, because the flask-app only emits "broadcast message" once each time. Apologies for the lengthy code - I left out irrelevant bits as best I could.
document.addEventListener('DOMContentLoaded', () => {
// IF USER SWITCHES / SELECTS EXISTING CHANNEL
document.querySelector('#select_channel').onsubmit = () => {
var channel = document.querySelector('select').value;
const r2 = newXHR();
r2.open('POST', '/select_channel');
const data = new FormData();
data.append('channel', channel);
r2.onload = () => {
connectSocket(channel);
};
r2.send(data);
return false;
}
// IF USER CREATES NEW CHANNEL
document.querySelector('#new_channel').onsubmit = () => {
const new_channel_name = document.querySelector('#new_channel_name').value;
const r1 = newXHR();
r1.open('POST', '/new_channel');
const data = new FormData();
data.append('new_channel_name', new_channel_name);
r1.onload = () => {
const response = JSON.parse(r1.responseText);
if (response.channel_exists) {
alert("Channel already exists");
return false;
}
else {
const option = document.createElement('option');
option.innerHTML = new_channel_name;
document.querySelector('select').append(option);
connectSocket(new_channel_name);
document.getElementById('new_channel').reset();
}
};
r1.send(data);
return false;
};
});
function connectSocket(channel) {
var socket = io();
socket.on('connect', () => {
// if user previously connected to any channel, disconnect him
if (localStorage.getItem('channel') != null)
{
socket.emit('leave', {'room': localStorage.getItem('channel'), 'username': display_name});
}
socket.emit('join', {'room': channel, 'username': display_name});
localStorage.setItem('channel', channel);
const data = new FormData();
data.append('username', display_name);
data.append('room', channel);
document.querySelector('#current_channel').innerHTML = channel;
});
document.querySelector('#send_message').onsubmit = () => {
var message = document.querySelector('#message').value;
socket.emit('send', {'message': message, 'room': channel});
console.log(`SENDING ${message}`);
return false;
}
// PROBLEM: EVERY TIME CHANNEL CHANGED AND MSG SENT IN THAT CHANNEL -> 1 EXTRA COPY OF THAT MESSAGE IS BROADCAST - I>E> THE BELOW IS DONE +1 TIMES
socket.on('broadcast message', function handle_broadcast (data) {
console.log(data);
});
}
The Python snippets:
# [IMPORT & CONFIG STATEMENTS...]
socketio = SocketIO(app, logger=True, engineio_logger=True)
# Global variables
channels = []
messagetext = None
#app.route("/select_channel", methods=["GET", "POST"])
def select_channel():
if request.method == "POST":
channel = request.form.get("channel")
session["channel"] = channel
return jsonify({"success": True})
return render_template("chat.html", channels = channels)
#app.route("/new_channel", methods=["GET", "POST"])
def new_channel():
if request.method == "POST":
new_channel = request.form.get("new_channel_name")
if new_channel in channels:
return jsonify({"channel_exists": True})
else:
channels.append(new_channel)
session["channel"] = new_channel
return json.dumps(channels)
return render_template("chat.html", channels = channels)
#socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
send(username + ' has entered the room.', room=room)
#socketio.on('leave')
def on_leave(data):
username = data['username']
room = data['room']
leave_room(room)
send(username + ' has left the room.', room=room)
#socketio.on("send")
def handle_send(data):
messagetext = data["message"]
room = data["room"]
emit("broadcast message", {"message": messagetext}, room=room)
if __name__ == '__main__':
socketio.run(app, debug=True)
I think what's happening is that in the Flask-SocketIO library, when you join a room, if you don't pass in a sid, it uses flask.request.sid. I'm not sure what Flask-SocketIO is using for that property, but my guess is that when you join a room, one sid is set. And when you leave a room, maybe a different sid is being used, which means that your original client didn't actually leave the room. So when they join again, a new connection is made (meaning a second, concurrent connection) which could explain why you get a broadcast message multiple times.
I would recommending trying to create your own sid to pass into the join_room() and leave_room() functions to see if that resolves the issue. You can pass it in from the client to your server and just for testing it could be something simple like session1.
I hope this helps.
Related
I'm using Django(3.2.11) with the Postgres database. I'm creating a chat app with Django channels. so I created a message consumer and save it into the database. and also created a message history consumer by room. now I want to create a user list consumer which has dynamic messages with the user name. for that I made a consumer who returns infinite data.
like:-
class ChatListConsumer(AsyncJsonWebsocketConsumer):
async def connect(self):
self.room_group_name = 'chat_list_%s' % self.scope['user'].user_id
self.connection = True
await self.channel_layer.group_add(
self.room_group_name,
self.channel_name
)
await self.accept()
async def disconnect(self, close_code):
self.connection = False
await self.channel_layer.group_discard(
self.room_group_name,
self.channel_name
)
def get_serializer(self, obj):
return {'data': ProductQueryUsersSerializer(
instance=obj.order_by('-updated_at'), many=True
).data, 'type': "chat_list"}
async def receive(self, text_data):
while self.connection:
try:
self.query = await database_sync_to_async(
ProductQueryUsers.objects.filter
)(Q(seller__user=self.scope['user']) | Q(user=self.scope['user'])
)
data = await database_sync_to_async(self.get_serializer)(self.query)
except EmptyPage:
data = {'data': [], 'type': "chat_list"}
await self.send(text_data=json.dumps(data))
and call this in javascript like:-
<div id="message"></div>
<script>
const chatSocket = new WebSocket('ws://192.168.29.72:8000/ws/chat-list/?user=VXNlcjo4Mg==');
chatSocket.onopen = function(e){
chatSocket.send(JSON.stringify({}));
console.log("open", e);
};
chatSocket.onmessage = function(e) {
const data = JSON.parse(e.data);
console.log(data)
data.data.forEach(function (item, index, arr) {
var element = document.getElementById(item.room_id);
if (typeof(element) != 'undefined' && element != null){
document.getElementById(item.room_id).innerHTML = item.last_message.message
} else {
document.getElementById('message').innerHTML += `<h1 id="${item.room_id}">${item.last_message.message}</h1>`
}
});
};
chatSocket.onerror = function(e){
console.log("error", e)
};
chatSocket.onclose = function(e){
console.log("close", e)
};
</script>
it infinite time return users' list with their last message. but when it run another socket does not connect. so how can I create a user list consumer with the last message received? and call the three sockets on the same page.
I am using twilio TURN server for webRTC peer connecting two browsers located on different sides of the world, still the connection does not open.
Log shows the local and remote descriptions are set on both sides. Audio/video tracks are also pushed and received, but the "onopen" method on either of the data channels are not firing. Below is the code extract.
create offer code
async createOffer(){
this.initiated = true
this.conn = new RTCPeerConnection(this.servers);
if (this.conn){
this.conn.ontrack = e => {
e.streams[0].getTracks().forEach(track => {
this.calleeStream?.addTrack(track);
this.logs.push('received track:' + track.label);
})
}
if (this.callerStream)
{
const s = this.callerStream;
this.callerStream.getTracks().forEach(track =>
{
this.conn?.addTrack(track,s);
this.logs.push('pushed track:' + track.label);
});
}
}
this.channel = this.conn.createDataChannel('channelX');
this.channel.onmessage = e => this.logs.push('received =>'+ e.data);
this.channel.onopen = e => {
this.logs.push('connection OPENED!!!');
this.enabletestmessage = true;
};
this.conn.onicecandidate = async e=> {
if (e.candidate===null && !this.iceCandiSent){
this.iceCandiSent = true;
this.logs.push('new ICE candidate received- reprinting SDP'+JSON.stringify(this.conn?.localDescription));
await this.dataService.createOffer(this.data.callerid,this.data.calleeid,JSON.stringify(this.conn?.localDescription));
this.logs.push('offer set in db');
this.logs.push('waiting for answer...');
}
}
const offer = await this.conn.createOffer();
await this.conn?.setLocalDescription(offer);
this.logs.push('local description (offer) set');
}
create answer code
async createAnswer(offerSDP:string){
this.initiated = true;
this.conn = new RTCPeerConnection(this.servers);
if (this.conn)
{
this.conn.ontrack = e => {
e.streams[0].getTracks().forEach(track => {
this.callerStream?.addTrack(track);
this.logs.push('received track:' + track.label);
})
}
if (this.calleeStream)
{
const s = this.calleeStream;
this.calleeStream.getTracks().forEach(track =>
{
this.conn?.addTrack(track,s);
this.logs.push('pushed track:' + track.label);
});
}
}
await this.conn.setRemoteDescription(JSON.parse(offerSDP));
this.logs.push('remote description (offer) set');
this.conn.onicecandidate = async e => {
if (e.candidate === null && !this.iceCandiSent){
this.iceCandiSent=true;
this.logs.push('new ICE candidate received- reprinting SDP'+JSON.stringify(this.conn?.localDescription));
await this.dataService.updateAnswer(this.data.callerid,this.data.calleeid,JSON.stringify(this.conn?.localDescription));
this.logs.push('answer set in db');
}
}
this.conn.ondatachannel = e => {
this.channel = e.channel;
this.channel.onmessage = e => this.logs.push('received =>'+ e.data);
this.channel.onopen = e => {
this.logs.push('connection RECEIVED!!!');
this.enabletestmessage = true;
};
}
const answer = await this.conn.createAnswer();
await this.conn.setLocalDescription(answer);
this.logs.push('local description (answer) set');
}
server side code for retrieving ice servers from Twillio
const twilio = require('twilio');
const client = twilio(<MY ACCOUNT SID>,<MY AUTH TOKEN>);
const result = await client.tokens.create();
return result.iceServers; //this is set to this.servers in the code above
Everything works when I run on two browser windows in my local machine. However even afer implementing TURN they dont work between browsers in Nepal and USA. The onopen event handlers on data channel does notfire even though local and remote descriptions are set on both sides. What am I missing ?
NOTE: signalling is done inside the onicecandidate event handler ( the line that calls dataService createOffer/updateAnswer methods)
So I'm messing around with creating a discord bot that repeatedly pings a user until they respond/say anything in the chat (annoying, right?). The amount of times to ping the user and the time between each ping can also be adjusted if necessary. However, I can't seem to find a way to detect if the pinged user actually says something in the chat, and a way to stop the loop.
The actual pinging part of the code is in this for loop:
const ping = async () => {
for(var i = 1; i <= pingAmount; i++){
//the wait() command
await new Promise(r => setTimeout(r, pingTime * 1000));
//the actual ping
message.channel.send(`hey <#${userID}> let\'s play minecraft`);
}
//sends a message once pinging is finished
message.channel.send("Pinging Complete.");
};
I've tried nesting the following code inside that loop, but I get no results.
client.on('message', message =>{
if(message.author == taggedUser) {
message.channel.send('User has replied. Stopping pings.')
return;
}
});
Any help is appreciated!
full code below:
module.exports = {
name: 'Ping',
description: "Pings specified user until they appear",
execute(message, args, Discord){
//initialize variables
const client = new Discord.Client();
const taggedUser = message.mentions.users.first();
const userID = message.mentions.users.first().id;
//splits the command
const slicedString = message.content.split(' ');
//grabs specific numbers from command as input
const pingAmount = slicedString.slice(4,5);
const pingTime = slicedString.slice(5);
//display confirmation info in chat
message.channel.send(`So, ${message.author.username}, you want to annoy ${taggedUser.username}? Alright then lol`);
message.channel.send(`btw ${taggedUser.username}\'s user ID is ${userID} lmao`);
message.channel.send(`amount of times to ping: ${pingAmount}`);
message.channel.send(`time between pings: ${pingTime} seconds`);
//checks to make sure pingTime isnt too short
if(pingTime < 5){
if(pingTime == 1){
message.channel.send(`1 second is too short!`);
return;
} else {
message.channel.send(`${pingTime} seconds is too short!`);
return;
}
}
//timer and loop using pingAmount and pingTime as inputs
const ping = async () => {
for(var i = 1; i <= pingAmount; i++){
//the wait() command
await new Promise(r => setTimeout(r, pingTime * 1000));
//the actual ping
message.channel.send(`hey <#${userID}> let\'s play minecraft`);
const pingedUsers = [taggedUser];
// doodle message
const msg = {author: {id:1}};
// message event
const onMessage = (message) => {
if (pingedUsers.indexOf(message.author.id) != -1) {
console.log("user replied");
}
}
onMessage(msg); // nothing
pingedUsers.push(msg.author.id); // push the author id
onMessage(msg); // he replied!
}
//sends a message once pinging is finished
message.channel.send("Pinging Complete.");
};
//runs the ping function
ping();
}
}
You should be comparing the author's snowflake (id) in this case.
You can put the pinged users in a list and see if the message author is in that list.
const pingedUsers = [];
// doodle message
const msg = {author: {id:1}};
// message event
const onMessage = (message) => {
if (pingedUsers.indexOf(message.author.id) != -1) {
console.log("user replied");
}
}
onMessage(msg); // nothing
pingedUsers.push(msg.author.id); // push the author id
onMessage(msg); // he replied!
I have a server that is written in Java running Rhino for js,
and I decide to rewrite this server into nodejs...
And I need to get data from DB synchronous like :
function executeRowsetParam(sql, p){
return DB.raw(sql,p)// returns object
}
so I can use it like :
var userName = executeRowsetParam('SELECT user_name FROM users where user_id = ?', ["123"]);
if(userName.getRow(0).getValue("user_name ") == "admin"){
//do sth
}
is just an a simple example sometimes I need to select from database data that i have to use in like 1000 lines of code so code like :
executeRowsetParam('SELECT user_name FROM users where user_id = ?', ["123"]).then((r)=>{
if(r.getRow(0).getValue("user_name ") == "admin"){
//do sth
}
})
won't work so well ...
I have code like this too:
IfExists(SQL,p){
if(DB.raw("selecect top 1 1 from" + sql,p) == 1){
return true
}else{return false}
and rewriting it into :
DB.raw("selecect top 1 1 from" + sql,p).then((r)=>{
if(r == 1){//do sth}else{//do else}
})
won't work for me
so is there some npm package that I can use to make selects from db synchronuch that would make my day.
is code like this would be okey ?
var Start = async () => {
var Server = require("./core/server/Server");
console.log('hi!');
console.dir("there is nothing to look at at the momment");
var db = require("./core/db/DB");
global.DB = new db()
function foo() {
return DB.executeSQL("asdasd", [123, 123])
}
console.dir(await foo());
console.dir('asd');
global.DEBUG = true;
global.NEW_GUID = require('uuid/v4');
var server = new Server()
server.start();
}
Start();
is that enough to let me use await in every single instance inside the server or have I make every single function async if I would use await?
Found and tested deasync works great!
var deasync = require('deasync');
module.exports = function DB(){
var knex = require('knex')(require("./../../config").DB);
this.executeRowsetParam = (sql, param) => {
let done = false;
knex.raw(sql, param).then((r => {
done = r;
})).catch((e => {
throw "Error in query "+ e
}))
deasync.loopWhile(function () { return !done; });
return done
}
}
I have created a publisher with the below code example.
var amqp = require('amqplib/callback_api');
amqp.connect('amqp://localhost', function(err, conn) {
conn.createChannel(function(err, ch) {
var args = process.argv.slice(2);
var routingkey = (args.length > 0) ? args[0] : 'anonymous.info';
var keys = routingkey.split(".")
var exchange = keys[0];
queueName = keys[1];
var msg = args.slice(1).join(' ') || 'Hello World!';
console.log("Exchange---------------------" , exchange);
console.log("routingkey-------------------", routingkey);
console.log("queueName-------------------" , queueName);
queueName = exchange;
ch.assertExchange(exchange, 'topic', {durable: false});
ch.assertQueue(queueName, {exclusive: false, durable: false}, false);
ch.bindQueue(queueName, exchange, routingkey);
ch.publish(exchange, routingkey, new Buffer(msg));
console.log(" [x] Sent %s: '%s'", routingkey, msg);
});
setTimeout(function() { conn.close(); process.exit(0) }, 500);
});
Based on the above publisher:
exchange : notification :::
queueName : notification :::
routingkey : ['notification.addworker' , 'notification.getworker',.....]
So, that i am able to publish messages to a unique queue Name using binding with multiple routing keys.
Here my problem, I am unable to consume messages based on routing key because the messages are bind to queue while publish itself.
Suggest if i did wrong the above code.
ConsumerCode:
var args = process.argv.slice(2);
amqp.connect('amqp://localhost', function(err, conn) {
conn.createChannel(function(err, ch) {
var ex = 'notification';
var args = process.argv.slice(2);
var routingkey = (args.length > 0) ? args[0] : 'anonymous.info';
var keys = routingkey.split(".")
var exchange = keys[0];
var queueName = exchange;
ch.assertExchange(exchange, 'direct', {durable: false});
ch.assertQueue(exchange, {exclusive: false, durable:false}, function(err, q) {
console.log(' [*] Waiting for logs. To exit press CTRL+C');
ch.bindQueue(queueName, ex, routingkey);
console.log("Exchange---------------------" , exchange);
console.log("routingkey-------------------", routingkey);
console.log("queueName-------------------" , queueName);
ch.consume(queueName, function(msg) {
console.log(" [x] %s:'%s'", msg.fields.routingKey, msg.content.toString());
}, {noAck: true});
});
});
});
Consumer is not able to filter the messages based on routing key.
Looking for suggestion where i make it wrong. I am insisted to use topic.
Thanks for patience.
I know this is an old post, but in case you're interested in my 2 cents... what I do is in the publish command I add some reply information:
ch.publish(exchange, send_key, new Buffer(message), { correlationId: corrId, replyTo: q.queue });
Where the q.queue comes from the assetQueue callback, and you can specify the reply queue.
This can then be consumed...
ch.consume(q.queue, function(msg) {...}
Not sure this answers your question exactly, I haven't yet managed to eliminate the queue name yet, I'm still a beginner myself.