How can I detect what room a user disconnected from? - flask-socketio

I have an application that I need to pass the room the current user has just disconnected from to the server. The user can be in multiple rooms via multiple tabs.
I can detect that the user has left this way but it does not seem to be able to have data passed with it:
#socketio.on('disconnect')
def on_disconnect():
print(session['id'])
print("user left " )
In my client end I have tried this:
socket.on('disconnect', function () {
socket.emit('user_disconnect', {"channel": "{{symbol}}"});
});
This emit never goes through to the server though. I am guessing the window closes or changes before this can get called? How can I pass data to the server on the disconnect event?

I believe I have figured out a solution.
Utilizing request.sid, I can store the room name in a list along with this sid on the server-side join event.
#socketio.on('join')
def on_join(data):
active_rooms.append(json.dumps({'room':data['channel'],'socket_id':str(request.sid)}))
When the disconnect event is triggered I can view this same sid and use it as a lookup in the list. The sid appears to be unique for each socket connection so multiple tabs will result in a new sid.

Related

Granular domain events

Initially we were using Domain events to handle communications with external systems. For instance, every time a user was updating his phone number OR his name we raise a PhoneNumberUpdated AND a NameUpdated event. These are then caught by handlers processed and sent to other systems.
public void SetName(Name name)
{
if (Name == name) return;
(...)
RaiseEvent(new NameUpdated(Id, name));
}
public void SetPhoneNumber(PhoneNumber number, PhoneNumberType type)
{
RaiseEvent(new PhoneNumberUpdated());
}
It works great as long as we do not need to "aggregate" events. For example, we got a new requirement asking us to to send one single email whenever a user updates his name and/or his phone number. With the current structure, our handlers would be notified multiples times (one time for each event raised) and this would result in multiple emails sent.
Making our events more generic don't seem to be a good solution. But then how would we aggregate several events raised within one transaction?
Thx
Seb
I believe your new requirement is a separate concern from your actual domain. Your domain generates events that describe what has happened. User notification, on the other hand, is a projection of that stream of events into email form. Just like you would keep your read model requirements separate from your domain, you should keep this separate as well.
A simple solution would be to capture the events you care about into a table and then once a day, on a schedule, and send one email per aggregate.

SignalR subscribe to event with param

I need to subscribe to server events from client, so syntax is
Hub.client.[my event here]= function
It's working, but what if I need to subscribe with params, i.e. user need to see only unread messages or a list of messages with a criteria. So I want the same event subscription with modifiers. Like :
Hub.subscribe.messages({read:true}) = function ....
At server side I want to push updates only for clients that subscribed to this type of criteria and specific parameters.
I know I can make groups of clients but that seems to be overhead and not best practice.
I've used the client syntax:
Hub.client["MethodName_" + dynamicParam] = function
And server side syntax:
HubContext.Clients.All.Invoke(string.Format("MethodName_{0}", dynamicParam), data);
That way I can register to dynamic events based on the client selection of "dynamicParam"

Loading datastore based on boolean flag

I want to make a purchase order manager, where a queue is created from a database and then assembled into an accordion. Then, the user can look at requests, and then check the request when it is done. The task will then move to a "completed purchases" list.
I've been using a "notPurchased" datastore with the following server script:
query.filters.purchased._equals = false;
return query.run();
And then when the "submit" button is pressed, I call datastore.load();. However, this doesn't seem to refresh the purchase queue immediately. I have to completely refresh the page in order to see purchase request moved to 'completed'. How do I make this change instantaneous?
I figured out a solution that reduced any lag. Instead of filtering the database with a query, I bound the 'visibility' property to the proper boolean flag. Now items move instantly!

meteor: how to stop asynchronous call?

Is it possible to stop (kill) asynchronous Call?
In my app I have at client side sth like:
Meteor.call('doCalculation', function(err, result) {
//do sth with result
});
'doCalculation' may take long time (this is ok) I dont want user to start new call when he/she has already one running call, I want to allow user to stop current call and submit new one. How correctly do this?
The only idea I have is to communicate between client and server using mongo. In some place in 'doCalculation' function I can observe some mongo document/collection and based on this do sth in the function (e.g. call exception). Do you have any better ideas?
You can use a semaphore for this purpose. When the semaphore is 1, requests are allowed to be sent. When the semaphore is 0, requests are not allowed to be sent. The semaphore should be 1 by default and just before you send the request, you need to set it to 0. When a response is successful, you set the semaphore back to 1.
As about the timeout: You could use a time out using setTimeout after sending the request, like this:
if (semaphore) {
var isTimedOut = false;
var isSuccess = false;
semaphore = 0; //No need to use var keyword, as this should be declared outside of this scope
Meteor.call('doCalculation', function(err, result) {
isSuccess = true;
//do sth with result
});
setTimeout(function() {
if (!isSuccess) {
isTimeout = true;
//do something else, to handle the time out state
}
}, 10000);
}
This is tricky, because you cannot generally set timeouts from the client's point of view. You don't need to, for a bunch of architectural reasons. The most important thing is that if you lose network connectivity or the server crashes (two cases timeouts are designed to manage), the client is aware immediately because it is disconnected. You can use Meteor.status().connected if this happens often.
It sounds like you're running a long calculation on the server. My suggestion is to return a calculationId immediately, and then update a collection with progress, e.g., CalculationProgresses.update(calculationId, {$set: {progress: currentProgress}}) as you calculate. Your UI can then update the progress reactively, in the most convenient way possible.
Note, that when you do run long calculations on the server, you need to occasionally "yield," giving the chance for other work to happen. Node, on which Meteor is based, is tricky for long calculations if you don't master this notion of yielding. In Meteor, you can yield easily by updating a collection (e.g., your progress collection). This will solve lots of problems you're probably experiencing as you write your application.
i think you need a server-side solution for this. if you go with a client-side solution, you don't handle 2 cases:
the user reloads their browser
the user uses 2 browsers
i would create these methods:
isCalculationActive() -- this checks if the user already has a calculation active. on the server, you can either keep that fact in memory or write it to the db. on the client, if this returns false, then you can proceed to call doCalculation(). if true, you can give the user a popup or alert or something to ask if they want to cancel and proceed.
doCalculation() -- this cancels any outstanding calculation by that user and starts a new one.
with these implemented, the user can reload their browser w/o affecting either the running calculation or correct behavior. and if they try a 2nd browser, everything should still work as expected.
if you want to give the user the option to simply stop the job and not start a new one, then you can simply create:
cancelCalculation() -- this cancels any outstanding calculation by that user.

In Meteor, how do I show newly inserted data as greyed out until it's been confirmed by the server?

Say my application has a list of items of some kind, and users can insert new items in the list.
What Meteor normally does is: when a user inserts an item in the list, it appears in their browser immediately, without waiting for server confirmation.
What I want is: when an item is in this state (submitted but not yet acknowledged by the server), it appears at its correct position in the list, but greyed out.
Is there a way to make Meteor do this?
Sure. Make a method that does the insertion. When the method runs, have it check to see if it is running in simulation, and if so, set a 'temporary' or 'unconfirmed' flag on the inserted item. Use that to decide whether to render the item as greyed out.
Assuming you're using MongoDB:
// Put this in a file that will be loaded on both the client and server
Meteor.methods({
add_item: function (name) {
Items.insert({name: name,
confirmed: !this.isSimulation});
}
});
Calling the method:
Meteor.call("add_item", "my item name");
That's all you need to do. The reason this works is that once the server has finished saving the item, the local (simulated) changes on the client will be backed out and replaced with whatever actually happened on the server (which won't include the 'unconfirmed' flag.)
The above is the simplest way to do it, but it will result in all of the
records in your database having a 'confirmed' attrbiute of true. To avoid this, only set the confirmed attribute if it's false.
Refer to this part of documentation for more information about isSimulation and Meteor.methods
This is what I did added an observer on the server side,
I created a variable called notify false from the client side itself
once the server receives the udpate it will make notify true and the client will be updated on the same.
Collection.find({"notify":false}).observe({
"added" : function(first){
collection.update({"_id":first._id},{$set : {"notify":true}});
}
});

Resources