How can i pass DOM Elements server-side via socketio to display new message content.
// Client
// Pass DOM element as string
function elemToString(elem){
return elem.outerHTML
}
window.addEventListener("keyup", sendMessage, false);
function sendMessage(key) {
if (key.keyCode === 13) {
if (chatbox.value !== "") {
const child = document.createElement("div");
child.textContent = `${chatbox.value}`
child.className = "messages"
chat.appendChild(child);
socket.emit('chatMsg', elemToString(child))
chatbox.value = "";
chatbox.style.display = "none";
setTimeout(()=>{
child.remove()
},4000)
}
else chatbox.style.display = "none";
}
}
// User Class
class User {
constructor(name, x, y) {
this.alive = false
this.name = name
this.cells = []
this.room = 'none'
this.message = []
}
update(cells) {
this.cells = cells
}
}
module.exports = User
// Server
socket.on('chatMsg', elem => {
user.message.push(elem)
})
I have a hotkey which sends new divs which display message content. I want to pass the data server-side via socket.io so all users can see new message content instead of just the individual client.
The first question I would ask is whether you really need to pass around HTML or DOM objects? Based on the above snippet, it appears that sending the messages as strings (without any HTML markup, just the message body) is by far the easiest way for both the server and the chat client(s). As to sending the message to all users in the chat room, this feature is already built into socket.io (see https://socket.io/docs/rooms-and-namespaces/) and unless you need to implement custom logging or filtering, you could just use it out of the box
There are several ways you could go about building this chat server:
Simple / POC approach
If you just want to have a quick prototype and see if it does what you want it to do, and you are not too concerned with auditing, logging, filtering etc., then:
When the client connects, it "joins" a socket.io "room" (the name of the room could match the room on the client or you may choose to format/hash it etc.)
All chat room clients send events to that same room and listen to the events emitted by that room. The socket.io backend takes care of re-broadcasting the events, so you would not need to do anything
To send message to the room, the "sender" client would broadcast a well known event, something like { message: '<<message text>>' }. All clients listen to events in the room, and once a new event arrives they create a new HTML element (say, a <div>) with that message as content and append it to the chat window
This should cover the basic chat room scenario
A bit more involved chat server
In the real-world implementations, we typically want to have a bit more control over what events can and cannot be broadcasted (for example, we want to have the ability to silence a chatter or filter profanity/abusive language etc.) In this case, we cannot simply use blind broadcast, so typically the client would use two socket.io channels - a "room" for incoming messages and a direct channel for outgoing.
In this scenario, the server acts as a bridge - it listens to the incoming messages, does all the necessary filtering, preprocessing, sanitizes the messages to prevent injections etc., and then broadcasts the resulting message in the room. In this scenario:
All chat clients subscribe to the same "incoming message" room, just like in the previous scenario
When the chatter needs to send a message (say, they hit Enter), the client socket emits an event to the server and tells it which room this message is intended for (e.g. { incoming_message: 'xyz', user: 'username', room: 'room_21' }
The server receives the message and processes it (for example, checks the username against the list of banned users), then broadcasts the processed message in the room specified in the original message. The other chat participants receive the message and render it
In this scenario, you can keep the history of every message sent by a particular user (much like you do in your code snippet)
This link (https://socket.io/docs/#Sending-and-getting-data-acknowledgements) has a few examples on sending and broadcasting arbitrary JSON messages and also shows working with channels etc.
Hope that helps and good luck with your chat server!
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'm wondering if there is an easy way in lance-gg to send player specific data to each player only, rather than emitting all data to all players.
I wish to have create a poker game, and don't want the data around what each player is holding broadcast to all players and instead only have each player receive information regarding their own cards.
Is this easily achievable between the current GameEngine and ServerEngine?
Over the course of the game the following steps will need to occur:
"deal" cards to each player (emit cards to each player individually, and also broadcast an event to indicate the other clients should animate the player being dealt cards receiving them)
store and hold the dealt cards outside the other data to be updated
retrieve player cards should the player be disconnected and then reconnect during the hand
"reveal" cards (broadcast any revealed cards like the flop and the showdown cards to all players)
Player's cards also need to be stored on the server but not re-broadcast with each step.
There is a low-level networking layer which can be used for client-server communication in Lance.
For example, if the server wants to send the event shakeItUp with data shakeData = { ... } to all clients, the game's serverEngine would call:
this.io.sockets.emit('shakeItUp', shakeData);
To send events and data to specific players, the serverEngine class can do
for (let socketId of Object.keys(this.connectedPlayers)) {
let player = this.connectedPlayers[socketId];
let playerId = player.socket.playerId;
let message = `hello player ${playerId}`;
this.connectedPlayers[socketId].socket.emit('secret', message);
}
The client listens to messages from the ClientEngine sub-class, after the connection is established:
// extend ClientEngine connect to add own events
connect() {
return super.connect().then(() => {
this.socket.on('secret', (e) => {
console.log(`my secret: ${e}`);
});
});
}
Background
I am trying to design an interactive classroom type of environment. The room has different slides, a chat box and some other basic features.
My understanding
A sure way to update a page in real time for all users is for one person to persist a change via ajax to a database, then all the other users poll the server at a timed interval (one second) to check for changes... if there are their view gets updated.
My code
Each room has a unique URL... http://www.example.com/room/ajc73
Users slide through the clides using this code:
showCard();
function showCard() {
$('#card-' + (cardId)).show();
}
$('#nextCard').click(function() {
nextCard();
});
$('#previousCard').click(function() {
previousCard();
});
function nextCard() {
if ($('#card-' + (cardId + 1)).length != 0) { // if there is a next card
$('#card-' + (cardId)).hide(); // hide current card
cardId++; // increment the card id
$('#card-' + (cardId)).show(); // and show the next card
location.hash = cardId;
}
}
function previousCard() {
if (cardId != 1) { // if we are not at the first card
$('#card-' + (cardId)).hide(); // hide the current card
cardId--; // decrement the card id
$('#card-' + (cardId)).show(); // and show the previous card
location.hash = cardId;
}
}
My question
Am I required to persist data from user1 to the database in order for it to be called and displayed to user2 or is there a way to cut out the database part and push changes directly to the browser?
Go for websockets. that will be a better option. since its real-time and just a simpler logic will help you achieve the result.
If you are not sure that whether you will be able to use websockets(like if you are using shared hosting and your provider doesn't allow this or any other reason) you can go for various services like pusher(easier to understand) that will help to do your job but with some cost.
You could use sockets and just broadcast any input to every client.
Of course, the same can be done with ajax and a rest api but it'll be harder, i'll use pseudocode:
clients = {};
fn newclient() {
clients[client_id] = {
pool: [];
....
}
}
fn onNewMessage(newmessage) {
forEach(client, fn(c) {
c.pool.push(newmessage);
})
}
fn clientRequestNews() {
response = clients[client].pool;
clients[client].pool.length = 0;
return response;
}
the point here is, in server memory there would be a entry for each client, each of them has a pool, when a new message is sent to the server, it's pushed to every client's pool.
When a client ask's for news, the server will return the clients pool, after that, it'll clean the pool of that client.
With this you dont need persistence.
Use web sockets. Please see here
You need websockets, a datastructure server and a pub/serve model with events:
A hint
Let's say I have two users, "Matt" & "Kevin". Matt wants to message Kevin, by clicking a chat button to send Kevin a direct message a chat box boots up, he sends a message and Kevin receives it.
I generate the chat id by taking the person who sent it (Matt) and the person who received the message (Kevin) and concatenating it into an id.
var me = "Matt";
var user = "Kevin";
var uniqueChatID = me+user;
As I save the message server side (with mongoDB) the message object has a chatID of MattKevin. So now when I want to get back to that chat I can pull in all messages with the chatID of MattKevin.
This works fine, until Kevin wants to boot up a chat with Matt, then the id becomes KevinMatt. Now I am referencing a different chat, it's backwards. So If I want to pass uniqueChatID to get the messages it will pull a different set.
var me = "Kevin";
var user = "Matt";
var uniqueChatID = me+user;
So I am curious how can I set this up a bit better so that my program knows, ok Matt and Kevin have a chat, so if Matt messages Kevin it pulls in their chat or visa versa, Kevin messages Matt and it gets the same messages?
Sort them alphabetically:
var me = "Kevin";
var user = "Matt";
var uniqueChatID = [me, user].sort().join('');
That said, while this technically works, I'd recommend you do a little housekeeping - ensure they're always lowercase, and ensure on your db that you enforce unique usernames. Or, I'd even suggest giving the user a unique identifier (like a UUID) and use that instead to create the UCID:
var me = CurrentUser.uuid(); // 8cb3ebb8-30f9-11e5-a151-feff819cdc9f
var targetUser = Chat.targetUser(); // Matt: 9bc1ef9c-6719-4041-afd3-c5b87c90690d
var uniqueChatID = [me, targetUser].sort().join(',');
// 8cb3ebb8-30f9-11e5-a151-feff819cdc9f,9bc1ef9c-6719-4041-afd3-c5b87c90690d
And lastly, if your db supports relationships or connections, your best option is to separate chat table/collection for each chat and "connect" (or create a relationship) between both users and the chat. Then the next time you go and load it up, the connection will lead you to a unique chat that's connected to both users.
I think you approach is too complex. Furthermore, it looks like you want to embed the individual chat messages into the document bearing the created _id. The problem here is that there is a 16 MB size limit on BSON documents at the time of this writing. Upon reaching this limit, your users simply could not communicate any more. Increasing the size of documents may also lead to frequent document relocations, which is a very costly operation unless you use the new WiredTiger storage engine introduced in version 3.0 of MongoDB.
So we need a more scalable approach.
Here is how I would do it:
User:
{
_id: "Kevin",
email: "kevin#example.com"
/* Put further user details as you see fit*/
}
Message:
{
_id: new ObjectId(),
from: "Kevin",
/* You might want to have multi-person chats, hence the array */
to: ["Matt"],
ts: new ISODate(),
message: "Hi, Matt!"
}
Index:
db.messages.ensureIndex({from:1,to:1,ts:1})
Query for reconstructing all messages a user received:
var user = "Matt"
db.messages.find({"to": user}).sort({ts:1})
Now you can iterate over the result set and open a chat window for each "from" you find.
Query for reconstructing a defined chat
var user = "Matt"
var sender = "Kevin"
db.messages.find({"from": sender, "to":user}).sort({ts:1})
will give you all messages sent to Matt by Kevin, ordered by time. Since both queries should utilize the index, they should be pretty fast. You can use .limit(x) to query only the last x messages sent to user.
With this approach, you don't need an artificial _id, the index created allows you to do every query related to the participants efficiently and the messages can be sorted in order. Because each message is saved individually and does not change any more, you can store an almost indefinite number of messages and bypass the document relocation problem.
I'm developing a text based adventure game with Meteor and I'm running into an issue with how to handle certain elements. Namely, how to emit data from the Server to the Client without any input from the Client.
The idea is that when a player is engaged in combat with a monster, the combat damage and updating the Player and Monster objects will be occurring in a loop on the server. When the player takes damage it should accordingly update the client UI. Is something like this possible with Publish / Subscribe?
I basically want something that sits and listens for events from the server to update the combat log accordingly.
In pseudo-code, this is something along the lines of what I'm looking for:
// Client Side:
Meteor.call("runCommand", "attack monster");
// Server Side
Meteor.methods({
runCommand: function(input) {
// Take input, run the loop to begin combat,
// whenever the user takes damage update the
// client UI and output a line saying how much
// damage the player just received and by who
}
});
I understand that you can publish a collection to the client, but that's not really as specific of a function I'm looking for, I don't want to publish the entire Player object to the client, I just want to tell the client to write a line to a textbox saying something like "You were hit for 12 damage by a monster!".
I was hoping there was a function similar to SocketIO where I could, if I wanted to, just emit an event to the client telling it to update the UI. I think I can use SocketIO for this if I need to, but people seemed to be adamant that something like this was doable with Meteor entirely without SocketIO, I just don't really understand how.
The only outs I see to this scenario are: writing all of the game logic client-side which feels like a bad idea, writing all of the combat logs to a collection which seems extremely excessive (but maybe it's not?), or using some sort of SocketIO type-tool to just emit messages to the client to tell it to write a new line to the text box.
Using Meteor, create a combat log collection seem to be the simplest option you have.
You can only listen on added event and then clear the collection when the combat is over.
It should be something like this :
var cursor = Combat_Log.find();
var handleCombatLog = cursor.observe({
added: function (tmp)
{
// do your stuff
}
});
I ask a similar question here, hope this will help ^^
Here's how I did it without a collection. I think you are right to be concerned about creating one. That would not be a good idea. First install Streamy.
https://atmospherejs.com/yuukan/streamy
Then on the server
//find active sockets for the user by id
var sockets = Streamy.socketsForUsers(USER_ID_HERE)._sockets
if (!Array.isArray(sockets) || !sockets.length) {
//user is not logged in
} else {
//send event to all open browser windows for the user
sockets.forEach((socket) => {
Streamy.emit('ddpEvent', { yourKey:yourValue }, socket);
})
}
Then in the client, respond to it like this:
Streamy.on('ddpEvent', function(data) {
console.log("data is ", data); //prints out {yourKey:yourValue}
});