SPE-2468: Printables WebView class

read address and download model
Printables as pernament tab
webview debug panel
adding token to request header
Logout to Printables
Correct user-agent
Receiving printables events
Improved refreshing of token to printables
User settings to printables url
merge changes
Changed login to Printables during runtime
Logging in plater fix
This commit is contained in:
David Kocik 2024-09-04 15:06:19 +02:00 committed by Lukas Matena
parent e8de4b7199
commit 1040332c87
24 changed files with 1769 additions and 1137 deletions

View File

@ -29,6 +29,10 @@ set(SLIC3R_GUI_SOURCES
GUI/UserAccount.hpp
GUI/WebViewDialog.cpp
GUI/WebViewDialog.hpp
GUI/WebViewPanel.cpp
GUI/WebViewPanel.hpp
GUI/ConnectRequestHandler.cpp
GUI/ConnectRequestHandler.hpp
GUI/WebView.cpp
GUI/WebView.hpp
GUI/WebViewPlatformUtils.hpp

View File

@ -0,0 +1,133 @@
#include "ConnectRequestHandler.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/format.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/UserAccount.hpp"
#include <boost/log/trivial.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
namespace pt = boost::property_tree;
namespace Slic3r::GUI {
ConnectRequestHandler::ConnectRequestHandler()
{
m_actions["REQUEST_LOGIN"] = std::bind(&ConnectRequestHandler::on_connect_action_request_login, this, std::placeholders::_1);
m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this, std::placeholders::_1);
m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready,this, std::placeholders::_1);
m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this, std::placeholders::_1);
m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1);
m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1);
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);
}
ConnectRequestHandler::~ConnectRequestHandler()
{
}
void ConnectRequestHandler::handle_message(const std::string& message)
{
// read msg and choose action
/*
v0:
{"type":"request","detail":{"action":"requestAccessToken"}}
v1:
{"action":"REQUEST_ACCESS_TOKEN"}
*/
std::string action_string;
try {
std::stringstream ss(message);
pt::ptree ptree;
pt::read_json(ss, ptree);
// v1:
if (const auto action = ptree.get_optional<std::string>("action"); action) {
action_string = *action;
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
if (action_string.empty()) {
BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message;
return;
}
assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling.
if (m_actions.find(action_string) != m_actions.end()) {
m_actions[action_string](message);
}
}
void ConnectRequestHandler::on_connect_action_error(const std::string &message_data)
{
BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data;
}
void ConnectRequestHandler::resend_config()
{
on_connect_action_request_config({});
}
void ConnectRequestHandler::on_connect_action_log(const std::string& message_data)
{
BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data;
}
void ConnectRequestHandler::on_connect_action_request_login(const std::string &message_data)
{}
void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data)
{
/*
accessToken?: string;
clientVersion?: string;
colorMode?: "LIGHT" | "DARK";
language?: ConnectLanguage;
sessionId?: string;
*/
const std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
//const std::string sesh = wxGetApp().plater()->get_user_account()->get_shared_session_key();
const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT";
wxString language = GUI::wxGetApp().current_language_code();
language = language.SubString(0, 1);
const std::string init_options = GUI::format("{\"accessToken\": \"%4%\",\"clientVersion\": \"%1%\", \"colorMode\": \"%2%\", \"language\": \"%3%\"}", SLIC3R_VERSION, dark_mode, language, token );
wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options);
run_script_bridge(script);
}
void ConnectRequestHandler::on_connect_action_request_open_in_browser(const std::string& message_data)
{
try {
std::stringstream ss(message_data);
pt::ptree ptree;
pt::read_json(ss, ptree);
if (const auto url = ptree.get_optional<std::string>("url"); url) {
wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url));
}
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
}
SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) :
wxDialog(parent, wxID_ANY, "Source Code",
wxDefaultPosition, wxSize(700,500),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE |
wxTE_RICH |
wxTE_READONLY);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(text, 1, wxEXPAND);
SetSizer(sizer);
}
} // namespace Slic3r::GUI

View File

@ -0,0 +1,44 @@
#ifndef slic3r_ConnectRequestHandler_hpp_
#define slic3r_ConnectRequestHandler_hpp_
#include <map>
#include <string>
#include <functional>
#include <wx/string.h>
#include <wx/dialog.h>
#include <wx/window.h>
//#define DEBUG_URL_PANEL
namespace Slic3r::GUI {
class ConnectRequestHandler
{
public:
ConnectRequestHandler();
~ConnectRequestHandler();
void handle_message(const std::string& message);
void resend_config();
protected:
// action callbacs stored in m_actions
virtual void on_connect_action_log(const std::string& message_data);
virtual void on_connect_action_error(const std::string& message_data);
virtual void on_connect_action_request_login(const std::string& message_data);
virtual void on_connect_action_request_config(const std::string& message_data);
virtual void on_connect_action_request_open_in_browser(const std::string& message_data);
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 run_script_bridge(const wxString &script) = 0;
std::map<std::string, std::function<void(const std::string&)>> m_actions;
};
class SourceViewDialog : public wxDialog
{
public:
SourceViewDialog(wxWindow* parent, wxString source);
};
} // namespace Slic3r::GUI
#endif /* slic3r_ConnectRequestHandler_hpp_ */

View File

@ -9,6 +9,7 @@
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <curl/curl.h>
namespace Slic3r {
namespace GUI {
@ -72,6 +73,21 @@ std::string filename_from_url(const std::string& url)
return std::string();
return std::string(url_plain.begin() + slash + 1, url_plain.end());
}
std::string unescape_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
int decodelen;
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
}
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
@ -133,23 +149,18 @@ void Downloader::start_download(const std::string& full_url)
{
assert(m_initialized);
// TODO: There is a misterious slash appearing in recieved msg on windows
#ifdef _WIN32
if (!boost::starts_with(full_url, "prusaslicer://open/?file=")) {
#else
if (!boost::starts_with(full_url, "prusaslicer://open?file=")) {
#endif
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
// TODO: show error?
std::string escaped_url = unescape_url(full_url);
if (boost::starts_with(escaped_url, "prusaslicer://open?file=")) {
escaped_url = escaped_url.substr(24);
}else if (boost::starts_with(escaped_url, "prusaslicer://open/?file=")) {
escaped_url = escaped_url.substr(25);
} else {
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
return;
}
}
size_t id = get_next_id();
// TODO: still same mistery
#ifdef _WIN32
std::string escaped_url = FileGet::escape_url(full_url.substr(25));
#else
std::string escaped_url = FileGet::escape_url(full_url.substr(24));
#endif
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
BOOST_LOG_TRIVIAL(error) << msg;

View File

@ -23,21 +23,6 @@ namespace GUI {
const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
std::string FileGet::escape_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
int decodelen;
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
bool FileGet::is_subdomain(const std::string& url, const std::string& domain)
{
// domain should be f.e. printables.com (.com including)

View File

@ -27,7 +27,6 @@ public:
void cancel();
void pause();
void resume();
static std::string escape_url(const std::string& url);
static bool is_subdomain(const std::string& url, const std::string& domain);
private:
std::unique_ptr<priv> p;

View File

@ -103,7 +103,6 @@
#include "WifiConfigDialog.hpp"
#include "UserAccount.hpp"
#include "UserAccountUtils.hpp"
#include "WebViewDialog.hpp"
#include "LoginDialog.hpp" // IWYU pragma: keep
#include "PresetArchiveDatabase.hpp"
@ -4113,6 +4112,12 @@ void GUI_App::show_printer_webview_tab()
mainframe->show_printer_webview_tab(preset_bundle->physical_printers.get_selected_printer_config());
}
void GUI_App::printables_request(const std::string& url)
{
this->mainframe->select_tab(size_t(0));
start_download(url);
}
bool LogGui::ignorred_message(const wxString& msg)

View File

@ -436,7 +436,7 @@ public:
void request_project_download(std::string project_id) {}
void request_open_project(std::string project_id) {}
void request_remove_project(std::string project_id) {}
void printables_request(const std::string& url);
private:
bool on_init_inner();
void init_app_config();

View File

@ -64,7 +64,7 @@
#include "GalleryDialog.hpp"
#include "NotificationManager.hpp"
#include "Preferences.hpp"
#include "WebViewDialog.hpp"
#include "WebViewPanel.hpp"
#include "UserAccount.hpp"
#ifdef _WIN32
@ -801,15 +801,39 @@ void MainFrame::create_preset_tabs()
add_created_tab(new TabSLAMaterial(m_tabpanel), "resin");
add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer");
m_printables_webview = new PrintablesWebViewPanel(m_tabpanel);
add_printables_webview_tab();
m_connect_webview = new ConnectWebViewPanel(m_tabpanel);
m_printer_webview = new PrinterWebViewPanel(m_tabpanel, L"");
m_printables_webview = new WebViewPanel(m_tabpanel, GUI::from_u8("https://www.printables.com"), { "_prusaSlicer" });
// new created tabs have to be hidden by default
m_connect_webview->Hide();
m_printer_webview->Hide();
}
void MainFrame::on_account_login(const std::string& token)
{
add_connect_webview_tab();
assert (m_printables_webview);
m_printables_webview->login(token);
}
void MainFrame::on_account_will_refresh()
{
m_printables_webview->send_will_refresh();
}
void MainFrame::on_account_did_refresh(const std::string& token)
{
m_printables_webview->send_refreshed_token(token);
}
void MainFrame::on_account_logout()
{
remove_connect_webview_tab();
assert (m_printables_webview);
m_printables_webview->logout();
}
void MainFrame::add_connect_webview_tab()
{
if (m_connect_webview_added) {
@ -820,7 +844,7 @@ void MainFrame::add_connect_webview_tab()
// insert "Connect" tab to position next to "Printer" tab
// order of tabs: Plater - Print Settings - Filaments - Printers - Prusa Connect - Prusa Link
int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1;
int n = m_tabpanel->FindPage(m_printables_webview) + 1;
wxWindow* page = m_connect_webview;
const wxString text(L"Prusa Connect");
const std::string bmp_name = "";
@ -856,7 +880,7 @@ void MainFrame::add_printables_webview_tab()
return;
}
int n = m_tabpanel->FindPage(m_connect_webview) + 1;
int n = m_tabpanel->FindPage(wxGetApp().get_tab(Preset::TYPE_PRINTER)) + 1;
wxWindow* page = m_printables_webview;
const wxString text(L"Printables");
const std::string bmp_name = "";
@ -865,6 +889,8 @@ void MainFrame::add_printables_webview_tab()
m_printables_webview->load_default_url_delayed();
m_printables_webview_added = true;
}
// no longer needed?
void MainFrame::remove_printables_webview_tab()
{
if (!m_printables_webview_added) {
@ -1221,6 +1247,8 @@ void MainFrame::on_sys_color_changed()
for (Tab* tab : wxGetApp().tabs_list)
tab->sys_color_changed();
if (m_printables_webview)
m_printables_webview->sys_color_changed();
if (m_connect_webview)
m_connect_webview->sys_color_changed();
if (m_printer_webview)

View File

@ -47,7 +47,7 @@ class PreferencesDialog;
class GalleryDialog;
class ConnectWebViewPanel;
class PrinterWebViewPanel;
class WebViewPanel;
class PrintablesWebViewPanel;
enum QuickSlice
{
@ -101,7 +101,7 @@ class MainFrame : public DPIFrame
ConnectWebViewPanel* m_connect_webview{ nullptr };
bool m_connect_webview_added{ false };
WebViewPanel* m_printables_webview{ nullptr };
PrintablesWebViewPanel* m_printables_webview{ nullptr };
bool m_printables_webview_added{ false };
PrinterWebViewPanel* m_printer_webview{ nullptr };
bool m_printer_webview_added{ false };
@ -127,6 +127,9 @@ class MainFrame : public DPIFrame
bool can_delete_all() const;
bool can_reslice() const;
void add_connect_webview_tab();
void remove_connect_webview_tab();
// MenuBar items changeable in respect to printer technology
enum MenuItems
{ // FFF SLA
@ -217,8 +220,10 @@ public:
void add_to_recent_projects(const wxString& filename);
void technology_changed();
void add_connect_webview_tab();
void remove_connect_webview_tab();
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();
void show_connect_tab(const wxString &url);
void add_printables_webview_tab();

View File

@ -915,13 +915,16 @@ 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.");
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->remove_connect_webview_tab();
this->main_frame->remove_printables_webview_tab();
this->main_frame->on_account_logout();
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
sidebar->update_printer_presets_combobox();
@ -938,16 +941,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
std::string who = user_account->get_username();
std::string username;
if (user_account->on_user_id_success(evt.data, username)) {
// Do not show notification on refresh.
if (who != username) {
// show notification only on login (not refresh).
std::string text = format(_u8L("Logged to Prusa Account as %1%."), username);
// login notification
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
// show connect tab
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::ImportantNotificationLevel, text);
this->main_frame->on_account_login(user_account->get_access_token());
} else {
// refresh do different operations than on_account_login
this->main_frame->on_account_did_refresh(user_account->get_access_token());
}
this->main_frame->add_connect_webview_tab();
this->main_frame->add_printables_webview_tab();
// Update User name in TopBar
this->main_frame->refresh_account_menu();
wxGetApp().update_wizard_login_page();
@ -960,8 +966,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
user_account->clear();
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account."));
this->main_frame->remove_connect_webview_tab();
this->main_frame->remove_printables_webview_tab();
this->main_frame->on_account_logout();
// Update User name in TopBar
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
@ -974,8 +979,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
user_account->clear();
this->notification_manager->close_notification_of_type(NotificationType::UserAccountID);
this->notification_manager->push_notification(NotificationType::UserAccountID, NotificationManager::NotificationLevel::WarningNotificationLevel, _u8L("Failed to connect to Prusa Account."));
this->main_frame->remove_connect_webview_tab();
this->main_frame->remove_printables_webview_tab();
this->main_frame->on_account_logout();
// Update User name in TopBar
this->main_frame->refresh_account_menu(true);
// Update sidebar printer status
@ -1036,7 +1040,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_UA_REFRESH_TIME, [this](UserAccountTimeEvent& evt) {
this->user_account->set_refresh_time(evt.data);
});
});
this->q->Bind(EVT_UA_ENQUEUED_REFRESH, [this](SimpleEvent& evt) {
this->main_frame->on_account_will_refresh();
});
}
wxGetApp().other_instance_message_handler()->init(this->q);

View File

@ -31,6 +31,7 @@ wxDEFINE_EVENT(EVT_UA_FAIL, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_RESET, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent);
wxDEFINE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent);
wxDEFINE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent);
void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const
{
@ -227,6 +228,7 @@ void UserAccountSession::enqueue_test_with_refresh()
void UserAccountSession::enqueue_refresh(const std::string& body)
{
wxQueueEvent(p_evt_handler, new SimpleEvent(EVT_UA_ENQUEUED_REFRESH));
std::string post_fields;
{
std::lock_guard<std::mutex> lock(m_credentials_mutex);

View File

@ -30,6 +30,7 @@ wxDECLARE_EVENT(EVT_UA_FAIL, UserAccountFailEvent); // Soft fail - clears only a
wxDECLARE_EVENT(EVT_UA_RESET, UserAccountFailEvent); // Hard fail - clears all
wxDECLARE_EVENT(EVT_UA_PRUSACONNECT_PRINTER_DATA_FAIL, UserAccountFailEvent); // Failed to get data for printer to select, soft fail, action does not repeat
wxDECLARE_EVENT(EVT_UA_REFRESH_TIME, UserAccountTimeEvent);
wxDECLARE_EVENT(EVT_UA_ENQUEUED_REFRESH, SimpleEvent);
typedef std::function<void(const std::string& body)> UserActionSuccessFn;
typedef std::function<void(const std::string& body)> UserActionFailFn;

View File

@ -1,6 +1,9 @@
#include "WebView.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/format.hpp"
#include "libslic3r/Platform.hpp"
#include <wx/uri.h>
#include <wx/webview.h>
@ -21,9 +24,9 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const
if (webView) {
wxString correct_url = url.empty() ? wxString("") : wxURI(url).BuildURI();
wxString user_agent = Slic3r::GUI::format_wxstr("%1%/%2% (%3%)",SLIC3R_APP_FULL_NAME, SLIC3R_VERSION, Slic3r::platform_to_string(Slic3r::platform()));
#ifdef __WIN32__
webView->SetUserAgent(SLIC3R_APP_FULL_NAME);
webView->SetUserAgent(user_agent);
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
//We register the wxfs:// protocol for testing purposes
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewArchiveHandler("wxfs")));
@ -35,7 +38,7 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const
// And the memory: file system
//webView->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new wxWebViewFSHandler("memory")));
webView->Create(parent, wxID_ANY, correct_url, wxDefaultPosition, wxDefaultSize);
webView->SetUserAgent(wxString::FromUTF8(SLIC3R_APP_FULL_NAME));
webView->SetUserAgent(user_agent);
#endif
#ifndef __WIN32__
Slic3r::GUI::wxGetApp().CallAfter([message_handlers, webView] {
@ -57,5 +60,3 @@ wxWebView* WebView::CreateWebView(wxWindow * parent, const wxString& url, const
}
return webView;
}

View File

@ -22,11 +22,6 @@
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
// if set to 1 the fetch() JS function gets override to include JWT in authorization header
// if set to 0, the /slicer/login is invoked from WebKit (passing JWT token only to this request)
// to set authorization cookie for all WebKit requests to Connect
#define AUTH_VIA_FETCH_OVERRIDE 0
wxDEFINE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent);
namespace pt = boost::property_tree;
@ -34,904 +29,6 @@ namespace pt = boost::property_tree;
namespace Slic3r {
namespace GUI {
WebViewPanel::~WebViewPanel()
{
SetEvtHandlerEnabled(false);
#ifdef DEBUG_URL_PANEL
delete m_tools_menu;
#endif
}
void WebViewPanel::load_url(const wxString& url)
{
if (!m_browser)
return;
this->on_page_will_load();
this->Show();
this->Raise();
#ifdef DEBUG_URL_PANEL
m_url->SetLabelText(url);
#endif
m_browser->LoadURL(url);
m_browser->SetFocus();
}
WebViewPanel::WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector<std::string>& message_handler_names, const std::string& loading_html/* = "loading"*/)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
, m_default_url (default_url)
, m_loading_html(loading_html)
, m_script_message_hadler_names(message_handler_names)
{
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
#ifdef DEBUG_URL_PANEL
// Create the button
bSizer_toolbar = new wxBoxSizer(wxHORIZONTAL);
m_button_back = new wxButton(this, wxID_ANY, wxT("Back"), wxDefaultPosition, wxDefaultSize, 0);
m_button_back->Enable(false);
bSizer_toolbar->Add(m_button_back, 0, wxALL, 5);
m_button_forward = new wxButton(this, wxID_ANY, wxT("Forward"), wxDefaultPosition, wxDefaultSize, 0);
m_button_forward->Enable(false);
bSizer_toolbar->Add(m_button_forward, 0, wxALL, 5);
m_button_stop = new wxButton(this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0);
bSizer_toolbar->Add(m_button_stop, 0, wxALL, 5);
m_button_reload = new wxButton(this, wxID_ANY, wxT("Reload"), wxDefaultPosition, wxDefaultSize, 0);
bSizer_toolbar->Add(m_button_reload, 0, wxALL, 5);
m_url = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
bSizer_toolbar->Add(m_url, 1, wxALL | wxEXPAND, 5);
m_button_tools = new wxButton(this, wxID_ANY, wxT("Tools"), wxDefaultPosition, wxDefaultSize, 0);
bSizer_toolbar->Add(m_button_tools, 0, wxALL, 5);
// Create panel for find toolbar.
wxPanel* panel = new wxPanel(this);
topsizer->Add(bSizer_toolbar, 0, wxEXPAND, 0);
topsizer->Add(panel, wxSizerFlags().Expand());
// Create sizer for panel.
wxBoxSizer* panel_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(panel_sizer);
// Create the info panel
m_info = new wxInfoBar(this);
topsizer->Add(m_info, wxSizerFlags().Expand());
#endif
SetSizer(topsizer);
// Create the webview
m_browser = WebView::CreateWebView(this, /*m_default_url*/ GUI::format_wxstr("file://%1%/web/%2%.html", boost::filesystem::path(resources_dir()).generic_string(), m_loading_html), m_script_message_hadler_names);
if (Utils::ServiceConfig::instance().webdev_enabled()) {
m_browser->EnableContextMenu();
m_browser->EnableAccessToDevTools();
}
if (!m_browser) {
wxStaticText* text = new wxStaticText(this, wxID_ANY, _L("Failed to load a web browser."));
topsizer->Add(text, 0, wxALIGN_LEFT | wxBOTTOM, 10);
return;
}
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
#ifdef DEBUG_URL_PANEL
// Create the Tools menu
m_tools_menu = new wxMenu();
wxMenuItem* viewSource = m_tools_menu->Append(wxID_ANY, "View Source");
wxMenuItem* viewText = m_tools_menu->Append(wxID_ANY, "View Text");
m_tools_menu->AppendSeparator();
wxMenu* script_menu = new wxMenu;
m_script_custom = script_menu->Append(wxID_ANY, "Custom script");
m_tools_menu->AppendSubMenu(script_menu, "Run Script");
wxMenuItem* addUserScript = m_tools_menu->Append(wxID_ANY, "Add user script");
wxMenuItem* setCustomUserAgent = m_tools_menu->Append(wxID_ANY, "Set custom user agent");
m_context_menu = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Context Menu");
m_dev_tools = m_tools_menu->AppendCheckItem(wxID_ANY, "Enable Dev Tools");
#endif
Bind(wxEVT_SHOW, &WebViewPanel::on_show, this);
// Connect the webview events
Bind(wxEVT_WEBVIEW_ERROR, &WebViewPanel::on_error, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &WebViewPanel::on_script_message, this, m_browser->GetId());
Bind(wxEVT_WEBVIEW_NAVIGATING, &WebViewPanel::on_navigation_request, this, m_browser->GetId());
#ifdef DEBUG_URL_PANEL
// Connect the button events
Bind(wxEVT_BUTTON, &WebViewPanel::on_back_button, this, m_button_back->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_forward_button, this, m_button_forward->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_stop_button, this, m_button_stop->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_reload_button, this, m_button_reload->GetId());
Bind(wxEVT_BUTTON, &WebViewPanel::on_tools_clicked, this, m_button_tools->GetId());
Bind(wxEVT_TEXT_ENTER, &WebViewPanel::on_url, this, m_url->GetId());
// Connect the menu events
Bind(wxEVT_MENU, &WebViewPanel::on_view_source_request, this, viewSource->GetId());
Bind(wxEVT_MENU, &WebViewPanel::on_view_text_request, this, viewText->GetId());
Bind(wxEVT_MENU, &WebViewPanel::On_enable_context_menu, this, m_context_menu->GetId());
Bind(wxEVT_MENU, &WebViewPanel::On_enable_dev_tools, this, m_dev_tools->GetId());
Bind(wxEVT_MENU, &WebViewPanel::on_run_script_custom, this, m_script_custom->GetId());
Bind(wxEVT_MENU, &WebViewPanel::on_add_user_script, this, addUserScript->GetId());
#endif
//Connect the idle events
Bind(wxEVT_IDLE, &WebViewPanel::on_idle, this);
}
void WebViewPanel::load_default_url_delayed()
{
assert(!m_default_url.empty());
m_load_default_url = true;
}
void WebViewPanel::load_error_page()
{
if (!m_browser)
return;
m_browser->Stop();
m_load_error_page = true;
}
void WebViewPanel::on_show(wxShowEvent& evt)
{
m_shown = evt.IsShown();
if (evt.IsShown() && m_load_default_url) {
m_load_default_url = false;
load_url(m_default_url);
}
}
void WebViewPanel::on_idle(wxIdleEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
if (m_browser->IsBusy()) {
wxSetCursor(wxCURSOR_ARROWWAIT);
} else {
wxSetCursor(wxNullCursor);
if (m_shown && m_load_error_page) {
m_load_error_page = false;
if (m_load_default_url_on_next_error) {
m_load_default_url_on_next_error = false;
load_url(m_default_url);
} else {
load_url(GUI::format_wxstr("file://%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string()));
}
}
}
#ifdef DEBUG_URL_PANEL
m_button_stop->Enable(m_browser->IsBusy());
#endif
}
/**
* Callback invoked when user entered an URL and pressed enter
*/
void WebViewPanel::on_url(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
#ifdef DEBUG_URL_PANEL
m_browser->LoadURL(m_url->GetValue());
m_browser->SetFocus();
#endif
}
/**
* Callback invoked when user pressed the "back" button
*/
void WebViewPanel::on_back_button(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->GoBack();
}
/**
* Callback invoked when user pressed the "forward" button
*/
void WebViewPanel::on_forward_button(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->GoForward();
}
/**
* Callback invoked when user pressed the "stop" button
*/
void WebViewPanel::on_stop_button(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->Stop();
}
/**
* Callback invoked when user pressed the "reload" button
*/
void WebViewPanel::on_reload_button(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->Reload();
}
void WebViewPanel::on_script_message(wxWebViewEvent& evt)
{
}
void WebViewPanel::on_navigation_request(wxWebViewEvent &evt)
{
}
void WebViewPanel::on_page_will_load()
{
}
/**
* Invoked when user selects the "View Source" menu item
*/
void WebViewPanel::on_view_source_request(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
SourceViewDialog dlg(this, m_browser->GetPageSource());
dlg.ShowModal();
}
/**
* Invoked when user selects the "View Text" menu item
*/
void WebViewPanel::on_view_text_request(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
wxDialog textViewDialog(this, wxID_ANY, "Page Text",
wxDefaultPosition, wxSize(700, 500),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, m_browser->GetPageText(),
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE |
wxTE_RICH |
wxTE_READONLY);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(text, 1, wxEXPAND);
SetSizer(sizer);
textViewDialog.ShowModal();
}
/**
* Invoked when user selects the "Menu" item
*/
void WebViewPanel::on_tools_clicked(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
#ifdef DEBUG_URL_PANEL
m_context_menu->Check(m_browser->IsContextMenuEnabled());
m_dev_tools->Check(m_browser->IsAccessToDevToolsEnabled());
wxPoint position = ScreenToClient(wxGetMousePosition());
PopupMenu(m_tools_menu, position.x, position.y);
#endif
}
void WebViewPanel::run_script(const wxString& javascript)
{
if (!m_browser || !m_shown)
return;
// Remember the script we run in any case, so the next time the user opens
// the "Run Script" dialog box, it is shown there for convenient updating.
m_javascript = javascript;
BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n";
m_browser->RunScriptAsync(javascript);
}
void WebViewPanel::on_run_script_custom(wxCommandEvent& WXUNUSED(evt))
{
wxTextEntryDialog dialog
(
this,
"Please enter JavaScript code to execute",
wxGetTextFromUserPromptStr,
m_javascript,
wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE
);
if (dialog.ShowModal() != wxID_OK)
return;
run_script(dialog.GetValue());
}
void WebViewPanel::on_add_user_script(wxCommandEvent& WXUNUSED(evt))
{
wxString userScript = "window.wx_test_var = 'wxWidgets webview sample';";
wxTextEntryDialog dialog
(
this,
"Enter the JavaScript code to run as the initialization script that runs before any script in the HTML document.",
wxGetTextFromUserPromptStr,
userScript,
wxOK | wxCANCEL | wxCENTRE | wxTE_MULTILINE
);
if (dialog.ShowModal() != wxID_OK)
return;
const wxString& javascript = dialog.GetValue();
BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n";
if (!m_browser->AddUserScript(javascript))
wxLogError("Could not add user script");
}
void WebViewPanel::on_set_custom_user_agent(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
wxString customUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.1 Mobile/15E148 Safari/604.1";
wxTextEntryDialog dialog
(
this,
"Enter the custom user agent string you would like to use.",
wxGetTextFromUserPromptStr,
customUserAgent,
wxOK | wxCANCEL | wxCENTRE
);
if (dialog.ShowModal() != wxID_OK)
return;
if (!m_browser->SetUserAgent(customUserAgent))
wxLogError("Could not set custom user agent");
}
void WebViewPanel::on_clear_selection(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->ClearSelection();
}
void WebViewPanel::on_delete_selection(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->DeleteSelection();
}
void WebViewPanel::on_select_all(wxCommandEvent& WXUNUSED(evt))
{
if (!m_browser)
return;
m_browser->SelectAll();
}
void WebViewPanel::On_enable_context_menu(wxCommandEvent& evt)
{
if (!m_browser)
return;
m_browser->EnableContextMenu(evt.IsChecked());
}
void WebViewPanel::On_enable_dev_tools(wxCommandEvent& evt)
{
if (!m_browser)
return;
m_browser->EnableAccessToDevTools(evt.IsChecked());
}
/**
* Callback invoked when a loading error occurs
*/
void WebViewPanel::on_error(wxWebViewEvent& evt)
{
#define WX_ERROR_CASE(type) \
case type: \
category = #type; \
break;
wxString category;
switch (evt.GetInt())
{
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CONNECTION);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_CERTIFICATE);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_AUTH);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_SECURITY);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_NOT_FOUND);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_REQUEST);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_USER_CANCELLED);
WX_ERROR_CASE(wxWEBVIEW_NAV_ERR_OTHER);
}
BOOST_LOG_TRIVIAL(error) << "WebViewPanel error: " << category;
load_error_page();
#ifdef DEBUG_URL_PANEL
m_info->ShowMessage(wxString("An error occurred loading ") + evt.GetURL() + "\n" +
"'" + category + "'", wxICON_ERROR);
#endif
}
void WebViewPanel::sys_color_changed()
{
#ifdef _WIN32
wxGetApp().UpdateDarkUI(this);
#endif
}
SourceViewDialog::SourceViewDialog(wxWindow* parent, wxString source) :
wxDialog(parent, wxID_ANY, "Source Code",
wxDefaultPosition, wxSize(700,500),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
wxTextCtrl* text = new wxTextCtrl(this, wxID_ANY, source,
wxDefaultPosition, wxDefaultSize,
wxTE_MULTILINE |
wxTE_RICH |
wxTE_READONLY);
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(text, 1, wxEXPAND);
SetSizer(sizer);
}
ConnectRequestHandler::ConnectRequestHandler()
{
m_actions["REQUEST_LOGIN"] = std::bind(&ConnectRequestHandler::on_connect_action_request_login, this, std::placeholders::_1);
m_actions["REQUEST_CONFIG"] = std::bind(&ConnectRequestHandler::on_connect_action_request_config, this, std::placeholders::_1);
m_actions["WEBAPP_READY"] = std::bind(&ConnectRequestHandler::on_connect_action_webapp_ready,this, std::placeholders::_1);
m_actions["SELECT_PRINTER"] = std::bind(&ConnectRequestHandler::on_connect_action_select_printer, this, std::placeholders::_1);
m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1);
m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1);
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);
}
ConnectRequestHandler::~ConnectRequestHandler()
{
}
void ConnectRequestHandler::handle_message(const std::string& message)
{
// read msg and choose action
/*
v0:
{"type":"request","detail":{"action":"requestAccessToken"}}
v1:
{"action":"REQUEST_ACCESS_TOKEN"}
*/
std::string action_string;
try {
std::stringstream ss(message);
pt::ptree ptree;
pt::read_json(ss, ptree);
// v1:
if (const auto action = ptree.get_optional<std::string>("action"); action) {
action_string = *action;
}
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
if (action_string.empty()) {
BOOST_LOG_TRIVIAL(error) << "Recieved invalid message from _prusaConnect (missing action). Message: " << message;
return;
}
assert(m_actions.find(action_string) != m_actions.end()); // this assert means there is a action that has no handling.
if (m_actions.find(action_string) != m_actions.end()) {
m_actions[action_string](message);
}
}
void ConnectRequestHandler::on_connect_action_error(const std::string &message_data)
{
BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data;
}
void ConnectRequestHandler::resend_config()
{
on_connect_action_request_config({});
}
void ConnectRequestHandler::on_connect_action_log(const std::string& message_data)
{
BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data;
}
void ConnectRequestHandler::on_connect_action_request_login(const std::string &message_data)
{}
void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data)
{
/*
accessToken?: string;
clientVersion?: string;
colorMode?: "LIGHT" | "DARK";
language?: ConnectLanguage;
sessionId?: string;
*/
const std::string token = wxGetApp().plater()->get_user_account()->get_access_token();
//const std::string sesh = wxGetApp().plater()->get_user_account()->get_shared_session_key();
const std::string dark_mode = wxGetApp().dark_mode() ? "DARK" : "LIGHT";
wxString language = GUI::wxGetApp().current_language_code();
language = language.SubString(0, 1);
const std::string init_options = GUI::format("{\"accessToken\": \"%4%\",\"clientVersion\": \"%1%\", \"colorMode\": \"%2%\", \"language\": \"%3%\"}", SLIC3R_VERSION, dark_mode, language, token );
wxString script = GUI::format_wxstr("window._prusaConnect_v1.init(%1%)", init_options);
run_script_bridge(script);
}
void ConnectRequestHandler::on_connect_action_request_open_in_browser(const std::string& message_data)
{
try {
std::stringstream ss(message_data);
pt::ptree ptree;
pt::read_json(ss, ptree);
if (const auto url = ptree.get_optional<std::string>("url"); url) {
wxGetApp().open_browser_with_warning_dialog(GUI::from_u8(*url));
}
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(error) << "Could not parse _prusaConnect message. " << e.what();
return;
}
}
ConnectWebViewPanel::ConnectWebViewPanel(wxWindow* parent)
: WebViewPanel(parent, GUI::from_u8(Utils::ServiceConfig::instance().connect_url()), { "_prusaSlicer" }, "connect_loading")
{
// m_browser->RegisterHandler(wxSharedPtr<wxWebViewHandler>(new WebViewHandler("https")));
auto* plater = wxGetApp().plater();
plater->Bind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this);
plater->Bind(EVT_UA_LOGGEDOUT, &ConnectWebViewPanel::on_user_logged_out, this);
}
ConnectWebViewPanel::~ConnectWebViewPanel()
{
m_browser->Unbind(EVT_UA_ID_USER_SUCCESS, &ConnectWebViewPanel::on_user_token, this);
}
wxString ConnectWebViewPanel::get_login_script(bool refresh)
{
Plater* plater = wxGetApp().plater();
const std::string& access_token = plater->get_user_account()->get_access_token();
assert(!access_token.empty());
auto javascript = wxString::Format(
#if AUTH_VIA_FETCH_OVERRIDE
refresh
?
"window.__access_token = '%s';window.__access_token_version = (window.__access_token_version || 0) + 1;console.log('Updated Auth token', window.__access_token);"
:
/*
* Notes:
* - The fetch() function has two distinct prototypes (i.e. input args):
* 1. fetch(url: string, options: object | undefined)
* 2. fetch(req: Request, options: object | undefined)
* - For some reason I can't explain the headers can be extended only via Request object
* i.e. the fetch prototype (2). So we need to convert (1) call into (2) before
*
*/
R"(
if (window.__fetch === undefined) {
window.__fetch = fetch;
window.fetch = function(req, opts = {}) {
if (typeof req === 'string') {
req = new Request(req, opts);
opts = {};
}
if (window.__access_token && (req.url[0] == '/' || req.url.indexOf('prusa3d.com') > 0)) {
req.headers.set('Authorization', 'Bearer ' + window.__access_token);
console.log('Header updated: ', req.headers.get('Authorization'));
console.log('AT Version: ', __access_token_version);
}
//console.log('Injected fetch used', req, opts);
return __fetch(req, opts);
};
}
window.__access_token = '%s';
window.__access_token_version = 0;
)",
#else
refresh
?
R"(
if (location.protocol === 'https:') {
if (window._prusaSlicer_initLogin !== undefined) {
console.log('Init login');
if (window._prusaSlicer !== undefined)
_prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login'});
_prusaSlicer_initLogin('%s');
} else {
console.log('Refreshing login skipped as no _prusaSlicer_login defined (yet?)');
if (window._prusaSlicer === undefined) {
console.log('Message handler _prusaSlicer not defined yet');
} else {
_prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login skipped as no _prusaSlicer_initLogin defined (yet?)'});
}
}
}
)"
:
R"(
function _prusaSlicer_log(msg) {
console.log(msg);
if (window._prusaSlicer !== undefined)
_prusaSlicer.postMessage({action: 'LOG', message: msg});
}
function _prusaSlicer_errorHandler(err) {
const msg = {
action: 'ERROR',
error: typeof(err) === 'string' ? err : JSON.stringify(err),
critical: false
};
console.error('Login error occurred', msg);
window._prusaSlicer.postMessage(msg);
};
function _prusaSlicer_delay(ms) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
async function _prusaSlicer_initLogin(token) {
const parts = token.split('.');
const claims = JSON.parse(atob(parts[1]));
const now = new Date().getTime() / 1000;
if (claims.exp <= now) {
_prusaSlicer_log('Skipping initLogin as token is expired');
return;
}
let retry = false;
let backoff = 1000;
const maxBackoff = 64000;
do {
let error = false;
try {
_prusaSlicer_log('Slicer Login request ' + token.substring(token.length - 8));
let resp = await fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer ' + token}});
let body = await resp.text();
_prusaSlicer_log('Slicer Login resp ' + resp.status + ' (' + token.substring(token.length - 8) + ') body: ' + body);
if (resp.status >= 500 || resp.status == 408) {
retry = true;
} else {
retry = false;
if (resp.status >= 400)
_prusaSlicer_errorHandler({status: resp.status, body});
}
} catch (e) {
_prusaSlicer_log('Slicer Login failed: ' + e.toString());
console.error('Slicer Login failed', e.toString());
retry = true;
}
if (retry) {
await _prusaSlicer_delay(backoff + 1000 * Math.random());
if (backoff < maxBackoff) {
backoff *= 2;
}
}
} while (retry);
}
if (location.protocol === 'https:' && window._prusaSlicer) {
_prusaSlicer_log('Requesting login');
_prusaSlicer.postMessage({action: 'REQUEST_LOGIN'});
}
)",
#endif
access_token
);
return javascript;
}
wxString ConnectWebViewPanel::get_logout_script()
{
return "sessionStorage.removeItem('_slicer_token');";
}
void ConnectWebViewPanel::on_page_will_load()
{
auto javascript = get_login_script(false);
BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n";
m_browser->AddUserScript(javascript);
}
void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e)
{
e.Skip();
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();
}
void ConnectWebViewPanel::on_user_logged_out(UserAccountSuccessEvent& e)
{
e.Skip();
// clear token from session storage
m_browser->RunScriptAsync(get_logout_script());
}
void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt)
{
BOOST_LOG_TRIVIAL(debug) << "received message from Prusa Connect FE: " << evt.GetString();
handle_message(into_u8(evt.GetString()));
}
void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt)
{
#ifdef DEBUG_URL_PANEL
m_url->SetValue(evt.GetURL());
#endif
BOOST_LOG_TRIVIAL(debug) << "Navigation requested to: " << into_u8(evt.GetURL());
if (evt.GetURL() == m_default_url) {
m_reached_default_url = true;
return;
}
if (evt.GetURL() == (GUI::format_wxstr("file:///%1%/web/connection_failed.html", boost::filesystem::path(resources_dir()).generic_string()))) {
return;
}
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();
}
}
void ConnectWebViewPanel::on_connect_action_error(const std::string &message_data)
{
ConnectRequestHandler::on_connect_action_error(message_data);
// TODO: make this more user friendly (and make sure only once opened if multiple errors happen)
// MessageDialog dialog(
// this,
// GUI::format_wxstr(_L("WebKit Runtime Error encountered:\n\n%s"), message_data),
// "WebKit Runtime Error",
// wxOK
// );
// dialog.ShowModal();
}
void ConnectWebViewPanel::logout()
{
wxString script = L"window._prusaConnect_v1.logout()";
run_script(script);
Plater* plater = wxGetApp().plater();
auto javascript = wxString::Format(
R"(
console.log('Preparing logout');
window.fetch('/slicer/logout', {method: 'POST', headers: {Authorization: 'Bearer %s'}})
.then(function (resp){
console.log('Logout resp', resp);
resp.text().then(function (json) { console.log('Logout resp body', json) });
});
)",
plater->get_user_account()->get_access_token()
);
BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n";
m_browser->RunScript(javascript);
}
void ConnectWebViewPanel::sys_color_changed()
{
resend_config();
}
void ConnectWebViewPanel::on_connect_action_request_login(const std::string &message_data)
{
run_script_bridge(get_login_script(true));
}
void ConnectWebViewPanel::on_connect_action_select_printer(const std::string& message_data)
{
assert(!message_data.empty());
wxGetApp().handle_connect_request_printer_select(message_data);
}
void ConnectWebViewPanel::on_connect_action_print(const std::string& message_data)
{
// PRINT request is not defined for ConnectWebViewPanel
assert(true);
}
PrinterWebViewPanel::PrinterWebViewPanel(wxWindow* parent, const wxString& default_url)
: WebViewPanel(parent, default_url, {})
{
if (!m_browser)
return;
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebViewPanel::on_loaded, this);
#ifndef NDEBUG
m_browser->EnableAccessToDevTools();
m_browser->EnableContextMenu();
#endif
}
void PrinterWebViewPanel::on_loaded(wxWebViewEvent& evt)
{
if (evt.GetURL().IsEmpty())
return;
if (!m_api_key.empty()) {
send_api_key();
} else if (!m_usr.empty() && !m_psk.empty()) {
send_credentials();
}
}
void PrinterWebViewPanel::send_api_key()
{
if (!m_browser || m_api_key_sent)
return;
m_api_key_sent = true;
wxString key = from_u8(m_api_key);
wxString script = wxString::Format(R"(
// Check if window.fetch exists before overriding
if (window.originalFetch === undefined) {
console.log('Patching fetch with API key');
window.originalFetch = window.fetch;
window.fetch = function(input, init = {}) {
init.headers = init.headers || {};
init.headers['X-Api-Key'] = sessionStorage.getItem('apiKey');
console.log('Patched fetch', input, init);
return window.originalFetch(input, init);
};
}
sessionStorage.setItem('authType', 'ApiKey');
sessionStorage.setItem('apiKey', '%s');
)",
key);
m_browser->RemoveAllUserScripts();
BOOST_LOG_TRIVIAL(debug) << "RunScript " << script << "\n";
m_browser->AddUserScript(script);
m_browser->Reload();
remove_webview_credentials(m_browser);
}
void PrinterWebViewPanel::send_credentials()
{
if (!m_browser || m_api_key_sent)
return;
m_browser->RemoveAllUserScripts();
m_browser->AddUserScript("sessionStorage.removeItem('authType'); sessionStorage.removeItem('apiKey'); console.log('Session Storage cleared');");
m_browser->Reload();
m_api_key_sent = true;
setup_webview_with_credentials(m_browser, m_usr, m_psk);
}
void PrinterWebViewPanel::sys_color_changed()
{
}
WebViewDialog::WebViewDialog(wxWindow* parent, const wxString& url, const wxString& dialog_name, const wxSize& size, const std::vector<std::string>& message_handler_names, const std::string& loading_html/* = "loading"*/)
: DPIDialog(parent, wxID_ANY, dialog_name, wxDefaultPosition, size, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
, m_loading_html(loading_html)

View File

@ -1,14 +1,13 @@
#ifndef slic3r_WebViewDialog_hpp_
#define slic3r_WebViewDialog_hpp_
//#define DEBUG_URL_PANEL
#include <map>
#include <wx/wx.h>
#include <wx/event.h>
#include "GUI_Utils.hpp"
#include "UserAccountSession.hpp"
#include "ConnectRequestHandler.hpp"
#ifdef DEBUG_URL_PANEL
#include <wx/infobar.h>
@ -22,91 +21,6 @@ wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent);
namespace Slic3r {
namespace GUI {
class WebViewPanel : public wxPanel
{
public:
WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector<std::string>& message_handler_names, const std::string& loading_html = "loading");
virtual ~WebViewPanel();
void load_url(const wxString& url);
void load_default_url_delayed();
void load_error_page();
void on_show(wxShowEvent& evt);
virtual void on_script_message(wxWebViewEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_url(wxCommandEvent& evt);
void on_back_button(wxCommandEvent& evt);
void on_forward_button(wxCommandEvent& evt);
void on_stop_button(wxCommandEvent& evt);
void on_reload_button(wxCommandEvent& evt);
void on_view_source_request(wxCommandEvent& evt);
void on_view_text_request(wxCommandEvent& evt);
void on_tools_clicked(wxCommandEvent& evt);
void on_error(wxWebViewEvent& evt);
void run_script(const wxString& javascript);
void on_run_script_custom(wxCommandEvent& evt);
void on_add_user_script(wxCommandEvent& evt);
void on_set_custom_user_agent(wxCommandEvent& evt);
void on_clear_selection(wxCommandEvent& evt);
void on_delete_selection(wxCommandEvent& evt);
void on_select_all(wxCommandEvent& evt);
void On_enable_context_menu(wxCommandEvent& evt);
void On_enable_dev_tools(wxCommandEvent& evt);
virtual void on_navigation_request(wxWebViewEvent &evt);
wxString get_default_url() const { return m_default_url; }
void set_default_url(const wxString& url) { m_default_url = url; }
virtual void sys_color_changed();
void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; }
protected:
virtual void on_page_will_load();
wxWebView* m_browser { nullptr };
bool m_load_default_url { false };
#ifdef DEBUG_URL_PANEL
wxBoxSizer *bSizer_toolbar;
wxButton * m_button_back;
wxButton * m_button_forward;
wxButton * m_button_stop;
wxButton * m_button_reload;
wxTextCtrl *m_url;
wxButton * m_button_tools;
wxMenu* m_tools_menu;
wxMenuItem* m_script_custom;
wxInfoBar *m_info;
wxStaticText* m_info_text;
wxMenuItem* m_context_menu;
wxMenuItem* m_dev_tools;
#endif
// Last executed JavaScript snippet, for convenience.
wxString m_javascript;
wxString m_response_js;
wxString m_default_url;
std::string m_loading_html;
//DECLARE_EVENT_TABLE()
bool m_load_error_page { false };
bool m_shown { false };
bool m_load_default_url_on_next_error { false };
std::vector<std::string> m_script_message_hadler_names;
};
class WebViewDialog : public DPIDialog
{
public:
@ -176,87 +90,6 @@ protected:
std::vector<std::string> m_script_message_hadler_names;
};
class ConnectRequestHandler
{
public:
ConnectRequestHandler();
~ConnectRequestHandler();
void handle_message(const std::string& message);
void resend_config();
protected:
// action callbacs stored in m_actions
virtual void on_connect_action_log(const std::string& message_data);
virtual void on_connect_action_error(const std::string& message_data);
virtual void on_connect_action_request_login(const std::string& message_data);
virtual void on_connect_action_request_config(const std::string& message_data);
virtual void on_connect_action_request_open_in_browser(const std::string& message_data);
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 run_script_bridge(const wxString &script) = 0;
std::map<std::string, std::function<void(const std::string&)>> m_actions;
};
class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
{
public:
ConnectWebViewPanel(wxWindow* parent);
~ConnectWebViewPanel() override;
void on_script_message(wxWebViewEvent& evt) override;
void logout();
void sys_color_changed() override;
void on_navigation_request(wxWebViewEvent &evt) override;
protected:
void on_connect_action_request_login(const std::string &message_data) 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 run_script_bridge(const wxString& script) override {run_script(script); }
void on_page_will_load() override;
void on_connect_action_error(const std::string &message_data) override;
private:
static wxString get_login_script(bool refresh);
static wxString get_logout_script();
void on_user_token(UserAccountSuccessEvent& e);
void on_user_logged_out(UserAccountSuccessEvent& e);
bool m_reached_default_url {false};
};
class PrinterWebViewPanel : public WebViewPanel
{
public:
PrinterWebViewPanel(wxWindow* parent, const wxString& default_url);
void on_loaded(wxWebViewEvent& evt);
void send_api_key();
void send_credentials();
void set_api_key(const std::string &key)
{
if (m_api_key != key) {
clear();
m_api_key = key;
}
}
void set_credentials(const std::string &usr, const std::string &psk)
{
if (m_usr != usr || m_psk != psk) {
clear();
m_usr = usr;
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;
std::string m_psk;
bool m_api_key_sent {false};
};
class PrinterPickWebViewDialog : public WebViewDialog, public ConnectRequestHandler
{
public:
@ -276,12 +109,6 @@ private:
std::string& m_ret_val;
};
class SourceViewDialog : public wxDialog
{
public:
SourceViewDialog(wxWindow* parent, wxString source);
};
class LoginWebViewDialog : public WebViewDialog
{
public:
@ -297,4 +124,4 @@ private:
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */
#endif /* slic3r_WebViewDialog_hpp_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,195 @@
#ifndef slic3r_WebViewPanel_hpp_
#define slic3r_WebViewPanel_hpp_
#include <map>
#include <wx/wx.h>
#include <wx/event.h>
#include "GUI_Utils.hpp"
#include "UserAccountSession.hpp"
#include "ConnectRequestHandler.hpp"
#ifdef DEBUG_URL_PANEL
#include <wx/infobar.h>
#endif
class wxWebView;
class wxWebViewEvent;
wxDECLARE_EVENT(EVT_OPEN_EXTERNAL_LOGIN, wxCommandEvent);
namespace Slic3r::GUI {
class WebViewPanel : public wxPanel
{
public:
WebViewPanel(wxWindow *parent, const wxString& default_url, const std::vector<std::string>& message_handler_names, const std::string& loading_html = "loading");
virtual ~WebViewPanel();
void load_url(const wxString& url);
void load_default_url_delayed();
void load_error_page();
virtual void on_show(wxShowEvent& evt);
virtual void on_script_message(wxWebViewEvent& evt);
void on_idle(wxIdleEvent& evt);
void on_url(wxCommandEvent& evt);
virtual void on_back_button(wxCommandEvent& evt);
virtual void on_forward_button(wxCommandEvent& evt);
void on_stop_button(wxCommandEvent& evt);
virtual void on_reload_button(wxCommandEvent& evt);
void on_view_source_request(wxCommandEvent& evt);
void on_view_text_request(wxCommandEvent& evt);
void on_tools_clicked(wxCommandEvent& evt);
void on_error(wxWebViewEvent& evt);
void run_script(const wxString& javascript);
void on_run_script_custom(wxCommandEvent& evt);
void on_add_user_script(wxCommandEvent& evt);
void on_set_custom_user_agent(wxCommandEvent& evt);
void on_clear_selection(wxCommandEvent& evt);
void on_delete_selection(wxCommandEvent& evt);
void on_select_all(wxCommandEvent& evt);
void On_enable_context_menu(wxCommandEvent& evt);
void On_enable_dev_tools(wxCommandEvent& evt);
virtual void on_navigation_request(wxWebViewEvent &evt);
wxString get_default_url() const { return m_default_url; }
void set_default_url(const wxString& url) { m_default_url = url; }
virtual void sys_color_changed();
void set_load_default_url_on_next_error(bool val) { m_load_default_url_on_next_error = val; }
protected:
virtual void on_page_will_load();
wxWebView* m_browser { nullptr };
bool m_load_default_url { false };
wxBoxSizer* topsizer;
wxBoxSizer* m_sizer_top;
#ifdef DEBUG_URL_PANEL
wxBoxSizer *bSizer_toolbar;
wxButton * m_button_back;
wxButton * m_button_forward;
wxButton * m_button_stop;
wxButton * m_button_reload;
wxTextCtrl *m_url;
wxButton * m_button_tools;
wxMenu* m_tools_menu;
wxMenuItem* m_script_custom;
wxInfoBar *m_info;
wxStaticText* m_info_text;
wxMenuItem* m_context_menu;
wxMenuItem* m_dev_tools;
#endif
// Last executed JavaScript snippet, for convenience.
wxString m_javascript;
wxString m_response_js;
wxString m_default_url;
bool m_reached_default_url {false};
std::string m_loading_html;
//DECLARE_EVENT_TABLE()
bool m_load_error_page { false };
bool m_shown { false };
bool m_load_default_url_on_next_error { false };
std::vector<std::string> m_script_message_hadler_names;
};
class ConnectWebViewPanel : public WebViewPanel, public ConnectRequestHandler
{
public:
ConnectWebViewPanel(wxWindow* parent);
~ConnectWebViewPanel() override;
void on_script_message(wxWebViewEvent& evt) override;
void logout();
void sys_color_changed() override;
void on_navigation_request(wxWebViewEvent &evt) override;
protected:
void on_connect_action_request_login(const std::string &message_data) 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 run_script_bridge(const wxString& script) override {run_script(script); }
void on_page_will_load() override;
void on_connect_action_error(const std::string &message_data) override;
private:
static wxString get_login_script(bool refresh);
static wxString get_logout_script();
void on_user_token(UserAccountSuccessEvent& e);
void on_user_logged_out(UserAccountSuccessEvent& e);
};
class PrinterWebViewPanel : public WebViewPanel
{
public:
PrinterWebViewPanel(wxWindow* parent, const wxString& default_url);
void on_loaded(wxWebViewEvent& evt);
void send_api_key();
void send_credentials();
void set_api_key(const std::string &key)
{
if (m_api_key != key) {
clear();
m_api_key = key;
}
}
void set_credentials(const std::string &usr, const std::string &psk)
{
if (m_usr != usr || m_psk != psk) {
clear();
m_usr = usr;
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;
std::string m_psk;
bool m_api_key_sent {false};
};
class PrintablesWebViewPanel : public WebViewPanel
{
public:
PrintablesWebViewPanel(wxWindow* parent);
void on_navigation_request(wxWebViewEvent &evt) override;
void on_loaded(wxWebViewEvent& evt);
void on_show(wxShowEvent& evt) override;
void on_script_message(wxWebViewEvent& evt) override;
void sys_color_changed() override;
void logout();
void login(const std::string access_token);
void send_refreshed_token(const std::string access_token);
void send_will_refresh();
private:
void handle_message(const std::string& message);
void on_printables_event_access_token_expired(const std::string& message_data);
void on_printables_event_print_gcode(const std::string& message_data);
void on_reload_event(const std::string& message_data);
void load_default_url();
std::string get_url_lang_theme(const wxString& url);
std::map<std::string, std::function<void(const std::string&)>> m_events;
};
} // namespace Slic3r::GUI
#endif /* slic3r_WebViewPanel_hpp_ */

View File

@ -7,5 +7,8 @@ namespace Slic3r::GUI {
void setup_webview_with_credentials(wxWebView* web_view, const std::string& username, const std::string& password);
void remove_webview_credentials(wxWebView* web_view);
void delete_cookies(wxWebView* web_view, const std::string& url);
void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token);
void remove_request_authorization(wxWebView* web_view);
void load_request(wxWebView* web_view, const std::string& address, const std::string& token);
}

View File

@ -6,6 +6,7 @@
#include "WebViewPlatformUtils.hpp"
#include <boost/log/trivial.hpp>
#include <libsoup/soup.h>
namespace Slic3r::GUI {
@ -60,7 +61,7 @@ void delete_cookie_callback (GObject* source_object, GAsyncResult* result, void*
{
WebKitCookieManager *cookie_manager = WEBKIT_COOKIE_MANAGER(source_object);
GError* err = nullptr;
gboolean b = webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err);
webkit_cookie_manager_delete_cookie_finish(cookie_manager, result, &err);
if (err) {
BOOST_LOG_TRIVIAL(error) << "Error deleting cookies: " << err->message;
g_error_free(err);
@ -107,4 +108,40 @@ void delete_cookies(wxWebView* web_view, const std::string& url)
WebKitCookieManager* cookieManager = webkit_web_context_get_cookie_manager(context);
webkit_cookie_manager_get_cookies(cookieManager, uri, nullptr, (GAsyncReadyCallback)Slic3r::GUI::get_cookie_callback, nullptr);
}
void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token)
{
// unused on Linux
assert(true);
}
void remove_request_authorization(wxWebView* web_view)
{
// unused on Linux
assert(true);
}
void load_request(wxWebView* web_view, const std::string& address, const std::string& token)
{
WebKitWebView* native_backend = static_cast<WebKitWebView *>(web_view->GetNativeBackend());
WebKitURIRequest* request = webkit_uri_request_new(address.c_str());
if(!request)
{
BOOST_LOG_TRIVIAL(error) << "load_request failed: request is nullptr. address: " << address;
return;
}
SoupMessageHeaders* soup_headers = webkit_uri_request_get_http_headers(request);
if (!soup_headers)
{
BOOST_LOG_TRIVIAL(error) << "load_request failed: soup_headers is nullptr.";
return;
}
if (!token.empty())
{
soup_message_headers_append(soup_headers, "Authorization", ("External " + token).c_str());
}
// Load the request in the WebView
webkit_web_view_load_request(native_backend, request);
}
}

View File

@ -155,7 +155,27 @@ void delete_cookies(wxWebView* web_view, const std::string& url)
}
}
}];
}
void add_request_authorization(wxWebView* web_view, const wxString& address, const std::string& token)
{
// unused on MacOS
assert(true);
}
void remove_request_authorization(wxWebView* web_view)
{
// unused on MacOS
assert(true);
}
void load_request(wxWebView* web_view, const std::string& address, const std::string& token)
{
WKWebView* backend = static_cast<WKWebView*>(web_view->GetNativeBackend());
NSString *url_string = [NSString stringWithCString:address.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *token_string = [NSString stringWithCString:token.c_str() encoding:[NSString defaultCStringEncoding]];
NSURL *url = [NSURL URLWithString:url_string];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *auth_value = [NSString stringWithFormat:@"External %@", token_string];
[request setValue:auth_value forHTTPHeaderField:@"Authorization"];
[backend loadRequest:request];
}
}

View File

@ -135,6 +135,7 @@ void delete_cookies(wxWebView* webview, const std::string& url)
std::string domain = cookie.second.get<std::string>("domain");
// Delete cookie by name and domain
wxString name_and_domain = GUI::format_wxstr(L"{\"name\": \"%1%\", \"domain\": \"%2%\"}", name, domain);
BOOST_LOG_TRIVIAL(debug) << "Deleting cookie: " << name_and_domain;
webView2->CallDevToolsProtocolMethod(L"Network.deleteCookies", name_and_domain.c_str(),
Microsoft::WRL::Callback<ICoreWebView2CallDevToolsProtocolMethodCompletedHandler>(
[](HRESULT errorCode, LPCWSTR resultJson) -> HRESULT { return S_OK; }).Get());
@ -145,6 +146,150 @@ void delete_cookies(wxWebView* webview, const std::string& url)
}
static EventRegistrationToken m_webResourceRequestedTokenForImageBlocking = {};
static wxString filter_patern;
namespace {
void RequestHeadersToLog(ICoreWebView2HttpRequestHeaders* requestHeaders)
{
wxCOMPtr<ICoreWebView2HttpHeadersCollectionIterator> iterator;
requestHeaders->GetIterator(&iterator);
BOOL hasCurrent = FALSE;
BOOST_LOG_TRIVIAL(info) <<"Logging request headers:";
while (SUCCEEDED(iterator->get_HasCurrentHeader(&hasCurrent)) && hasCurrent)
{
wchar_t* name = nullptr;
wchar_t* value = nullptr;
iterator->GetCurrentHeader(&name, &value);
BOOST_LOG_TRIVIAL(debug) <<"name: " << name << L", value: " << value;
if (name) {
CoTaskMemFree(name);
}
if (value) {
CoTaskMemFree(value);
}
BOOL hasNext = FALSE;
iterator->MoveNext(&hasNext);
}
}
}
void add_request_authorization(wxWebView* webview, const wxString& address, const std::string& token)
{
// This function adds a filter so when pattern document is being requested, callback is triggered
// Inside add_WebResourceRequested callback, there is a Authorization header added.
// The filter needs to be removed to stop adding the auth header
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
if (!webView2) {
return;
}
wxCOMPtr<ICoreWebView2_2> wv2_2;
HRESULT hr = webView2->QueryInterface(IID_PPV_ARGS(&wv2_2));
if (FAILED(hr)) {
return;
}
filter_patern = address + "/*";
webView2->AddWebResourceRequestedFilter( filter_patern.c_str(), COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT);
if (FAILED(webView2->add_WebResourceRequested(
Microsoft::WRL::Callback<ICoreWebView2WebResourceRequestedEventHandler>(
[token](ICoreWebView2 *sender, ICoreWebView2WebResourceRequestedEventArgs *args) {
// Get the web resource request
wxCOMPtr<ICoreWebView2WebResourceRequest> request;
HRESULT hr = args->get_Request(&request);
if (FAILED(hr))
{
BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to get_Request.";
return S_OK;
}
// Get the request headers
wxCOMPtr<ICoreWebView2HttpRequestHeaders> headers;
hr = request->get_Headers(&headers);
if (FAILED(hr))
{
BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to get_Headers.";
return S_OK;
}
LPWSTR wideUri = nullptr;
request->get_Uri(&wideUri);
std::wstring ws(wideUri);
std::string val = "External " + token;
// Add or modify the Authorization header
hr = headers->SetHeader(L"Authorization", GUI::from_u8(val).c_str());
BOOST_LOG_TRIVIAL(debug) << "add_WebResourceRequested " << ws;
// This function is only needed for debug purpose
RequestHeadersToLog(headers.Get());
return S_OK;
}
).Get(), &m_webResourceRequestedTokenForImageBlocking
))) {
BOOST_LOG_TRIVIAL(error) << "Adding request Authorization: Failed to add callback.";
}
}
void remove_request_authorization(wxWebView* webview)
{
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(webview->GetNativeBackend());
if (!webView2) {
return;
}
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";
}
}
void load_request(wxWebView* web_view, const std::string& address, const std::string& token)
{
// This function should create its own GET request and send it (works on linux)
// For that we would use NavigateWithWebResourceRequest.
// For that we need ICoreWebView2Environment smart pointer.
// Such pointer does exists inside wxWebView edge backend. (wxWebViewEdgeImpl::m_webViewEnvironment)
// But its currently private and not getable. (It wouldn't be such problem to create the getter)
ICoreWebView2 *webView2 = static_cast<ICoreWebView2 *>(web_view->GetNativeBackend());
if (!webView2) {
return;
}
// GetEnviroment does not exists
wxCOMPtr<ICoreWebView2Environment> webViewEnvironment;
//webViewEnvironment = static_cast<ICoreWebView2Environment *>(web_view->GetEnviroment());
if (!webViewEnvironment.Get()) {
return;
}
wxCOMPtr<ICoreWebView2Environment2> webViewEnvironment2;
if (FAILED(webViewEnvironment->QueryInterface(IID_PPV_ARGS(&webViewEnvironment2))))
{
return;
}
wxCOMPtr<ICoreWebView2WebResourceRequest> webResourceRequest;
if (FAILED(webViewEnvironment2->CreateWebResourceRequest(
L"https://www.printables.com/", L"GET", NULL,
L"Content-Type: application/x-www-form-urlencoded", &webResourceRequest)))
{
return;
}
wxCOMPtr<ICoreWebView2_2> wv2_2;
if (FAILED(webView2->QueryInterface(IID_PPV_ARGS(&wv2_2)))) {
return;
}
if (FAILED(wv2_2->NavigateWithWebResourceRequest(webResourceRequest.get())))
{
return;
}
}
} // namespace Slic3r::GUI
#endif // WIN32

View File

@ -24,7 +24,9 @@ ServiceConfig::ServiceConfig()
, m_account_url("https://account.prusa3d.com")
, m_account_client_id("oamhmhZez7opFosnwzElIgE2oGgI2iJORSkw587O")
, m_media_url("https://media.printables.com")
, m_preset_repo_url("https://preset-repo-api.prusa3d.com") {
, m_preset_repo_url("https://preset-repo-api.prusa3d.com")
, m_printables_url("https://www.printables.com")
{
#ifdef SLIC3R_REPO_URL
m_preset_repo_url = SLIC3R_REPO_URL;
#endif
@ -34,6 +36,7 @@ ServiceConfig::ServiceConfig()
update_from_env(m_account_client_id, "PRUSA_ACCOUNT_CLIENT_ID");
update_from_env(m_media_url, "PRUSA_MEDIA_URL", true);
update_from_env(m_preset_repo_url, "PRUSA_PRESET_REPO_URL", true);
update_from_env(m_printables_url, "PRUSA_PRINTABLES_URL", true);
}
ServiceConfig& ServiceConfig::instance()

View File

@ -30,6 +30,8 @@ public:
bool webdev_enabled() const { return m_webdev_enabled; }
void set_webdev_enabled(bool enabled) { m_webdev_enabled = enabled; }
const std::string& printables_url() const { return m_printables_url; }
static ServiceConfig& instance();
private:
std::string m_connect_url;
@ -37,6 +39,7 @@ private:
std::string m_account_client_id;
std::string m_media_url;
std::string m_preset_repo_url;
std::string m_printables_url;
bool m_webdev_enabled{false};
};