diff --git a/src/slic3r/GUI/ConnectRequestHandler.cpp b/src/slic3r/GUI/ConnectRequestHandler.cpp index 4af2f92148..0f4bf4a208 100644 --- a/src/slic3r/GUI/ConnectRequestHandler.cpp +++ b/src/slic3r/GUI/ConnectRequestHandler.cpp @@ -26,7 +26,7 @@ ConnectRequestHandler::ConnectRequestHandler() m_actions["ERROR"] = std::bind(&ConnectRequestHandler::on_connect_action_error, this, std::placeholders::_1); m_actions["LOG"] = std::bind(&ConnectRequestHandler::on_connect_action_log, this, std::placeholders::_1); m_actions["RELOAD_HOME_PAGE"] = std::bind(&ConnectRequestHandler::on_reload_event, this, std::placeholders::_1); - + m_actions["CLOSE_DIALOG"] = std::bind(&ConnectRequestHandler::on_connect_action_close_dialog, this, std::placeholders::_1); } ConnectRequestHandler::~ConnectRequestHandler() { diff --git a/src/slic3r/GUI/ConnectRequestHandler.hpp b/src/slic3r/GUI/ConnectRequestHandler.hpp index 017d68f0de..50053b545b 100644 --- a/src/slic3r/GUI/ConnectRequestHandler.hpp +++ b/src/slic3r/GUI/ConnectRequestHandler.hpp @@ -29,6 +29,7 @@ protected: virtual void on_connect_action_select_printer(const std::string& message_data) = 0; virtual void on_connect_action_print(const std::string& message_data) = 0; virtual void on_connect_action_webapp_ready(const std::string& message_data) = 0; + virtual void on_connect_action_close_dialog(const std::string& message_data) = 0; virtual void on_reload_event(const std::string& message_data) = 0; virtual void run_script_bridge(const wxString &script) = 0; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 22fef94c11..a5d3c120ee 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -4190,10 +4190,6 @@ void GUI_App::printables_slice_request(const std::string& download_url, const st m_downloader->init(dest_folder); m_downloader->start_download_printables(download_url, true, model_url, this); } -void GUI_App::printables_print_request(const std::string& download_url, const std::string& model_url) -{ - plater()->printables_to_connect_gcode(Utils::ServiceConfig::instance().printables_url() + model_url); -} void GUI_App::printables_login_request() { diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index a85c8a8480..5dde60ea54 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -413,14 +413,6 @@ public: void open_wifi_config_dialog(bool forced, const wxString& drive_path = {}); bool get_wifi_config_dialog_shown() const { return m_wifi_config_dialog_shown; } - void request_login(bool show_user_info = false) {} - bool check_login() { return false; } - void get_login_info() {} - bool is_user_login() { return true; } - - void request_user_login(int online_login) {} - void request_user_logout() {} - int request_user_unbind(std::string dev_id) { return 0; } bool select_printer_from_connect(const std::string& cmd); void select_filament_from_connect(const std::string& cmd); void handle_connect_request_printer_select(const std::string& cmd); @@ -438,7 +430,6 @@ public: void request_remove_project(std::string project_id) {} void printables_download_request(const std::string& download_url, const std::string& model_url); void printables_slice_request(const std::string& download_url, const std::string& model_url); - void printables_print_request(const std::string& download_url, const std::string& model_url); void printables_login_request(); void open_link_in_printables(const std::string& url); private: diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 45d6eaded8..525214cc29 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -127,6 +127,7 @@ #include "UserAccountUtils.hpp" #include "DesktopIntegrationDialog.hpp" #include "WebViewDialog.hpp" +#include "WebViewPanel.hpp" #include "ConfigWizardWebViewPage.hpp" #include "PresetArchiveDatabase.hpp" @@ -914,11 +915,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN_WIZARD, open_external_login); this->q->Bind(EVT_OPEN_EXTERNAL_LOGIN, open_external_login); - - // void on_account_login(const std::string& token); - // void on_account_will_refresh(); - // void on_account_did_refresh(const std::string& token); - // void on_account_logout(); + this->q->Bind(EVT_UA_LOGGEDOUT, [this](UserAccountSuccessEvent& evt) { user_account->clear(); std::string text = _u8L("Logged out from Prusa Account."); @@ -1031,7 +1028,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) wxGetApp().handle_connect_request_printer_select_inner(evt.data); }); this->q->Bind(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, [this](UserAccountFailEvent& evt) { - BOOST_LOG_TRIVIAL(error) << "Failed communication with Prusa Account: " << evt.data; + BOOST_LOG_TRIVIAL(error) << "Failed communication with Connect Printer endpoint: " << evt.data; user_account->on_communication_fail(); std::string msg = _u8L("Failed to select printer from Prusa Connect."); this->notification_manager->close_notification_of_type(NotificationType::SelectFilamentFromConnect); @@ -1044,6 +1041,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_UA_ENQUEUED_REFRESH, [this](SimpleEvent& evt) { this->main_frame->on_account_will_refresh(); }); + + this->q->Bind(EVT_PRINTABLES_CONNECT_PRINT, [this](wxCommandEvent& evt) { + if (!this->user_account->is_logged()) { + // show login dialog instead of print dialog + this->user_account->do_login(); + return; + } + this->q->printables_to_connect_gcode(into_u8(evt.GetString())); + }); } wxGetApp().other_instance_message_handler()->init(this->q); @@ -6059,12 +6065,9 @@ bool load_secret(const std::string& id, const std::string& opt, std::string& usr void Plater::printables_to_connect_gcode(const std::string& url) { - { - PrintablesConnectUploadDialog dialog(this, url); - if (dialog.ShowModal() != wxID_OK) { - return; - } - } + PrintablesConnectUploadDialog dialog(this, url); + dialog.ShowModal(); + } void Plater::connect_gcode() diff --git a/src/slic3r/GUI/UserAccount.cpp b/src/slic3r/GUI/UserAccount.cpp index b7bc79a59f..2ac4a47980 100644 --- a/src/slic3r/GUI/UserAccount.cpp +++ b/src/slic3r/GUI/UserAccount.cpp @@ -103,6 +103,10 @@ void UserAccount::enqueue_printer_data_action(const std::string& uuid) { m_communication->enqueue_printer_data_action(uuid); } +void UserAccount::request_refresh() +{ + m_communication->request_refresh(); +} bool UserAccount::on_login_code_recieved(const std::string& url_message) { diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp index 967581f3b0..4e0276ee5c 100644 --- a/src/slic3r/GUI/UserAccount.hpp +++ b/src/slic3r/GUI/UserAccount.hpp @@ -48,6 +48,7 @@ public: void enqueue_connect_printer_models_action(); void enqueue_avatar_action(); void enqueue_printer_data_action(const std::string& uuid); + void request_refresh(); // Clears all data and connections, called on logout or EVT_UA_RESET void clear(); diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index 877ff1fe1c..0268da1d74 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -470,6 +470,13 @@ void UserAccountCommunication::enqueue_printer_data_action(const std::string& uu } wakeup_session_thread(); } + +void UserAccountCommunication::request_refresh() +{ + m_token_timer->Stop(); + enqueue_refresh(); +} + void UserAccountCommunication::enqueue_refresh() { { @@ -529,8 +536,7 @@ void UserAccountCommunication::on_activate_app(bool active) #endif if (active && m_next_token_refresh_at > 0 && m_next_token_refresh_at - now < refresh_threshold) { BOOST_LOG_TRIVIAL(info) << "Enqueue access token refresh on activation"; - m_token_timer->Stop(); - enqueue_refresh(); + request_refresh(); } } @@ -547,9 +553,10 @@ void UserAccountCommunication::set_refresh_time(int seconds) { assert(m_token_timer); m_token_timer->Stop(); - const auto prior_expiration_secs = 5 * 60; - int milliseconds = std::max((seconds - prior_expiration_secs) * 1000, 60000); + const auto prior_expiration_secs = std::max(seconds / 24, 10); + int milliseconds = std::max((seconds - prior_expiration_secs) * 1000, 1000); m_next_token_refresh_at = std::time(nullptr) + milliseconds / 1000; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " " << milliseconds / 1000; m_token_timer->StartOnce(milliseconds); } diff --git a/src/slic3r/GUI/UserAccountCommunication.hpp b/src/slic3r/GUI/UserAccountCommunication.hpp index b1d569be54..fdd4ff147a 100644 --- a/src/slic3r/GUI/UserAccountCommunication.hpp +++ b/src/slic3r/GUI/UserAccountCommunication.hpp @@ -53,6 +53,7 @@ public: void enqueue_test_connection(); void enqueue_printer_data_action(const std::string& uuid); void enqueue_refresh(); + void request_refresh(); // Callbacks - called from UI after receiving Event from Session thread. Some might use Session thread. // diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index 1ff4f22f18..55e66f6d5b 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -155,7 +154,6 @@ void UserAccountSession::token_success_callback(const std::string& body) BOOST_LOG_TRIVIAL(debug) << "Access token refreshed"; // Data we need std::string access_token, refresh_token, shared_session_key; - int expires_in = 300; try { std::stringstream ss(body); pt::ptree ptree; @@ -164,25 +162,20 @@ void UserAccountSession::token_success_callback(const std::string& body) const auto access_token_optional = ptree.get_optional("access_token"); const auto refresh_token_optional = ptree.get_optional("refresh_token"); const auto shared_session_key_optional = ptree.get_optional("shared_session_key"); - const auto expires_in_optional = ptree.get_optional("expires_in"); - if (access_token_optional) access_token = *access_token_optional; if (refresh_token_optional) refresh_token = *refresh_token_optional; if (shared_session_key_optional) shared_session_key = *shared_session_key_optional; - assert(expires_in_optional); - if (expires_in_optional) - expires_in = *expires_in_optional; } catch (const std::exception&) { std::string msg = "Could not parse server response after code exchange."; wxQueueEvent(p_evt_handler, new UserAccountFailEvent(EVT_UA_RESET, std::move(msg))); return; } - - if (access_token.empty() || refresh_token.empty() || shared_session_key.empty()) { + int expires_in = Utils::get_exp_seconds(access_token); + if (access_token.empty() || refresh_token.empty() || shared_session_key.empty() || expires_in <= 0) { // just debug msg, no need to translate std::string msg = GUI::format("Failed read tokens after POST.\nAccess token: %1%\nRefresh token: %2%\nShared session token: %3%\nbody: %4%", access_token, refresh_token, shared_session_key, body); { diff --git a/src/slic3r/GUI/UserAccountSession.hpp b/src/slic3r/GUI/UserAccountSession.hpp index 6b3d793d3c..ec1924778c 100644 --- a/src/slic3r/GUI/UserAccountSession.hpp +++ b/src/slic3r/GUI/UserAccountSession.hpp @@ -122,7 +122,7 @@ public: m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_STATUS] = std::make_unique("CONNECT_STATUS", sc.connect_status_url(), EVT_UA_PRUSACONNECT_STATUS_SUCCESS, EVT_UA_FAIL); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_PRINTER_MODELS] = std::make_unique("CONNECT_PRINTER_MODELS", sc.connect_printer_list_url(), EVT_UA_PRUSACONNECT_PRINTER_MODELS_SUCCESS, EVT_UA_FAIL); m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_AVATAR] = std::make_unique("AVATAR", sc.media_url(), EVT_UA_AVATAR_SUCCESS, EVT_UA_FAIL); - m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID] = std::make_unique("USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID", sc.connect_printers_url(), EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, EVT_UA_FAIL); + m_actions[UserAccountActionID::USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID] = std::make_unique("USER_ACCOUNT_ACTION_CONNECT_DATA_FROM_UUID", sc.connect_printers_url(), EVT_UA_PRUSACONNECT_PRINTER_DATA_SUCCESS, EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL); } ~UserAccountSession() { diff --git a/src/slic3r/GUI/WebView.hpp b/src/slic3r/GUI/WebView.hpp index 40ec2fa534..11afc09268 100644 --- a/src/slic3r/GUI/WebView.hpp +++ b/src/slic3r/GUI/WebView.hpp @@ -10,8 +10,6 @@ class wxString; namespace WebView { - // When using WebView in Panel, it is benfitable to call create only when it is being shown. (lower CPU usage) - // But when using WebView in Dialog, call both. wxWebView* webview_new(); void webview_create(wxWebView* webview, wxWindow *parent, const wxString& url, const std::vector& message_handlers); }; diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index 485c974c27..1b7740f630 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -219,13 +219,20 @@ void WebViewDialog::on_reload_button(wxCommandEvent& WXUNUSED(evt)) m_browser->Reload(); } - void WebViewDialog::on_navigation_request(wxWebViewEvent &evt) { + BOOST_LOG_TRIVIAL(debug) << "WebViewDialog::on_navigation_request " << evt.GetURL(); } +void WebViewDialog::on_loaded(wxWebViewEvent &evt) +{ + BOOST_LOG_TRIVIAL(debug) << "WebViewDialog::on_loaded " << evt.GetURL(); +} + + void WebViewDialog::on_script_message(wxWebViewEvent& evt) { + BOOST_LOG_TRIVIAL(error) << "Unhandled script message: " << evt.GetString(); } /** @@ -634,13 +641,14 @@ void PrinterPickWebViewDialog::on_reload_event(const std::string& message_data) m_browser->LoadURL(m_default_url); } + PrintablesConnectUploadDialog::PrintablesConnectUploadDialog(wxWindow* parent, const std::string url) : WebViewDialog(parent - , GUI::from_u8(url) - , _L("Choose a printer") - , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 4 * 3) - ,{"_prusaSlicer"} - , "connect_loading") + , GUI::from_u8(url) + , _L("Choose a printer") + , wxSize(parent->GetClientSize().x / 4 * 3, parent->GetClientSize().y/ 4 * 3) + ,{"_prusaSlicer"} + , "connect_loading") { wxDisplay display(wxDisplay::GetFromWindow(this)); wxRect geometry = display.GetGeometry(); @@ -657,7 +665,39 @@ void PrintablesConnectUploadDialog::on_dpi_changed(const wxRect &suggested_rect) Refresh(); } +void PrintablesConnectUploadDialog::on_script_message(wxWebViewEvent& evt) +{ + handle_message(into_u8(evt.GetString())); +} +void PrintablesConnectUploadDialog::on_connect_action_select_printer(const std::string& message_data) +{ + // SELECT_PRINTER request is not defined for PrintablesConnectUploadDialog + assert(true); +} +void PrintablesConnectUploadDialog::on_connect_action_print(const std::string& message_data) +{ + assert(true); +} + +void PrintablesConnectUploadDialog::on_connect_action_webapp_ready(const std::string& message_data) +{ + // WEBAPP_READY request is not defined for PrintablesConnectUploadDialog + assert(true); +} + +void PrintablesConnectUploadDialog::on_reload_event(const std::string& message_data) +{ + if (!m_browser) { + return; + } + m_browser->LoadURL(m_default_url); +} + +void PrintablesConnectUploadDialog::on_connect_action_close_dialog(const std::string& message_data) +{ + this->EndModal(wxID_OK); +} LoginWebViewDialog::LoginWebViewDialog(wxWindow *parent, std::string &ret_val, const wxString& url, wxEvtHandler* evt_handler) : WebViewDialog(parent, url, _L("Log in dialog"), wxSize(50 * wxGetApp().em_unit(), 80 * wxGetApp().em_unit()), {}) diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index fc24eab489..318fd9cefa 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -52,7 +52,7 @@ public: void On_enable_dev_tools(wxCommandEvent& evt); virtual void on_navigation_request(wxWebViewEvent &evt); - virtual void on_loaded(wxWebViewEvent &evt) {} + virtual void on_loaded(wxWebViewEvent &evt); void run_script(const wxString& javascript); @@ -105,17 +105,25 @@ protected: void run_script_bridge(const wxString& script) override { run_script(script); } void on_dpi_changed(const wxRect &suggested_rect) override; void on_reload_event(const std::string& message_data) override; + void on_connect_action_close_dialog(const std::string& message_data) override {assert(true);} private: std::string& m_ret_val; }; -class PrintablesConnectUploadDialog : public WebViewDialog +class PrintablesConnectUploadDialog : public WebViewDialog, public ConnectRequestHandler { public: PrintablesConnectUploadDialog(wxWindow* parent, const std::string url); + void on_script_message(wxWebViewEvent& evt) override; protected: void on_dpi_changed(const wxRect &suggested_rect) override; + void on_connect_action_select_printer(const std::string& message_data) override; + void on_connect_action_print(const std::string& message_data) override; + void on_connect_action_webapp_ready(const std::string& message_data) override; + void on_reload_event(const std::string& message_data) override; + void run_script_bridge(const wxString &script) override { run_script(script); } + void on_connect_action_close_dialog(const std::string& message_data) override; }; class LoginWebViewDialog : public WebViewDialog diff --git a/src/slic3r/GUI/WebViewPanel.cpp b/src/slic3r/GUI/WebViewPanel.cpp index 583c7ec960..e2242fc395 100644 --- a/src/slic3r/GUI/WebViewPanel.cpp +++ b/src/slic3r/GUI/WebViewPanel.cpp @@ -10,6 +10,7 @@ #include "slic3r/GUI/WebViewPlatformUtils.hpp" #include "slic3r/GUI/MsgDialog.hpp" #include "slic3r/GUI/Field.hpp" +#include "slic3r/Utils/Http.hpp" #include "libslic3r/AppConfig.hpp" #include "libslic3r/Config.hpp" @@ -17,7 +18,7 @@ #include #include - +#include #include #include #include @@ -29,6 +30,8 @@ // to set authorization cookie for all WebKit requests to Connect #define AUTH_VIA_FETCH_OVERRIDE 0 +wxDEFINE_EVENT(EVT_PRINTABLES_CONNECT_PRINT, wxCommandEvent); + namespace pt = boost::property_tree; namespace Slic3r::GUI { @@ -220,6 +223,7 @@ void WebViewPanel::on_show(wxShowEvent& evt) if (m_do_late_webview_create) { m_do_late_webview_create = false; late_create(); + return; } if (m_load_default_url) { m_load_default_url = false; @@ -251,7 +255,7 @@ void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt)) // So we just reset the handler here. if (!m_script_message_hadler_names.empty()) { m_browser->RemoveScriptMessageHandler(Slic3r::GUI::from_u8(m_script_message_hadler_names.front())); - bool b = m_browser->AddScriptMessageHandler(Slic3r::GUI::from_u8(m_script_message_hadler_names.front())); + m_browser->AddScriptMessageHandler(Slic3r::GUI::from_u8(m_script_message_hadler_names.front())); } } @@ -267,6 +271,10 @@ void WebViewPanel::on_loaded(wxWebViewEvent& evt) if (evt.GetURL().IsEmpty()) return; m_load_default_url_on_next_error = false; + if (evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND && m_load_default_url) { + m_load_default_url = false; + load_default_url(); + } } /** @@ -568,12 +576,13 @@ ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent) { auto* plater = wxGetApp().plater(); plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this); + plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this); } void ConnectWebViewPanel::late_create() { WebViewPanel::late_create(); - if (!m_browser) { + if (!m_browser) { return; } @@ -587,6 +596,21 @@ void ConnectWebViewPanel::late_create() resend_config(); } +void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) +{ + e.Skip(); + if (!m_browser) { + return; + } + auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); + assert(!access_token.empty()); + + wxString javascript = get_login_script(true); + BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; + m_browser->RunScriptAsync(javascript); + resend_config(); +} + ConnectWebViewPanel::~ConnectWebViewPanel() { } @@ -774,6 +798,9 @@ void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) if (m_reached_default_url && !evt.GetURL().StartsWith(m_default_url)) { BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; evt.Veto(); + } else if (m_reached_default_url && evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND) { + // Do not allow back button to loading screen + evt.Veto(); } } @@ -847,16 +874,32 @@ PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& defau { } +void PrinterWebViewPanel::on_navigation_request(wxWebViewEvent &evt) +{ + const wxString url = evt.GetURL(); + if (url.StartsWith(m_default_url) && !m_api_key_sent) { + m_reached_default_url = true; + if (!m_usr.empty() && !m_psk.empty()) { + send_credentials(); + } + } else if (m_reached_default_url && evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND) { + // Do not allow back button to loading screen + evt.Veto(); + } +} + void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt) { if (evt.GetURL().IsEmpty()) return; m_load_default_url_on_next_error = false; - + if (evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND && m_load_default_url) { + m_load_default_url = false; + load_default_url(); + return; + } if (!m_api_key.empty()) { send_api_key(); - } else if (!m_usr.empty() && !m_psk.empty()) { - send_credentials(); } } void PrinterWebViewPanel::on_script_message(wxWebViewEvent& evt) @@ -900,15 +943,12 @@ void PrinterWebViewPanel::send_credentials() return; m_browser->RemoveAllUserScripts(); m_browser->AddUserScript("sessionStorage.removeItem('authType'); sessionStorage.removeItem('apiKey'); console.log('Session Storage cleared');"); - m_browser->Reload(); + // reload would be done only if called from on_loaded + //m_browser->Reload(); m_api_key_sent = true; setup_webview_with_credentials(m_browser, m_usr, m_psk); } -void PrinterWebViewPanel::sys_color_changed() -{ -} - PrintablesWebViewPanel::PrintablesWebViewPanel(wxWindow* parent) : WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().printables_url()), { "ExternalApp" }, "other_loading", "other_error", false) @@ -960,6 +1000,9 @@ void PrintablesWebViewPanel::on_navigation_request(wxWebViewEvent &evt) } else if (m_reached_default_url && url.StartsWith("http")) { BOOST_LOG_TRIVIAL(info) << evt.GetURL() << " does not start with default url. Vetoing."; evt.Veto(); + } else if (m_reached_default_url && evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND) { + // Do not allow back button to loading screen + evt.Veto(); } } @@ -970,9 +1013,22 @@ wxString PrintablesWebViewPanel::get_default_url() const void PrintablesWebViewPanel::on_loaded(wxWebViewEvent& evt) { + if (evt.GetURL().Find(GUI::format_wxstr("/web/%1%.html", m_loading_html)) != wxNOT_FOUND && m_load_default_url) { + m_load_default_url = false; + load_default_url(); + return; + } + if (evt.GetURL().StartsWith(m_default_url)) { + define_css(); + } else { + m_styles_defined = false; + } #ifdef _WIN32 // This is needed only once after add_request_authorization - remove_request_authorization(m_browser); + if (m_remove_request_auth) { + m_remove_request_auth = false; + remove_request_authorization(m_browser); + } #endif m_load_default_url_on_next_error = false; } @@ -1049,9 +1105,11 @@ void PrintablesWebViewPanel::logout(const std::string& override_url/* = std::str if (!m_shown || !m_browser) { return; } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + hide_loading_overlay(); delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); m_browser->RunScript("localStorage.clear();"); - + std::string next_url = override_url.empty() ? get_url_lang_theme(m_browser->GetCurrentURL()) : get_url_lang_theme(from_u8(override_url)); @@ -1061,17 +1119,18 @@ void PrintablesWebViewPanel::logout(const std::string& override_url/* = std::str // We cannot do simple reload here, it would keep the access token in the header load_request(m_browser, next_url, std::string()); #endif // - + } void PrintablesWebViewPanel::login(const std::string& access_token, const std::string& override_url/* = std::string()*/) { if (!m_shown) { return; } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + hide_loading_overlay(); // We cannot add token to header as when making the first request. // In fact, we shall not do request here, only run scripts. // postMessage accessTokenWillChange -> postMessage accessTokenChange -> window.location.reload(); - wxString script = "window.postMessage(JSON.stringify({ event: 'accessTokenWillChange' }))"; run_script(script); @@ -1094,19 +1153,23 @@ void PrintablesWebViewPanel::load_default_url() if (!m_browser) { return; } + hide_loading_overlay(); std::string actual_default_url = get_url_lang_theme(from_u8(Utils::ServiceConfig::instance().printables_url() + "/homepage")); const std::string access_token = wxGetApp().plater()->get_user_account()->get_access_token(); - // in case of opening printables logged out - delete cookies and localstorage to get rid of last login if (access_token.empty()) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " logout"; delete_cookies(m_browser, Utils::ServiceConfig::instance().printables_url()); m_browser->AddUserScript("localStorage.clear();"); load_url(actual_default_url); return; } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " login"; + // add token to first request #ifdef _WIN32 add_request_authorization(m_browser, m_default_url, access_token); + m_remove_request_auth = true; load_url(GUI::from_u8(actual_default_url)); #else load_request(m_browser, actual_default_url, access_token); @@ -1118,6 +1181,7 @@ void PrintablesWebViewPanel::send_refreshed_token(const std::string& access_toke if (m_load_default_url) { return; } + hide_loading_overlay(); wxString script = GUI::format_wxstr("window.postMessage(JSON.stringify({" "event: 'accessTokenChange'," "token: '%1%'" @@ -1136,7 +1200,7 @@ void PrintablesWebViewPanel::send_will_refresh() void PrintablesWebViewPanel::on_script_message(wxWebViewEvent& evt) { - BOOST_LOG_TRIVIAL(error) << "received message from Printables: " << evt.GetString(); + BOOST_LOG_TRIVIAL(debug) << "received message from Printables: " << evt.GetString(); handle_message(into_u8(evt.GetString())); } @@ -1150,10 +1214,17 @@ void PrintablesWebViewPanel::sys_color_changed() void PrintablesWebViewPanel::on_printables_event_access_token_expired(const std::string& message_data) { - // accessTokenExpired - // Printables pozaduje refresh access tokenu, muze byt volano nekolikrat.Nechme na Mobilni aplikaci at zaridi to ze zareaguje jen jednou - - // We do no react on this event now - Our Acount managment should know when to renew our tokens. + // { "event": "accessTokenExpired:) + // There seems to be a situation where we get accessTokenExpired when there is active token from Slicer POW + // We need get new token and freeze webview until its not refreshed + if (m_refreshing_token) { + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << " already refreshing"; + return; + } + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + m_refreshing_token = true; + show_loading_overlay(); + wxGetApp().plater()->get_user_account()->request_refresh(); } void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) @@ -1161,6 +1232,24 @@ void PrintablesWebViewPanel::on_reload_event(const std::string& message_data) // Event from our error / loading html pages load_default_url(); } + +namespace { +std::string escape_url(const std::string& unescaped) +{ + std::string ret_val; + CURL* curl = curl_easy_init(); + if (curl) { + char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size()); + if (decoded) { + ret_val = std::string(decoded); + curl_free(decoded); + } + curl_easy_cleanup(curl); + } + return ret_val; +} +} + void PrintablesWebViewPanel::on_printables_event_print_gcode(const std::string& message_data) { // { "event": "downloadFile", "url": "https://media.printables.com/somesecure.stl", "modelUrl": "https://www.printables.com/model/123" } @@ -1181,7 +1270,9 @@ void PrintablesWebViewPanel::on_printables_event_print_gcode(const std::string& return; } assert(!download_url.empty() && !model_url.empty()); - wxGetApp().printables_print_request(download_url, model_url); + wxCommandEvent* evt = new wxCommandEvent(EVT_PRINTABLES_CONNECT_PRINT); + evt->SetString(from_u8(Utils::ServiceConfig::instance().connect_printables_print_url() +"?url=" + escape_url(download_url))); + wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt); } void PrintablesWebViewPanel::on_printables_event_download_file(const std::string& message_data) { @@ -1206,6 +1297,7 @@ void PrintablesWebViewPanel::on_printables_event_download_file(const std::string assert(!download_url.empty() && !model_url.empty()); boost::filesystem::path url_path(download_url); show_download_notification(url_path.filename().string()); + wxGetApp().printables_download_request(download_url, model_url); } void PrintablesWebViewPanel::on_printables_event_slice_file(const std::string& message_data) @@ -1229,6 +1321,7 @@ void PrintablesWebViewPanel::on_printables_event_slice_file(const std::string& m return; } assert(!download_url.empty() && !model_url.empty()); + wxGetApp().printables_slice_request(download_url, model_url); } @@ -1238,17 +1331,47 @@ void PrintablesWebViewPanel::on_printables_event_required_login(const std::strin wxGetApp().printables_login_request(); } -void PrintablesWebViewPanel::show_download_notification(const std::string& filename) +void PrintablesWebViewPanel::define_css() { - std::string message_filename = GUI::format(_u8L("Downloading %1%"),filename); - std::string message_dest = GUI::format(_u8L("To %1%"), escape_string_cstyle(wxGetApp().app_config->get("url_downloader_dest"))); - std::string script = GUI::format(R"( - // Inject custom CSS - var style = document.createElement('style'); - style.innerHTML = ` + if (m_styles_defined) { + return; + } + m_styles_defined = true; + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + const std::string script = R"( + var style = document.createElement('style'); + style.innerHTML = ` body { /* Add your body styles here */ } + .slic3r-loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(127 127 127 / 50%); + z-index: 50; + display: flex; + align-items: center; + justify-content: center; + } + .slic3r-loading-anim { + width: 60px; + aspect-ratio: 4; + --_g: no-repeat radial-gradient(circle closest-side,#000 90%,#0000); + background: + var(--_g) 0% 50%, + var(--_g) 50% 50%, + var(--_g) 100% 50%; + background-size: calc(100%/3) 100%; + animation: slic3r-loading-anim 1s infinite linear; + } + @keyframes slic3r-loading-anim { + 33%{background-size:calc(100%/3) 0% ,calc(100%/3) 100%,calc(100%/3) 100%} + 50%{background-size:calc(100%/3) 100%,calc(100%/3) 0% ,calc(100%/3) 100%} + 66%{background-size:calc(100%/3) 100%,calc(100%/3) 100%,calc(100%/3) 0% } + } .notification-popup { position: fixed; right: 10px; @@ -1300,17 +1423,32 @@ void PrintablesWebViewPanel::show_download_notification(const std::string& filen color: #ffa500; /* Orange "X" */ font-weight: bold; } - `; - document.head.appendChild(style); + `; + document.head.appendChild(style); + )"; + run_script(script); +} - // Define the notification functions - function appendNotification() { +void PrintablesWebViewPanel::show_download_notification(const std::string& filename) +{ + // There was a trouble with passing wide characters to the script (it was displayed wrong) + // Solution is to URL-encode the strings here and pass it. + // Then inside javascript decodes it. + const std::string message_filename = Http::url_encode(GUI::format(_u8L("Downloading %1%"),filename)); + const std::string message_dest = Http::url_encode(GUI::format(_u8L("To %1%"), wxGetApp().app_config->get("url_downloader_dest"))); + std::string script = GUI::format(R"( + function removeNotification() { + const notifDiv = document.getElementById('slicer-notification'); + if (notifDiv) + notifDiv.remove(); + } + function appendNotification() { const body = document.getElementsByTagName('body')[0]; const notifDiv = document.createElement('div'); notifDiv.innerHTML = `
- PrusaSlicer: %1% -
%2% + PrusaSlicer: ${decodeURIComponent('%1%')} +
${decodeURIComponent('%2%')}
`; notifDiv.className = 'notification-popup'; @@ -1319,16 +1457,42 @@ void PrintablesWebViewPanel::show_download_notification(const std::string& filen window.setTimeout(removeNotification, 5000); } - - function removeNotification() { - const notifDiv = document.getElementById('slicer-notification'); - if (notifDiv) - notifDiv.remove(); - } - - appendNotification(); -)", message_filename, message_dest); + appendNotification(); + )", message_filename, message_dest); run_script(script); } +void PrintablesWebViewPanel::show_loading_overlay() +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + std::string script = R"( + function slic3r_showLoadingOverlay() { + const body = document.getElementsByTagName('body')[0]; + const overlayDiv = document.createElement('div'); + overlayDiv.className = 'slic3r-loading-overlay' + overlayDiv.id = 'slic3r-loading-overlay'; + overlayDiv.innerHTML = '
'; + body.appendChild(overlayDiv); + } + slic3r_showLoadingOverlay(); + )"; + run_script(script); +} + +void PrintablesWebViewPanel::hide_loading_overlay() +{ + BOOST_LOG_TRIVIAL(debug) << __FUNCTION__; + m_refreshing_token = false; + std::string script = R"( + function slic3r_hideLoadingOverlay() { + const overlayDiv = document.getElementById('slic3r-loading-overlay'); + if (overlayDiv) + overlayDiv.remove(); + } + slic3r_hideLoadingOverlay(); + )"; + run_script(script); +} + + } // namespace slic3r::GUI \ No newline at end of file diff --git a/src/slic3r/GUI/WebViewPanel.hpp b/src/slic3r/GUI/WebViewPanel.hpp index 3034e23d47..1611ad545c 100644 --- a/src/slic3r/GUI/WebViewPanel.hpp +++ b/src/slic3r/GUI/WebViewPanel.hpp @@ -17,7 +17,7 @@ class wxWebView; class wxWebViewEvent; -wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent); +wxDECLARE_EVENT(EVT_PRINTABLES_CONNECT_PRINT, wxCommandEvent); namespace Slic3r::GUI { @@ -141,6 +141,8 @@ protected: void on_page_will_load() override; void on_connect_action_error(const std::string &message_data) override; void on_reload_event(const std::string& message_data) override; + void on_connect_action_close_dialog(const std::string& message_data) override {assert(true);} + void on_user_token(UserAccountSuccessEvent& e); private: static wxString get_login_script(bool refresh); static wxString get_logout_script(); @@ -154,6 +156,7 @@ public: void on_loaded(wxWebViewEvent& evt) override; void on_script_message(wxWebViewEvent& evt) override; + void on_navigation_request(wxWebViewEvent &evt) override; void send_api_key(); void send_credentials(); void set_api_key(const std::string &key) @@ -168,7 +171,6 @@ public: m_psk = psk; } void clear() { m_api_key.clear(); m_usr.clear(); m_psk.clear(); m_api_key_sent = false; } - void sys_color_changed() override; private: std::string m_api_key; std::string m_usr; @@ -202,10 +204,19 @@ private: void on_printables_event_required_login(const std::string& message_data); void load_default_url() override; std::string get_url_lang_theme(const wxString& url) const; + void define_css(); void show_download_notification(const std::string& filename); + void show_loading_overlay(); + void hide_loading_overlay(); std::map> m_events; std::string m_next_show_url; + + bool m_refreshing_token {false}; + bool m_styles_defined {false}; +#ifdef _WIN32 + bool m_remove_request_auth { false }; +#endif /* Eventy Slicer -> Printables accessTokenWillChange diff --git a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp index 2beeed0dce..8a9f8c79c0 100644 --- a/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp +++ b/src/slic3r/GUI/WebViewPlatformUtilsWin32.cpp @@ -26,11 +26,13 @@ void setup_webview_with_credentials(wxWebView* webview, const std::string& usern { ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); if (!webView2) { + BOOST_LOG_TRIVIAL(error) << "setup_webview_with_credentials Failed: Webview 2 is null."; return; } wxCOMPtr wv2_10; HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_10)); if (FAILED(hr)) { + BOOST_LOG_TRIVIAL(error) << "setup_webview_with_credentials Failed: ICoreWebView2_10 is null."; return; } @@ -68,11 +70,13 @@ void remove_webview_credentials(wxWebView* webview) { ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); if (!webView2) { + BOOST_LOG_TRIVIAL(error) << "remove_webview_credentials Failed: webView2 is null."; return; } wxCOMPtr wv2_10; HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_10)); if (FAILED(hr)) { + BOOST_LOG_TRIVIAL(error) << "remove_webview_credentials Failed: ICoreWebView2_10 is null."; return; } @@ -93,6 +97,7 @@ void delete_cookies(wxWebView* webview, const std::string& url) { ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); if (!webView2) { + BOOST_LOG_TRIVIAL(error) << "delete_cookies Failed: webView2 is null."; return; } @@ -183,11 +188,13 @@ void add_request_authorization(wxWebView* webview, const wxString& address, cons // The filter needs to be removed to stop adding the auth header ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); if (!webView2) { + BOOST_LOG_TRIVIAL(error) << "Adding request Authorization Failed: Webview 2 is null."; return; } wxCOMPtr wv2_2; HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_2)); if (FAILED(hr)) { + BOOST_LOG_TRIVIAL(error) << "Adding request Authorization Failed: QueryInterface ICoreWebView2_2 has failed."; return; } filter_patern = address + "/*"; @@ -238,8 +245,10 @@ void remove_request_authorization(wxWebView* webview) { ICoreWebView2 *webView2 = static_cast(webview->GetNativeBackend()); if (!webView2) { + BOOST_LOG_TRIVIAL(error) << "remove_request_authorization Failed: webView2 is null."; return; } + BOOST_LOG_TRIVIAL(info) << "remove_request_authorization"; webView2->RemoveWebResourceRequestedFilter(filter_patern.c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); if(FAILED(webView2->remove_WebResourceRequested( m_webResourceRequestedTokenForImageBlocking))) { BOOST_LOG_TRIVIAL(error) << "WebView: Failed to remove resources"; @@ -256,6 +265,7 @@ void load_request(wxWebView* web_view, const std::string& address, const std::st ICoreWebView2 *webView2 = static_cast(web_view->GetNativeBackend()); if (!webView2) { + BOOST_LOG_TRIVIAL(error) << "load_request Failed: webView2 is null."; return; } @@ -263,12 +273,14 @@ void load_request(wxWebView* web_view, const std::string& address, const std::st wxCOMPtr webViewEnvironment; //webViewEnvironment = static_cast(web_view->GetEnviroment()); if (!webViewEnvironment.Get()) { + BOOST_LOG_TRIVIAL(error) << "load_request Failed: ICoreWebView2Environment is null."; return; } wxCOMPtr webViewEnvironment2; if (FAILED(webViewEnvironment->QueryInterface(IID_PPV_ARGS(&webViewEnvironment2)))) { + BOOST_LOG_TRIVIAL(error) << "load_request Failed: ICoreWebView2Environment2 is null."; return; } wxCOMPtr webResourceRequest; @@ -277,14 +289,17 @@ void load_request(wxWebView* web_view, const std::string& address, const std::st L"https://www.printables.com/", L"GET", NULL, L"Content-Type: application/x-www-form-urlencoded", &webResourceRequest))) { + BOOST_LOG_TRIVIAL(error) << "load_request Failed: CreateWebResourceRequest failed."; return; } wxCOMPtr wv2_2; if (FAILED(webView2->QueryInterface(IID_PPV_ARGS(&wv2_2)))) { + BOOST_LOG_TRIVIAL(error) << "load_request Failed: ICoreWebView2_2 is null."; return; } if (FAILED(wv2_2->NavigateWithWebResourceRequest(webResourceRequest.get()))) { + BOOST_LOG_TRIVIAL(error) << "load_request Failed: NavigateWithWebResourceRequest failed."; return; } } diff --git a/src/slic3r/Utils/Jwt.cpp b/src/slic3r/Utils/Jwt.cpp index 448a062656..1b8ba9168b 100644 --- a/src/slic3r/Utils/Jwt.cpp +++ b/src/slic3r/Utils/Jwt.cpp @@ -8,19 +8,21 @@ #include #include #include +#include namespace Slic3r::Utils { -bool verify_exp(const std::string& token) +namespace { +boost::optional get_exp(const std::string& token) { size_t payload_start = token.find('.'); if (payload_start == std::string::npos) - return false; + return boost::none; payload_start += 1; // payload starts after dot const size_t payload_end = token.find('.', payload_start); if (payload_end == std::string::npos) - return false; + return boost::none; size_t encoded_length = payload_end - payload_start; size_t decoded_length = boost::beast::detail::base64::decoded_size(encoded_length); @@ -40,7 +42,7 @@ bool verify_exp(const std::string& token) std::tie(written_bytes, read_bytes) = boost::beast::detail::base64::decode(json.data(), json_b64.data(), json_b64.length()); json.resize(written_bytes); if (written_bytes == 0) - return false; + return boost::none; namespace pt = boost::property_tree; @@ -48,10 +50,26 @@ bool verify_exp(const std::string& token) std::istringstream iss(json); pt::json_parser::read_json(iss, payload); - auto exp_opt = payload.get_optional("exp"); + return payload.get_optional("exp"); +} +} + +int get_exp_seconds(const std::string& token) +{ + auto exp_opt = get_exp(token); + if (!exp_opt) + return 0; + auto now = std::chrono::system_clock::now(); + auto now_in_seconds = std::chrono::duration_cast(now.time_since_epoch()).count(); + double remaining_time = *exp_opt - now_in_seconds; + return (int)remaining_time; +} + +bool verify_exp(const std::string& token) +{ + auto exp_opt = get_exp(token); if (!exp_opt) return false; - auto now = time(nullptr); return exp_opt.get() > now; } diff --git a/src/slic3r/Utils/Jwt.hpp b/src/slic3r/Utils/Jwt.hpp index b957e8df8d..468de282c4 100644 --- a/src/slic3r/Utils/Jwt.hpp +++ b/src/slic3r/Utils/Jwt.hpp @@ -5,6 +5,6 @@ namespace Slic3r::Utils { bool verify_exp(const std::string& token); - +int get_exp_seconds(const std::string& token); } diff --git a/src/slic3r/Utils/ServiceConfig.hpp b/src/slic3r/Utils/ServiceConfig.hpp index 6bfe5d9a94..20c3547137 100644 --- a/src/slic3r/Utils/ServiceConfig.hpp +++ b/src/slic3r/Utils/ServiceConfig.hpp @@ -14,6 +14,7 @@ public: std::string connect_select_printer_url() const { return m_connect_url + "/slicer-select-printer"; } std::string connect_printers_url() const { return m_connect_url + "/app/printers/"; } std::string connect_teams_url() const { return m_connect_url + "/app/teams"; } + std::string connect_printables_print_url() const { return m_connect_url + "/slicer-print"; } const std::string& account_url() const { return m_account_url; } const std::string& account_client_id() const { return m_account_client_id; }