I'm having an issue with an ActionCable and Turbolinks. I've set up a chat app similar to the example chat app shared by DHH.
In order to have multiple chat rooms and pass a chat room ID to the ActionCable subscription initializer, I have something like this:
$(document).on("turbolinks:load",function(){
var pod_slug = $("#pod_slug_value").val();
App.pods = App.cable.subscriptions.create(
{ channel: 'PodsChannel', pod_slug: pod_slug },
{
received: function(data) {
if ( $(".chat-stream").length ){
$(data.message).appendTo($(".chat-stream"));
$(".chat-stream").scrollTop($(".chat-stream")[0].scrollHeight);
}
},
speak: function(message, pod_slug) {
return this.perform('speak',{
message: message,
pod_slug: pod_slug
});
}
});
// captures text input from input field
captureMessage();
});
However, when I click around the app and come back to the page, Turbolinks seems to be binding the subscription handler multiple times and now when I submit a message, I get duplicate messages in the chat stream.
I've tried slicing this problems in every which way. I don't have the problem when I don't preface the subscription handler with:
$(document).on("turbolinks:load",function(){...
But then I am unable to get the chat room id (pod_slug), because the document DOM doesn't load before the javascript executes.
It seems like this problem would have a simple solution as both ActionCable and Turbolinks are heavily supported by Basecamp. Many of the demo chat tutorials suggest setting up chat in exactly this way. Am I missing something here?
I have had the same issue and fixed it in this way
if (!App.pods) {
App.pods = App.cable.subscriptions.create(
{ channel: 'PodsChannel', pod_slug: pod_slug },
{ ... /* and so on */ }
}
Every time you reload page, 'turbolinks:load' event works, so you have to check whether App.pods object has been already created.
Hope this will help you.
You can use App.cable.subscriptions.remove to remove a subscription returned by App.cable.subscriptions.create. So maybe check if App.pods is set, and if so remove it before you subscribe again.
I like your approach, MaruniakS. Digging into the consumer object, it looks like it has a test to see if it's disconnected. Changing just the first line, we could also do:
if (!App.pods || App.pods.consumer.connection.disconnected) {
App.pods = App.cable.subscriptions.create(
{ channel: 'PodsChannel', pod_slug: pod_slug },
{ ... /* and so on */ }
}
Related
I'm building a chat application. All works fine; however, when I click send, the message isn't displayed until I manually reload the page.
I think I'm missing something after the subscribe.
onSubmit() {
this.chatService
.sendMessage(
'https://db_url/message.json',
{
message: this.chatForm.value.message,
username: this.authService.user.displayName,
}
)
.subscribe((data) => {
// ...
console.log(data);
});
this.chatForm.reset();
}
Could someone point out what I might be missing or doing wrong in my code? Thank you.
You can try storing the subscribed data in a messages array like
messages: Array<Message> = []; and then subscribe to it on the onInit() method, and display the messages maybe with <div *ngFor="let message of messages | slice: ind:messages.length"> (or just display it on the console, whatever you want).
It may also be because of subscribing to the sendMessages function instead of the getMessages? (without reading the rest of the code, I can't tell exactly)
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!
I have written firebase cloud function to trigger on update record. sometimes I am not getting the same record which is updating. I am adding my code below.Please check attached image also.
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}/userResponse').onUpdate(event => {
return admin.database().ref(`/Notification/${event.params.userId}/${event.params.notificationId}`).once('value').then(function (snapshot) {
var notification = snapshot.val();
if (!notification) {
console.error("Notification not found on notification update");
return;
};
I can also get Notification object from the parent but I want to know issue best approach and the problem with this code.
this is error log
this is database structure
This is my 1st post here please let me know if need more information.
Thanks
You don't have to call once within the Function since it is already returning the data at the location you are listening to, just listen to the parent node.
So you should do like:
exports.onNotificationUpdate = functions.database.ref('/Notification/{userId}/{notificationId}').onUpdate(event => {
const notification = event.data.val();
if (notification === null) {
console.error("Notification not found on notification update");
return null;
//actually this would only be called in case of deletion of the Notification
} else {
//do something with the notification data: send Android notification, send mail, write in another node of the database, etc.
//BUT return a Promise
//notification const declared above is a JavaScript object containing what is under this node (i.e. a similar structure than your database structure as shown in the image within your post.)
}
});
I would suggest that you have a look at these three videos from the Firebase team:
https://www.youtube.com/watch?v=7IkUgCLr5oA&t=517s
https://www.youtube.com/watch?v=652XeeKNHSk&t=27s
https://www.youtube.com/watch?v=d9GrysWH1Lc
Also, note that Cloud Functions have been updated and the first line of your code shall be written differently if you are using a CF version above 1.0.0. See https://firebase.google.com/docs/functions/beta-v1-diff
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've made a working chat with meteor and mongodb, but I want to play a sound or something when there is a new message. However, I don't know how to check if data is updated. I could check if there is a new message by counting the messages before and after the update, but I just don't know how to check for an update.
So my question here is: How do I check for an update in the data?
I have a website that needs to pop up a toastr alert whenever a new message arrives. My collection is called "Alerts". This is what I do:
Alerts.find({notified: false}).observeChanges({
added: function(id, doc) {
Alerts.update(id, {
$set: {
notified: true
}
});
toastr.info(foo, bar);
}
});
Whenever a new alert is created whose field "notified" is false, a toastr alert will be created and that alert will be marked as "notified: true".
Alternatively you could do the same thing but create a separate collection of "notifications" that when observed, are removed from the collection as well that are a distinct collection from your chat messages collection.
You could create a tailing cursor on the oplog collection, so you get a new document whenever something (anything!) in the database changes. But that's not really an elegant solution, because that handler would need to process a lot of junk.
It might be better to have the routine which writes the message to the database also inform any currently online users. There is really no good reason to go the detour over the database.