I have the following code:
openTokInit() {
this.session = OT.initSession(this.tokboxApiKey, this.sessionId);
const self = this;
this.session.on('connectionCreated', function(event) {
self.connectionCount++;
});
if (this.connectionCount < 2) {
this.session.connect(this.token, err => {
if (err) {
reject(err);
} else {
resolve(this.session);
}
});
}
The problem is that when the if statement runs, the connectionCount is always 0, because the 'connectionCreated' event is fired a few seconds later. I'm not clear on how to appropriately wait for all the connectionCreated events to fire before connecting a new session.
Adam here from the OpenTok team.
You won't get the "connectionCreated" Event until after you connect. So you will need to instead disconnect if you have connected and you are the 3rd (or more) participant. I would use the connection.creationTime to see who got there first to avoid 2 people connecting at about the same time and both of them disconnecting. Something like this should do the trick:
session = OT.initSession(apiKey, sessionId);
let connectionsBeforeUs = 0;
session.on('connectionCreated', (event) => {
if (event.connection.connectionId !== session.connection.connectionId &&
event.connection.creationTime < session.connection.creationTime) {
// There is a new connection and they got here before us
connectionsBeforeUs += 1;
if (connectionsBeforeUs >= 2) {
// We should leave there are 2 or more people already here before us
alert('disconnecting this room is already full');
session.disconnect();
}
}
});
session.connect(token);
Here is a jsbin that demonstrates it working.
I'm not sure how your whole application works but another option might be to do this at the server side and only hand out 2 tokens for people to connect. So when they try to get a 3rd token you block them at that point. Rather than letting them connect to the session and then disconnect themselves. The advantage of this approach is that you can notice quicker and give the user feedback sooner. Also a malicious user can't just hack the javascript and connect anyway. You could also use the session monitoring API to track users connecting from your server.
Another option again is to use the forceDisconnect() function to kick people out of a room if there are already 2 people in there. So it's the responsibility of the people already in the room to kick out the third participant rather than the 3rd participant noticing that there are already people in there and leaving themselves. This will mean that the malicious person couldn't hack the JavaScript code in their browser and join other people's rooms.
Without knowing your whole application though it's hard to know what the best option is for you.
I hope this helps!
Related
I'm trying to create a basic TCG style game with Node/Vue/Socket.io and can't seem to figure out how to emit to both clients when a "ready" count = 2 but with different data, I'll explain a little below...
The sequence of events is as such:
player connects -> server sends player a "deck" -> player clicks ready to start and also sends back their first 'card'.. Then the server should send out to each player the other players first card. (Note my emit events don't have the correct titles atm - they were already written up on the front end so just kept them the same)
On connection I've pushed to an array called sockets, that I was using for testing. Then in the "ready" event I created an array called "firstCards" that I'm pushing the socket event data to then adding a .socket property to it (to signify who's who), then incrementing ready.
I've had a little bit of a play around with a few different methods but I can only seem to get the last card sent to both clients as opposed to each client getting the other clients first.. I also tried just putting the "if" statement outside of the socket event (as you will see below with the comment on the brackets/curly braces) which doesn't seem to work either.
I haven't tried this kind of asymmetric data transfer before and unsure if that is even the correct term... or whether this is even the correct way to do so, any help would be much appreciated!
This is the code I'm using so far:
socket.on('ready-up', function (card)
{
console.log(`Player ${socket.id} is ready`);
ready++;
console.log(ready);
card.socket = socket.id;
firstCards.push(card);
console.log(firstCards);
});
if (ready == 2)
{
for (let i = 0; i < sockets.length; i++)
{
io.to(sockets[i]).emit('p2hand', "Both players ready");
let opp = sockets.find(element => element != socket.id);
console.log(`Socket ID is: ${socket.id}`);
console.log(`Opp ID is: ${opp}`);
let card = firstCards.find(element => element.socket == opp)
console.log(card);
io.to(opp).emit('reveal',
{
'name': card.name,
'hp': card.hp,
'mp': card.mp,
'skills': card.skills,
'icon': card.icon
});
// io.to(opp).emit('reveal', card);
ready = 0;
}
}
// });
So I figured this one out for anyone who may end up wanting to do what I was trying to do....
I decided that upon connection, both clients join a room called "game1".
The server will then emit "firstCards" to that room.
After that it was just a case of making sure the player-client know which was the opponents card... Now I could have used the .name property for this, but I decided to add an "id" property using the socket.id instead due to the possibility of the same card being drawn for both players.
I'm thinking that all server-client interactions will now have to carry this property for any other cards in the game such as items, spells, etc
I developed an application for the school I work for that create a bridge between our registration system and Google Calendar. In short, it populates multiple calendars (teachers, students, classrooms, and a calendar that contain all courses) based on data from the registration system, it allows teacher to register student attendance and homework for each class and it does a bunch of other stuff.
The application also allows a teacher to trigger an update of the student list when he notices a registered student is not entered as an attendant in the calendar events.
In the code:
I make a call to the registration system to get the updated list of students for the course.
I make a call to Google Calendar to get the recurrences of the events that represent the course in the calendar.
I make a call to Google Calendar to batch patch the "attendees" array of every recurrence
this.updateEvent = function(calendarId, eventId, resource)
{
return $window.gapi.client.calendar.events.patch({
calendarId: calendarId,
eventId: eventId,
resource: resource
});
};
this.batchUpdateEvents = function(calendarId, eventList)
{
var counter = 0;
var batchPromises = [];
for(var i=0; i < eventList.length; i++)
{
if(counter === 0)
{
var batch = $window.gapi.client.newBatch();
}
var event = eventList[i];
batch.add(this.updateEvent(calendarId, event.id, event.resource), {id: event.id});
counter++;
if(counter === 50 || i === eventList.length-1)
{
counter = 0;
batchPromises.push(batch.then());
}
}
return $q.all(batchPromises).then(
function(response){
var updateResults = {};
response.forEach(function(batchResponse){
updateResults = Object.assign(updateResults, batchResponse.result);
});
return updateResults;
});
};
It works fine in most cases but regularly all the calendar events associated with the course are deleted. I can't figure out how to reproduce the bug and since everything is coded in javascript I can't log the errors.
In this process, this is the only 2 times my application interacts with Google Calendar. I really don't see how a get request could delete anything so I assume the batch of patch requests is the problem. Although the only thing I include in the patch is the attendee array so its actually hard to mess it up to that point.
Has anybody got an idea of the direction I should look into? I am a bit at loss here.
It's possible that batchUpdateEvents could be receiving a valid calendarId, but an eventList with blank values. If that's the case checking for a valid value in eventList[0].resource before proceeding to the for loop would prevent blank event values from being saved (which might be causing Google to delete the event). If this script should only add events and never delete them it could also be beneficial to check event.resource before adding it to the batch.
I have an application where users can follow other users. I want to have a real-time update system, display the total count of followers a user has.
I've just started playing around with Firebase and Pusher, but I don't understand one thing - will each user have their own 'channel'? How would I handle such thing as a follower counter update?
I followed the tutorial and saw that push method can create lists, so one solution I can see is having a list of all the users, each user being an object something like this:
{ username: 'john_doe', follower_count: 6 }
But I don't understand how would I actually track it on the front end? Should I have a filter, something like this?
var user = users.filter(function(user) { return user.username === 'john_doe'; });
// code to display user.follower_count
Or is there a different way that I'm missing?
Most of Firebase logic is based on listeners, you can add a listener to events happening in your collections and when those events happen, you do something.
One way to go about this would be:
var myFollowerListRef = new Firebase(PATH_TO_YOUR_COLLECTION);
myFollowerListRef.on("value", function(snapshot) {
console.log(snapshot.length);
});
This way, every time your follower collection changes, the asynchronous function fires and you can do what you want with the fresh data.
For more information:
https://www.firebase.com/docs/web/guide/retrieving-data.html
Hope this helps, I'm still a beginner in Firebase.
#Th0rndike's approach is the simplest and works fine for relatively short lists. For longer lists, consider using a transaction. From the Firebase documentation on saving transactional data:
var upvotesRef = new Firebase('https://docs-examples.firebaseio.com/android/saving-data/fireblog/posts/-JRHTHaIs-jNPLXOQivY/upvotes');
upvotesRef.transaction(function (current_value) {
return (current_value || 0) + 1;
});
But I recommend that you read the entire Firebase guide. It contains solutions for a lot of common use-cases.
I am trying to remove an item from $firebaseArray (boxes).
The remove funcion:
function remove(boxJson) {
return boxes.$remove(boxJson);
}
It works, however it is immediately added back:
This is the method that brings the array:
function getBoxes(screenIndex) {
var boxesRef = screens
.child("s-" + screenIndex)
.child("boxes");
return $firebaseArray(boxesRef);
}
I thought perhaps I'm holding multiple references to the firebaseArray and when one deletes, the other adds, but then I thought firebase should handle it, no?
Anyway I'm lost on this, any idea?
UPDATE
When I hack it and delete twice (with a timeout) it seems to work:
function removeForce(screenIndex, boxId) {
setTimeout(function () {
API.removeBox(screenIndex, boxId);
}, 1000);
return API.removeBox(screenIndex, boxId);
}
and the API.removeBox:
function removeBox(screenIndex, boxId) {
var boxRef = screens
.child("s-" + screenIndex)
.child("boxes")
.child(boxId);
return boxRef.remove();
}
When you remove something from firebase it is asynchronous. Per the docs the proper way to remove an item is from firebase, using AngularFire is:
var obj = $firebaseObject(ref);
obj.$remove().then(function(ref) {
// data has been deleted locally and in the database
}, function(error) {
console.log("Error:", error);
});
$remove() ... Removes the entire object locally and from the database. This method returns a promise that will be fulfilled when the data has been removed from the server. The promise will be resolved with a Firebase reference for the exterminated record.
Link to docs: https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject-remove
The most likely cause is that you have a security rules that disallows the deletion.
When you call boxes.$remove Firebase immediately fires the child_removed event locally, to ensure the UI is updated quickly. It then sends the command to the Firebase servers to check it and update the database.
On the server there is a security rule that disallows this deletion. The servers send a "it failed" response back to the client, which then raises a child_added event to fix the UI.
Appearantly I was saving the items again after deleting them. Clearly my mistake:
function removeSelected(boxes) {
var selectedBoxes = Selector.getSelectedBoxes(boxes);
angular.forEach(selectedBoxes, function (box) {
BoxManager.remove(box);
});
Selector.clearSelection(boxes, true);
}
In the clearSelection method I was updating a field on the boxes and saved them again.
Besides the obvious mistake this is a lesson for me on how to work with Firebase. If some part of the system keeps a copy of your deleted item, saving it won't produce a bug but revive the deleted item.
For those, who have the similar issue, but didn't solve it yet.
There are two methods for listening events: .on() and .once(). In my case that was the cause of a problem.
I was working on a migration procedure, that should run once
writeRef
.orderByChild('text_hash')
.equalTo(addItem.text_hash)
.on('value', val => { // <--
if (!val.exists()) {
writeRef.push(addItem)
}
});
So the problem was exactly because of .on method. It fires each time after a data manipulation from FB's console.
Changing to .once solved that.
I have publishing setup along with sessions to send out messages to the right rooms.
I'm currently having issues as to how do I go about limiting returned messages so if a room has, say, 200 messages in it and another one is posted, the oldest one is deleted.
//how messages are created
Meteor.methods({
newMessage: function (message) {
message.user = Meteor.userId();
Messages.insert(message);
}
});
//how messages are published
Meteor.publish('messages', function (channel) {
return Messages.find({channel: channel});
});
//how chatrooms are published
Meteor.publish('channels', function () {
return Channels.find();
});
Problem is, normally I would do this by putting this in the publications {sort:{limit:15}}
However, that doesn't work in this case and results in ALL of the messages being limited.
They need to be sorted by room, or, well, per session:key.
Is there a simple way of going about this? Or would I have to make a method on the serverside to run .forEach channel?
There's no decent way of publishing the top 15 posts from each room in a single cursor. If the number of rooms is small it might make sense to publish an array of cursors instead, each cursor in the array corresponding to a single room.
Meteor.publish('messages', function (channel) {
return Messages.find({channel: channel}, {limit: 15});
});
I have no idea how I missed this but apparently I can just do it this way.
Huh.
(channel:channel corresponds to currently set session's name)