From 93efd6497e480933205213946cff076b9b063466 Mon Sep 17 00:00:00 2001 From: yw4z Date: Mon, 8 Jun 2026 17:30:10 +0300 Subject: [PATCH 1/2] Improve incremental compile speed on windows (#12881) Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --- CMakeLists.txt | 9 ++- src/dev-utils/CMakeLists.txt | 33 ++++++--- src/dev-utils/encoding-check.cpp | 116 ++++++++++++++++++++----------- 3 files changed, 108 insertions(+), 50 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 58ade33f18..e24e296ad4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -220,7 +220,9 @@ if (MSVC) # /bigobj (Increase Number of Sections in .Obj file) # error C3859: virtual memory range for PCH exceeded; please recompile with a command line option of '-Zm90' or greater # Generate symbols at every build target, even for the release. - add_compile_options(-bigobj -Zm520 /Zi) + # -Zm520 fixes error C3859 but forces the compiler to pre-allocate that memory for every translation unit regardless + # combining /Zi with /FS frees up a significant amount of memory pressure across all parallel compile jobs and makes /MP faster overall. + add_compile_options(-bigobj /Zi /FS) # Disable STL4007: Many result_type typedefs and all argument_type, first_argument_type, and second_argument_type typedefs are deprecated in C++17. #FIXME Remove this line after eigen library adapts to the new C++17 adaptor rules. add_compile_options(-D_SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING) @@ -231,6 +233,11 @@ if (MSVC) # Disable warnings on comparison of unsigned and signed # C4018: signed/unsigned mismatch add_compile_options(/wd4018) + # Prevent linker restart when TBB (or other deps) built with /GL are linked + # Make your full (clean) build slower because it enables link-time code generation across all translation units. + # helps incremental builds by preventing the double-link restart from TBB + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /LTCG") endif () if (${CMAKE_CXX_COMPILER_ID} STREQUAL "AppleClang" AND ${CMAKE_CXX_COMPILER_VERSION} VERSION_GREATER 15) diff --git a/src/dev-utils/CMakeLists.txt b/src/dev-utils/CMakeLists.txt index 28c261f8a6..e3534a024a 100644 --- a/src/dev-utils/CMakeLists.txt +++ b/src/dev-utils/CMakeLists.txt @@ -28,17 +28,34 @@ function(encoding_check TARGET) # Obtain target source files get_target_property(T_SOURCES ${TARGET} SOURCES) - # Define top-level encoding check target for this ${TARGET} - add_custom_target(encoding-check-${TARGET} - DEPENDS encoding-check ${T_SOURCES} - COMMAND $ ${TARGET} ${T_SOURCES} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + set(STAMP_FILE "${CMAKE_CURRENT_BINARY_DIR}/encoding-check-${TARGET}.stamp") + set(RSP_FILE "${CMAKE_CURRENT_BINARY_DIR}/encoding-check-${TARGET}.rsp") + + # Resolve absolute paths + set(ABS_SOURCES "") + foreach(file ${T_SOURCES}) + if(IS_ABSOLUTE "${file}") + list(APPEND ABS_SOURCES "${file}") + else() + list(APPEND ABS_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/${file}") + endif() + endforeach() + + # Write response file at configure time + string(REPLACE ";" "\n" RSP_CONTENT "${ABS_SOURCES}") + file(WRITE "${RSP_FILE}" "${RSP_CONTENT}") + + # Single process call via response file — no command line length limit + add_custom_command( + OUTPUT "${STAMP_FILE}" + DEPENDS encoding-check ${ABS_SOURCES} COMMENT "Checking source files encodings for target ${TARGET}" + COMMAND $ ${TARGET} "@${RSP_FILE}" + COMMAND ${CMAKE_COMMAND} -E touch "${STAMP_FILE}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) - - # This adds dependency on encoding-check-${TARGET} to ${TARET} - # via the global-encoding-check + add_custom_target(encoding-check-${TARGET} DEPENDS "${STAMP_FILE}") add_dependencies(global-encoding-check encoding-check-${TARGET}) add_dependencies(${TARGET} global-encoding-check) endif() diff --git a/src/dev-utils/encoding-check.cpp b/src/dev-utils/encoding-check.cpp index f4615a7b15..2a645e2ab2 100644 --- a/src/dev-utils/encoding-check.cpp +++ b/src/dev-utils/encoding-check.cpp @@ -2,7 +2,7 @@ #include #include #include - +#include /* * The utf8_check() function scans the '\0'-terminated string starting @@ -68,63 +68,97 @@ unsigned char *utf8_check(unsigned char *s) return NULL; } - -static const char* target; - -void error_exit(const char* error, const char* filename) -{ - std::cerr << "\n\tError: " << error << ": " << filename << "\n" - << "\tTarget: " << target << "\n" - << std::endl; - std::exit(-2); -} - - -void utf8_check_file(const char* filename) +// ORCA +bool check_file(const char* target, const char* filename) { std::ifstream file(filename, std::ios::binary | std::ios::ate); - const auto size = file.tellg(); - - if (size == 0) { - return; + if (!file.is_open()) { + std::cerr << "\n\tError: Could not open file: " << filename + << "\n\tTarget: " << target << "\n" << std::endl; + return false; } + const std::streampos pos = file.tellg(); + + // Fix: tellg() returns -1 (cast to streampos) on failure. + // Also guard against implausibly large files before casting to size_t. + if (pos < 0) { + std::cerr << "\n\tError: Could not determine file size: " << filename + << "\n\tTarget: " << target << "\n" << std::endl; + return false; + } + + const auto size = static_cast(pos); + + if (size == 0) + return true; + file.seekg(0, std::ios::beg); std::vector buffer(size); - if (file.read(buffer.data(), size)) { - buffer.push_back('\0'); - - // Check UTF-8 validity - if (utf8_check(reinterpret_cast(buffer.data())) != nullptr) { - error_exit("Source file does not contain (valid) UTF-8", filename); - } - - // Check against a BOM mark - if (buffer.size() >= 3 - && buffer[0] == '\xef' - && buffer[1] == '\xbb' - && buffer[2] == '\xbf') { - error_exit("Source file is valid UTF-8 but contains a BOM mark", filename); - } - } else { - error_exit("Could not read source file", filename); + // Fix: cast size to streamsize explicitly — streampos and streamsize are + // distinct types and passing streampos where streamsize is expected is + // implementation-defined behaviour on some platforms. + if (!file.read(buffer.data(), static_cast(size))) { + std::cerr << "\n\tError: Could not read source file: " << filename + << "\n\tTarget: " << target << "\n" << std::endl; + return false; } -} + buffer.push_back('\0'); + + if (utf8_check(reinterpret_cast(buffer.data())) != nullptr) { + std::cerr << "\n\tError: Source file does not contain (valid) UTF-8: " << filename + << "\n\tTarget: " << target << "\n" << std::endl; + return false; + } + + if (buffer.size() >= 3 + && buffer[0] == '\xef' + && buffer[1] == '\xbb' + && buffer[2] == '\xbf') { + std::cerr << "\n\tError: Source file is valid UTF-8 but contains a BOM mark: " << filename + << "\n\tTarget: " << target << "\n" << std::endl; + return false; + } + + return true; +} int main(int argc, char const *argv[]) { if (argc < 3) { - std::cerr << "Usage: " << argv[0] << " " << std::endl; + std::cerr << "Usage: " << argv[0] << " [...]" << std::endl; return -1; } - target = argv[1]; + const char* target = argv[1]; + // Collect all files — support @responsefile syntax + std::vector files; for (int i = 2; i < argc; i++) { - utf8_check_file(argv[i]); + if (argv[i][0] == '@') { + // Response file — read paths from it, one per line + std::ifstream rsp(argv[i] + 1); + if (!rsp.is_open()) { + std::cerr << "Could not open response file: " << (argv[i]+1) << std::endl; + return -1; + } + std::string line; + while (std::getline(rsp, line)) { + if (!line.empty()) + files.push_back(line); + } + } else { + files.push_back(argv[i]); + } } - return 0; -} + bool all_ok = true; + for (const auto& f : files) { + if (!check_file(target, f.c_str())) + all_ok = false; + } + + return all_ok ? 0 : -2; +} \ No newline at end of file From cd49d14a1437efa288961faba2d7af72bc10fa00 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Tue, 9 Jun 2026 02:34:25 +0800 Subject: [PATCH 2/2] Fix/orca auth network glitch logout (#14110) * fix(auth): don't log out of Orca cloud on transient token-refresh failures --- src/slic3r/Utils/OrcaCloudServiceAgent.cpp | 174 +++++++++++---------- src/slic3r/Utils/OrcaCloudServiceAgent.hpp | 33 +++- 2 files changed, 118 insertions(+), 89 deletions(-) diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 1b7f779914..02804d1598 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -1564,21 +1564,24 @@ bool OrcaCloudServiceAgent::decode_jwt_expiry(const std::string& token, std::chr return false; } -bool OrcaCloudServiceAgent::refresh_now(const std::string& refresh_token, const std::string& reason, bool async) +RefreshResult OrcaCloudServiceAgent::refresh_now(const std::string& refresh_token, const std::string& reason, bool async) { - if (refresh_token.empty()) return false; + if (refresh_token.empty()) return RefreshResult::AuthRejected; // nothing to refresh bool expected = false; if (!refresh_running.compare_exchange_strong(expected, true)) { BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: refresh already running, skip (reason=" << reason << ")"; - return false; + // Another refresh is already in flight. Treat as transient so we keep the session + // instead of logging out: that in-flight refresh surfaces its own Success/AuthRejected, + // so a genuine rejection is only deferred to the next request, never lost. + return RefreshResult::Transient; } auto worker = [this, refresh_token, reason]() { (void) reason; - bool ok = refresh_session_with_token(refresh_token); + RefreshResult r = refresh_session_with_token(refresh_token); refresh_running.store(false); - return ok; + return r; }; if (async) { @@ -1586,13 +1589,15 @@ bool OrcaCloudServiceAgent::refresh_now(const std::string& refresh_token, const refresh_thread.join(); } refresh_thread = std::thread([worker]() { worker(); }); - return true; + // Fire-and-forget: the outcome isn't known yet and no current caller consumes it. + // Return Transient (indeterminate) rather than implying a completed, successful refresh. + return RefreshResult::Transient; } return worker(); } -bool OrcaCloudServiceAgent::refresh_from_storage(const std::string& reason, bool async) +RefreshResult OrcaCloudServiceAgent::refresh_from_storage(const std::string& reason, bool async) { std::string refresh_token = get_refresh_token(); if (refresh_token.empty()) { @@ -1600,7 +1605,7 @@ bool OrcaCloudServiceAgent::refresh_from_storage(const std::string& reason, bool } if (refresh_token.empty()) { BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: no refresh token available for refresh (reason=" << reason << ")"; - return false; + return RefreshResult::AuthRejected; // no persisted token: nothing to preserve } return refresh_now(refresh_token, reason, async); @@ -1616,37 +1621,55 @@ bool OrcaCloudServiceAgent::refresh_if_expiring(std::chrono::seconds skew, const if (!needs_refresh) return true; - if (refresh_from_storage(reason, false)) return true; + if (refresh_from_storage(reason, false) == RefreshResult::Success) return true; std::this_thread::sleep_for(std::chrono::milliseconds(750)); - return refresh_from_storage(reason + "_retry", false); + return refresh_from_storage(reason + "_retry", false) == RefreshResult::Success; } -bool OrcaCloudServiceAgent::refresh_session_with_token(const std::string& refresh_token) +// Maps a token-refresh HTTP outcome to a RefreshResult. http_code == 0 means the +// server could not be reached; session_established is only meaningful for a 2xx body. +static RefreshResult classify_refresh_result(unsigned http_code, bool session_established) +{ + if (http_code == 0) + return RefreshResult::Transient; // no response: network/transport failure + if (http_code == 400 || http_code == 401 || http_code == 403) + return RefreshResult::AuthRejected; // refresh token rejected + if (http_code >= 400) + return RefreshResult::Transient; // rate-limit (429), server error (5xx) or other 4xx: keep the session + return session_established ? RefreshResult::Success // 2xx with a usable session + : RefreshResult::Transient; // 2xx but unusable body +} + +RefreshResult OrcaCloudServiceAgent::refresh_session_with_token(const std::string& refresh_token) { std::string body = "{\"refresh_token\":\"" + refresh_token + "\"}"; std::string url = auth_base_url + auth_constants::TOKEN_PATH + "?grant_type=refresh_token"; std::string response; unsigned int http_code = 0; - if (!http_post_token(body, &response, &http_code, url) || http_code >= 400) { + // http_post_token sets http_code to 0 when the server could not be reached. + http_post_token(body, &response, &http_code, url); + + bool established = false; + if (http_code >= 200 && http_code < 300) { + if (session_handler) { + established = session_handler(response); + } else { + // No session handler set - parse the token response directly and establish the + // session, so OrcaCloudServiceAgent is self-contained without external setup. + try { + established = set_user_session(json::parse(response)); + } catch (const std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: token refresh parse exception - " << e.what(); + } + } + } else { std::string truncated_response = response.size() > 200 ? response.substr(0, 200) + "..." : response; BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: token refresh failed - http_code=" << http_code << ", response_body=" << truncated_response; - return false; } - if (session_handler) { - return session_handler(response); - } - - // No session handler set - parse the token response directly and establish session - // This makes OrcaCloudServiceAgent self-contained without requiring external setup - try { - return set_user_session(json::parse(response)); - } catch (const std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: token refresh parse exception - " << e.what(); - return false; - } + return classify_refresh_result(http_code, established); } // ============================================================================ @@ -1759,15 +1782,20 @@ void OrcaCloudServiceAgent::clear_session() // HTTP Helpers // ============================================================================ -bool OrcaCloudServiceAgent::attempt_refresh_after_unauthorized(const std::string& reason) +RefreshResult OrcaCloudServiceAgent::attempt_refresh_after_unauthorized(const std::string& reason) { - if (refresh_from_storage(reason, false)) return true; + RefreshResult r = refresh_from_storage(reason, false); + if (r != RefreshResult::Transient) return r; // Success or AuthRejected: decided, no retry + // Only a transient (network/server) failure is worth retrying. std::this_thread::sleep_for(std::chrono::milliseconds(500)); - if (refresh_from_storage(reason + "_retry", false)) return true; + r = refresh_from_storage(reason + "_retry", false); - BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh result=failure source=" << reason << " action=logout"; - return false; + if (r == RefreshResult::Transient) + BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh result=transient source=" << reason << " action=keep_session"; + else if (r == RefreshResult::AuthRejected) + BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh result=rejected source=" << reason << " action=logout"; + return r; } std::map OrcaCloudServiceAgent::data_headers() @@ -1780,6 +1808,24 @@ std::map OrcaCloudServiceAgent::data_headers() return headers; } +bool OrcaCloudServiceAgent::resolve_unauthorized(HttpResult& res, + const std::function& perform, const std::string& reason) +{ + if (res.status != 401) + return false; + + RefreshResult rr = attempt_refresh_after_unauthorized(reason); + if (rr == RefreshResult::Success) { + res = perform(); // refreshed: retry the original request with the new token + return false; + } + + // Transient (no connection / 5xx / 429 / ambiguous): keep the session and token, + // suppress the auth error so the GUI does not log the user out. + // AuthRejected (refresh token genuinely rejected): let the 401 surface -> logout. + return rr == RefreshResult::Transient; +} + int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* response_body, unsigned int* http_code) { std::string url = api_base_url + path; @@ -1788,12 +1834,6 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon if (!ensure_token_fresh("http_get_" + path)) BOOST_LOG_TRIVIAL(warning) << "ensure_token_fresh returned false"; - struct HttpResult { - bool success{false}; - unsigned int status{0}; - std::string body; - }; - auto perform = [&]() { HttpResult result; try { @@ -1832,20 +1872,16 @@ int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* respon }; HttpResult res = perform(); - - // Single retry on 401 - no recursion - if (res.status == 401 && attempt_refresh_after_unauthorized("http_get_" + path)) { - res = perform(); - } + bool suppress = resolve_unauthorized(res, perform, "http_get_" + path); if (response_body) *response_body = res.body; if (http_code) *http_code = res.status; - if (!res.success || res.status >= 400) { + if (!suppress && (!res.success || res.status >= 400)) { invoke_http_error_callback(res.status, res.body); } - return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; + return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; } int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code) @@ -1855,12 +1891,6 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& ensure_token_fresh("http_post_" + path); - struct HttpResult { - bool success{false}; - unsigned int status{0}; - std::string body; - }; - auto perform = [&]() { HttpResult result; try { @@ -1902,20 +1932,16 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& }; HttpResult res = perform(); - - // Single retry on 401 - no recursion - if (res.status == 401 && attempt_refresh_after_unauthorized("http_post_" + path)) { - res = perform(); - } + bool suppress = resolve_unauthorized(res, perform, "http_post_" + path); if (response_body) *response_body = res.body; if (http_code) *http_code = res.status; - if (!res.success || res.status >= 400) { + if (!suppress && (!res.success || res.status >= 400)) { invoke_http_error_callback(res.status, res.body); } - return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; + return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; } int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code) @@ -1925,12 +1951,6 @@ int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string& ensure_token_fresh("http_put_" + path); - struct HttpResult { - bool success{false}; - unsigned int status{0}; - std::string body; - }; - auto perform = [&]() { HttpResult result; try { @@ -1959,7 +1979,7 @@ int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string& .on_error([&](std::string resp_body, std::string error, unsigned resp_status) { result.success = false; result.status = resp_status == 0 ? 404 : resp_status; - result.body = body; + result.body = resp_body; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) .timeout_max(30) @@ -1972,20 +1992,16 @@ int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string& }; HttpResult res = perform(); - - // Single retry on 401 - no recursion - if (res.status == 401 && attempt_refresh_after_unauthorized("http_put_" + path)) { - res = perform(); - } + bool suppress = resolve_unauthorized(res, perform, "http_put_" + path); if (response_body) *response_body = res.body; if (http_code) *http_code = res.status; - if (!res.success || res.status >= 400) { + if (!suppress && (!res.success || res.status >= 400)) { invoke_http_error_callback(res.status, res.body); } - return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; + return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; } int OrcaCloudServiceAgent::http_delete(const std::string& path, std::string* response_body, unsigned int* http_code) @@ -1995,12 +2011,6 @@ int OrcaCloudServiceAgent::http_delete(const std::string& path, std::string* res ensure_token_fresh("http_delete_" + path); - struct HttpResult { - bool success{false}; - unsigned int status{0}; - std::string body; - }; - auto perform = [&]() { HttpResult result; try { @@ -2039,20 +2049,16 @@ int OrcaCloudServiceAgent::http_delete(const std::string& path, std::string* res }; HttpResult res = perform(); - - // Single retry on 401 - no recursion - if (res.status == 401 && attempt_refresh_after_unauthorized("http_delete_" + path)) { - res = perform(); - } + bool suppress = resolve_unauthorized(res, perform, "http_delete_" + path); if (response_body) *response_body = res.body; if (http_code) *http_code = res.status; - if (!res.success || res.status >= 400) { + if (!suppress && (!res.success || res.status >= 400)) { invoke_http_error_callback(res.status, res.body); } - return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; + return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED; } bool OrcaCloudServiceAgent::http_post_token(const std::string& body, std::string* response_body, unsigned int* http_code, const std::string& custom_url) @@ -2108,7 +2114,7 @@ bool OrcaCloudServiceAgent::http_post_token(const std::string& body, std::string }) .on_error([&](std::string body, std::string error, unsigned resp_status) { success = false; - status = resp_status == 0 ? 404 : resp_status; + status = resp_status; // keep 0 for "no response" so the refresh classifier sees a transport failure resp_body = body; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp index 5c58987a0b..6852992198 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp @@ -20,6 +20,14 @@ namespace Slic3r { class AppConfig; struct BundleMetadata; +// Outcome of a token-refresh attempt: decides whether a 401 should log the user +// out (AuthRejected) or be treated as a recoverable condition (Transient). +enum class RefreshResult { + Success, // new tokens obtained + AuthRejected, // server definitively rejected the refresh token -> logout is correct + Transient // network/server problem -> keep the session and retry later +}; + // Constants for OAuth loopback server namespace auth_constants { constexpr int LOOPBACK_PORT = 41172; @@ -276,10 +284,10 @@ public: void clear_refresh_token(); // Token refresh helpers - bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason); - bool refresh_from_storage(const std::string& reason, bool async = false); - bool refresh_now(const std::string& refresh_token, const std::string& reason, bool async = false); - bool refresh_session_with_token(const std::string& refresh_token); + bool refresh_if_expiring(std::chrono::seconds skew, const std::string& reason); + RefreshResult refresh_from_storage(const std::string& reason, bool async = false); + RefreshResult refresh_now(const std::string& refresh_token, const std::string& reason, bool async = false); + RefreshResult refresh_session_with_token(const std::string& refresh_token); // Session state helpers. nickname is the human-facing UI label after provider fallback resolution. bool set_user_session(const std::string& token, @@ -299,13 +307,28 @@ private: std::function on_error ); + // Shared result of one HTTP attempt by the data methods (get/post/put/delete). + struct HttpResult { + bool success{false}; + unsigned int status{0}; + std::string body; + }; + + // Applies the "retry once on 401" policy for the data HTTP methods. + // `res` holds the first response; `perform` re-issues the request after a + // successful refresh. Returns true if the auth error should be SUPPRESSED + // (i.e. the session must be kept rather than logged out). + bool resolve_unauthorized(HttpResult& res, + const std::function& perform, + const std::string& reason); + // HTTP request helpers int http_get(const std::string& path, std::string* response_body, unsigned int* http_code); int http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code); int http_put(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code); int http_delete(const std::string& path, std::string* response_body, unsigned int* http_code); std::map data_headers(); - bool attempt_refresh_after_unauthorized(const std::string& reason); + RefreshResult attempt_refresh_after_unauthorized(const std::string& reason); // Auth HTTP helpers bool http_post_token(const std::string& body, std::string* response_body, unsigned int* http_code, const std::string& url = "");