-
Notifications
You must be signed in to change notification settings - Fork 237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature: Notification system #1766
Feature: Notification system #1766
Conversation
✅ Deploy Preview for specter-desktop-docs canceled.
|
This really looks awesome! I'll soon will look more into this. One thing i'd suggest right away:
We should simplify this. Why not having somewhere a method like:
in that case, we only need to change the import. Maybe we can put that method into |
Thanks. I am glad to hear this. Doing #778 with zmq will be straight forward once this PR is merged.
Yeah, this long command was annoying. I now put it in https://github.com/relativisticelectron/specter-desktop/blob/20220616_notification_system/src/cryptoadvance/specter/notifications/current_flask_user.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a first feedback. I haven't looked at the javascript-code at all. Will do that later but maybe that might be the source of my misunderstandings.
src/cryptoadvance/specter/user.py
Outdated
# setting up the notifications system | ||
js_notifications = ui_notifications.JSNotifications() | ||
webapi_notifications = ui_notifications.WebAPINotifications() | ||
self.notification_manager = NotificationManager( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So ... every user gets its own NotificationManager? Can't we have a generic NotificationManager which is only there once? I know that e.g. the WalletManager is per User but i think that was an architectural error in the first place. So maybe we can have something similiar to the way encrypted Data is managed:
https://gist.github.com/k9ert/7cb1ca08453e848ae4ec03eb0fd0790a
A Generic Manager (in the diagram ServiceEncryptedStorageManager
which is in this case derived from GenericDataManager
) receives the call. It's only there once and initialized by the specter object (I don't like the singleton-logic in there due to flask way of handling the app
#1634 ). It'll be Flask aware and derives the user either by current_user
or by argument. It holds a cache of user-specific serviceEncryptedStorage
objects (and creates them as needed) and simply hand over that call to the correct of those objects.
That way, the managers are all centrally available and not somehow obtained through obscure object-networks where you don't know whether the objects are there to manage other objects or rather carry data with minimalistic functionality. I'd like to have a clear distinction between "BusinessObjects" and managers. Managers are handling business-Objects but business-Objects should NOT reference Managers. Otherwise you'll get to a horrible situation and unclear responibilities and confusing initialisation/upgrading code, see e.g. #1661
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. I had the manager first instantiated in app.specter
. However (of course) it was not differentiating between messages of different users. I would have to think about how and where to bring the user awareness in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ideally, this can be done at the endpoint-level. Making the managers non-user-aware is an advantage as then specter can be used independent of a flask-app. From an architectural point of view, that's a signifiant advantage.
However we already broke with that in the user_manager and the service_manager. In the service_manager, i mitigated that issue by assigning the object after the specter-initialisation. Similiar to how the specter-object is assigned to the Flask-App.
I think the mentioned ServiceEncryptedStorageManager
is mitigating it through it's not so nice Singleton approach.
Maybe we can have a rule like: If it's Flask-aware, assign it to an attribute of Specter after initialisation. If it's not, let it be created in the __init__method of Specter class.
The UserManager is not mitigating that issue right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not understand all, but I experimented on a branch and moved the NotificationManager directly in the Specter class.
- user independent UINotifications are created immediately
- user aware UINotifications are created later (one could extend this to save/load in json, which UINotifications were present for the users)
- Notifications and UINotifications have now a user_id to track to which user they belong to
See here the diff: https://github.com/relativisticelectron/specter-desktop/compare/20220616_notification_system...relativisticelectron:singleton?expand=1
Is that what you had in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Passing in the NotificationManager
imho doesn't improve the situation. Ideally businessObjects don't have many objects. They mostly carry data. Managers on the other hand, shouldn't carry much state and information but rather methods in order to manipulate businessObjects.
If you create a reference from a businessObject towards a manager, no matter whether you've initialized the manager in that businessObject or not, you're complicating things. E.g. testing: Instantiating a User is no longer that easy because you have to pass in a manager and if you pass some Mock of that Manager, you have to avoid using the User in a way which would trigger the Manager.
So, that's why i think that we should avoid making the businessObjects too clever and give them ways to be powerful. They should be passive and manipulated from the outside.
However (of course) it was not differentiating between messages of different users.
Well, there are two ways to achieve that:
- You pass in the user for each method of the NotificationManager
- You let the NotificationManager be aware of flask and let it import
current_user
I wrote above that it would be cool from an architectural point of view to have managers NOT be flask aware if it's not needed. Because managers are usually instantiated within the specter-object and so, the specter-object gets flask aware as well and you can no longer use the specterobject without flask ( you get: "Outside of ApplicationContext blabla").
So in order to have Managers which are flask aware but still not make specter flask-aware, you can assign the manager after the initialisation of Specter like we do it with the ServiceManager:
https://github.com/relativisticelectron/specter-desktop/blob/master/src/cryptoadvance/specter/server.py#L148-L151
That way it's still comfortable for the calling code to get hold on a reference of that manager via app.specter
. Hope that clarifies it? I would suggest you do it that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for all the feedback and explanation. I will work on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You pass in the user for each method of the NotificationManager
I did this now, and create the instance now after the Specter creation: https://github.com/relativisticelectron/specter-desktop/blob/e5de884350b8f338a50d23627ad1b8438184a5dd/src/cryptoadvance/specter/server.py#L154
src/cryptoadvance/specter/notifications/notification_manager.py
Outdated
Show resolved
Hide resolved
src/cryptoadvance/specter/notifications/notification_manager.py
Outdated
Show resolved
Hide resolved
src/cryptoadvance/specter/notifications/notification_manager.py
Outdated
Show resolved
Hide resolved
src/cryptoadvance/specter/notifications/notification_manager.py
Outdated
Show resolved
Hide resolved
I'll try to do another round of review the coming days. However i already know what i'm still concerned about: The constant requests every 5 seconds. How easy/difficult would it be to replace that by websockets? Update: |
Yes, websockets seem to be the solution for the repeated 5s requests. And they seem to be relatively straight forward too: https://www.alanwsmith.com/posts/setup-a-python-flask-web-server-with-websockets--20eo3p4t928f I can develop the websocket in a websocket branch (branching off this PR). EDIT: websockets is kind of fun. But I have to figure out how to run it properly besides flask and send messages between the main thread and the websockts-server thread. |
I'm currently also diving into sockets (without web-) and it's not very convenient coming from the rest-world. However in your case i don't think it's very complicated. You only need to communicate in one direction (from the server to the browser) and there is almost (at least in the beginning) only one message: "There are news! Reload!". |
Yeah, the requests are way easier to implement. Currently I am thinking about how to get a message from python (like a notification) into the infinite websockets server loop (which then can send it to the browser):
One way is to create a python websocket client and connect to the server (link). This allows sending messages into the infinite loop. Do you know a better way? EDIT: My idea really seems to be a legit way to do it..... https://stackoverflow.com/questions/54648015/python-websockets-how-to-send-message-from-function ... not elegant... buy I can do it that way. |
I'm not sure i've understood the problem correctly. I took the example from: Shouldn't that do it? I mean this should also be possible in a server-side loop instead of a flask-endpoint, no? |
Yes, that is the same logic as my code. I find it however not elegant, that a "long lived" connection/protocol, needs a fresh (python) client to (python) server The newest version has now this connection be also forever, and it works :-) https://github.com/relativisticelectron/specter-desktop/blob/20220703_websockets/src/cryptoadvance/specter/websockets_server.py#L142 EDIT: But the code is much more complex then the simple javascript polling that is currently in the PR.... And I put no consideration (yet) in for blocked ports, port config file, dropped connection (and trying to reconnect), user-awareness, .... @k9ert : What do you think?
|
I don't see that many code complexity. Sure, it's more complex than the polling but the effort of opening http-connections every x seconds and polluting the log is imho worth that complexity. |
ok.
I have to figure out how the websocket message goes only to the intended logged-in user, and not to all users (also the server has to check that answers come from the right user). In the current PR (polling) that is done via Idea: When loading the page, javascript |
@@ -57,6 +57,7 @@ class BaseConfig(object): | |||
# The prefix for extensions which don't get access to the session cookie (if SPECTER_URL_PREFIX isn't compromised) | |||
ISOLATED_CLIENT_EXT_URL_PREFIX = "/ext" | |||
|
|||
HOST = os.getenv("HOST", "127.0.0.1") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we move that to the config of the extension?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need the app.config["HOST"]
in the notification extension. Adding it to the global BaseConfig, does make it consistent with PORT
. And if another HOST
value if given for the server then the config value is updated here https://github.com/relativisticelectron/specter-desktop/blob/bf03c3236958c49405b7d56c71e0b6ab67c9f5e4/src/cryptoadvance/specter/cli/cli_server.py#L127
I don't really see how to move this to the service.
|
||
|
||
async function sendRequest(url, method_str, formData) { | ||
if (!formData) { | ||
formData = new FormData(); | ||
} | ||
formData.append("csrf_token", "{{ csrf_token() }}") | ||
d = { | ||
method: method_str, | ||
} | ||
if (method_str == 'POST') { | ||
d['body'] = formData; | ||
} | ||
|
||
const response = await fetch(url, d); | ||
if(response.status != 200){ | ||
showError(await response.text()); | ||
console.log(`Error while calling ${url} with ${method_str} ${formData}`) | ||
return | ||
} | ||
return await response.json(); | ||
} | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is, in the meantime, already implemented in static/helpers.js
Can you remove this here and use the other version ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 0d69ae8
@@ -172,6 +172,7 @@ | |||
{% endif %} | |||
</script> | |||
{% endif %} | |||
<!-- This scripts block will be overwritten in /device --> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure i understand the comment. The blocks are there to be overwritten. Sure, some of them indeed are. But why do we comment that here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was a reminder for me, that {% include "includes/language/language_js.jinja" %}
is actually not imported any more in /device
(If I remember correctly). I am actually not sure if this behavior was intentional. I can remove the comment.
src/cryptoadvance/specter/specter.py
Outdated
# a list of functions that are called at cleanup_on_exit taking in each signum, frame | ||
self.call_functions_at_cleanup_on_exit = [] | ||
|
||
def service_manager_cleanup_on_exit(signum, frame): | ||
return self.service_manager.execute_ext_callbacks( | ||
callbacks.cleanup_on_exit, signum, frame | ||
) | ||
|
||
self.call_functions_at_cleanup_on_exit.append(service_manager_cleanup_on_exit) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is IMHO problematic as it's making the specter-object more aware of the serviceManager
. We currently only pass ot down to other managers.
Imho a reasonable solution would be to create the clean_on_exit
method to server.py and move the registering to server.py. Then we could call the specter.clean_on_exit
function from there OR, even better, register both functions.
This second option is currently prevented by this this:
# For some reason we need to explicitely exit here. Otherwise it will hang
exit(0)
Is this currently the case? I can't reproduce it but i know that this was, at some point, necessary for Stepan and maybe even @moneymanolis on Mac? Can we fix that somehow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't recall from the top of my head.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is IMHO problematic as it's making the specter-object more aware of the
serviceManager
.
Agreed. Done in bac4422
- removed duplicate sendRequest
Shutting down specter, i get this:
Any idea what might caught that? Can you fix that before the merge? Apart from that, it's imho ready to merge. I've tested it via the dev-extension and added a video in the above description. |
I cannot reproduce this error; I am using CTRL-C to close the specter server. How do you shut down specter (gracefully)? However it is clear why the error occurs
is called twice, and then it tries to send a websocket message to a shutdown websocket server. EDIT: Can you check if 7040326 solved it? |
That commit seem to have made it worse. The websocket-connection is somehow breaking. Here is a curated Log:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you do pip3 install -r requirements.txt --require-hashes
? (Line 2026 in .env/lib/python3.10/site-packages/werkzeug/routing.py
looks different for me)
How can I reproduce this error?
My bad! That issue disappeared but the ConnectionClosed did not. I assume that this is because of the
But maybe you want that idiom to be implemented somewhere deeper in the stack?! |
I've also resolved the conflict in
|
I would prefer to solve the problem where it occurs. How can I reproduce the error? |
I'm not aware doing anything special. python 3.10.4. Starting like below. CTRL-C for stopping the process.
|
…ification_system # Conflicts: # requirements.txt # tests/test_managers_service.py # tests/test_util_reflection.py
I'm ready to merge this! Any last words? |
The longest running PR is finally merged. Took 227 days, 250 commits, 149 comments. |
A feature for a notification system has been added, including websockets and SSL support, improved error handling, and better logging. There have been various bug fixes, refactoring, and documentation updates throughout the process. The feature is now complete and includes new functionality for managing and displaying notifications, as well as testing and debugging tools.m>
A feature for a notification system has been added, including websockets and SSL support, improved error handling, and better logging. There have been various bug fixes, refactoring, and documentation updates throughout the process. The feature is now complete and includes new functionality for managing and displaying notifications, as well as testing and debugging tools.m> Co-authored-by: relativisticelectron <[email protected]>
This is a notification system for sync and async messages
Issue: #1695
Features:
NotificationManager
(1 global one) forwards notifications to UINotifications (such asLoggingNotifications
,FlashNotifications
,JSConsoleNotifications
,JSNotifications
,WebAPINotifications
, which are instantiated for each user)NotificationManager
in an identical wayJSNotifications
,WebAPINotifications
WebAPINotifications
(= Notifications API ) is only available in localhost, tor-browser or httpsFurther things that are enabled by this
(this could even be in some config file) to
JSNotifications
,WebAPINotifications
are straight forward to implement. One could even extend this to add Buttons in the Notifications.JSNotifications
, or you want to send a rpc command to another process, or start a program like notify-send) can be added very easily.Limitations
WebAPINotifications
(= Notifications API ) does not seem to support images in Linux, and Android for Firefox or Chromium.Future extension ideas
Todos
app.specter.user_manager.get_user().notification_manager.flash
self["last_shown_date"]
is set consitently for all UINotificationsuser_id
is now an argument in all relevant functionsJSNotifications
,WebAPINotifications
inNotificationManager
. Make it such, that for all other UINotifications the messages are also deleted soon.RuntimeError: Working outside of request context.
It arises that I try to call a flash notification from a different thread (if the flash is the fallback for a failed websocket notification).There is a weird problem occurring .... Needs to be solved!See Bug: SSL and Chromium freezes Specter #1844logger.debug
after review (this will allow fast debugging if needed)** Current Status**