diff --git a/.gitmodules b/.gitmodules index d2231907..bd42da3a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ [submodule "lib/twitch-eventsub-ws/lib/date"] path = lib/twitch-eventsub-ws/lib/date url = https://github.com/HowardHinnant/date.git +[submodule "lib/twitch-eventsub-ws/lib/fmt"] + path = lib/twitch-eventsub-ws/lib/fmt + url = https://github.com/fmtlib/fmt diff --git a/CHANGELOG.md b/CHANGELOG.md index fbe7b854..280bfd10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - Bugfix: Fixed an issue where Splits could get lost by dragging it onto your Recycle Bin. (#6147) - Bugfix: Fixed some Twitch commands not getting tab-completed correctly. (#6143) - Bugfix: Fixed shared chat badges displaying pixelated when Chatterino is scaled too much. (#6146) +- Bugfix: Fixed a crash that could occur when eventsub was enabled and Chatterino was attached to a conhost on Windows that was later gone. (#6161) - Dev: Mini refactor of Split. (#6148) - Dev: Conan will no longer generate a `CMakeUserPresets.json` file. (#6117) - Dev: Pass `--force-openssl` when installing from CMake in Qt 6.8+. (#6129) diff --git a/lib/twitch-eventsub-ws/benchmarks/src/parse.cpp b/lib/twitch-eventsub-ws/benchmarks/src/parse.cpp index 1af797e1..a07d7fd4 100644 --- a/lib/twitch-eventsub-ws/benchmarks/src/parse.cpp +++ b/lib/twitch-eventsub-ws/benchmarks/src/parse.cpp @@ -170,11 +170,12 @@ void BM_ParseAndHandleMessages(benchmark::State &state) { auto messages = readMessages(); + auto log = std::make_shared(); std::unique_ptr listener = std::make_unique(); boost::asio::io_context ioc; boost::asio::ssl::context ssl( boost::asio::ssl::context::method::tls_client); - auto sess = std::make_shared(ioc, ssl, std::move(listener)); + auto sess = std::make_shared(ioc, ssl, std::move(listener), log); for (auto _ : state) { diff --git a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/logger.hpp b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/logger.hpp new file mode 100644 index 00000000..b377a365 --- /dev/null +++ b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/logger.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include + +namespace chatterino::eventsub::lib { + +class Logger +{ +public: + virtual ~Logger() = default; + virtual void debug(std::string_view msg) = 0; + virtual void warn(std::string_view msg) = 0; +}; + +class NullLogger : public Logger +{ +public: + void debug(std::string_view msg) override + { + } + + void warn(std::string_view msg) override + { + } +}; + +} // namespace chatterino::eventsub::lib diff --git a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/session.hpp b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/session.hpp index 957b4b81..cf380c64 100644 --- a/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/session.hpp +++ b/lib/twitch-eventsub-ws/include/twitch-eventsub-ws/session.hpp @@ -1,5 +1,7 @@ #pragma once +#include "twitch-eventsub-ws/logger.hpp" + #include #include #include @@ -23,7 +25,8 @@ public: // Resolver and socket require an io_context explicit Session(boost::asio::io_context &ioc, boost::asio::ssl::context &ctx, - std::unique_ptr listener); + std::unique_ptr listener, + std::shared_ptr log_); // Start the asynchronous operation void run(std::string _host, std::string _port, std::string _path, @@ -63,6 +66,7 @@ private: void checkKeepalive(); + std::shared_ptr log; boost::asio::ip::tcp::resolver resolver; boost::beast::websocket::stream< boost::beast::ssl_stream> diff --git a/lib/twitch-eventsub-ws/lib/fmt b/lib/twitch-eventsub-ws/lib/fmt new file mode 160000 index 00000000..12391371 --- /dev/null +++ b/lib/twitch-eventsub-ws/lib/fmt @@ -0,0 +1 @@ +Subproject commit 123913715afeb8a437e6388b4473fcc4753e1c9a diff --git a/lib/twitch-eventsub-ws/src/CMakeLists.txt b/lib/twitch-eventsub-ws/src/CMakeLists.txt index 2cc99ff6..d6915e40 100644 --- a/lib/twitch-eventsub-ws/src/CMakeLists.txt +++ b/lib/twitch-eventsub-ws/src/CMakeLists.txt @@ -53,6 +53,7 @@ target_include_directories(${PROJECT_NAME} "${CMAKE_CURRENT_SOURCE_DIR}/../include" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../lib/date/include" + "${CMAKE_CURRENT_SOURCE_DIR}/../lib/fmt/include" ) target_link_libraries(${PROJECT_NAME} diff --git a/lib/twitch-eventsub-ws/src/session.cpp b/lib/twitch-eventsub-ws/src/session.cpp index 9c21e60e..45c2f104 100644 --- a/lib/twitch-eventsub-ws/src/session.cpp +++ b/lib/twitch-eventsub-ws/src/session.cpp @@ -15,10 +15,26 @@ #include #include -#include #include #include +#if __cpp_lib_format >= 201907L && !defined(__APPLE__) +# include +#else +# define FMT_HEADER_ONLY +# include "fmt/format.h" +#endif + +namespace c2fmt { + +#if __cpp_lib_format >= 201907L && !defined(__APPLE__) +using std::format; +#else +using fmt::format; +#endif + +} // namespace c2fmt + namespace beast = boost::beast; namespace http = beast::http; namespace websocket = beast::websocket; @@ -230,8 +246,10 @@ namespace { // Resolver and socket require an io_context Session::Session(boost::asio::io_context &ioc, boost::asio::ssl::context &ctx, - std::unique_ptr listener) - : resolver(boost::asio::make_strand(ioc)) + std::unique_ptr listener, + std::shared_ptr log_) + : log(std::move(log_)) + , resolver(boost::asio::make_strand(ioc)) , ws(boost::asio::make_strand(ioc), ctx) , listener(std::move(listener)) { @@ -418,7 +436,8 @@ void Session::onClose(beast::error_code ec) void Session::fail(beast::error_code ec, std::string_view op) { - std::cerr << op << ": " << ec.message() << " (" << ec.location() << ")\n"; + this->log->warn(c2fmt::format("{}: {} ({})", op, ec.message(), + ec.location().to_string())); if (!this->ws.is_open() && this->listener) { if (this->keepaliveTimer) @@ -505,7 +524,8 @@ boost::system::error_code Session::onSessionWelcome( this->keepaliveTimeout = std::chrono::seconds{payload.keepaliveTimeoutSeconds.value_or(60)} * 2; assert(!this->keepaliveTimer); - std::cerr << "Keepalive: " << this->keepaliveTimeout.count() << 's'; + this->log->debug( + c2fmt::format("Keepalive: {}s", this->keepaliveTimeout.count())); this->checkKeepalive(); return {}; @@ -551,7 +571,7 @@ void Session::checkKeepalive() { if (!this->receivedMessage) { - std::cerr << "Keepalive timeout, closing\n"; + this->log->debug("Keepalive timeout, closing"); if (this->listener) { this->listener->onClose(std::move(this->listener), {}); @@ -572,7 +592,8 @@ void Session::checkKeepalive() this->keepaliveTimer->async_wait([this](boost::system::error_code ec) { if (ec) { - std::cerr << "Keepalive timer cancelled: " << ec.message() << '\n'; + this->log->warn( + c2fmt::format("Keepalive timer cancelled: {}", ec.message())); return; } this->checkKeepalive(); diff --git a/lib/twitch-eventsub-ws/tests/src/parse.cpp b/lib/twitch-eventsub-ws/tests/src/parse.cpp index edff65f7..5b933943 100644 --- a/lib/twitch-eventsub-ws/tests/src/parse.cpp +++ b/lib/twitch-eventsub-ws/tests/src/parse.cpp @@ -153,11 +153,12 @@ TEST_P(TestHandleMessageP, Run) { auto buf = readToFlatBuffer(filePath(GetParam() + ".json")); + auto log = std::make_shared(); std::unique_ptr listener = std::make_unique(); boost::asio::io_context ioc; boost::asio::ssl::context ssl( boost::asio::ssl::context::method::tls_client); - auto sess = std::make_shared(ioc, ssl, std::move(listener)); + auto sess = std::make_shared(ioc, ssl, std::move(listener), log); auto ec = sess->handleMessage(buf); ASSERT_FALSE(ec.failed()) << ec.what() << ec.message() << ec.location().to_string(); diff --git a/resources/licenses/fmtlib.txt b/resources/licenses/fmtlib.txt new file mode 100644 index 00000000..1cd1ef92 --- /dev/null +++ b/resources/licenses/fmtlib.txt @@ -0,0 +1,27 @@ +Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into a machine-executable object form of such +source code, you may redistribute such embedded portions in such object form +without including the above copyright and permission notices. diff --git a/src/providers/twitch/eventsub/Controller.cpp b/src/providers/twitch/eventsub/Controller.cpp index f755b6c1..6072f2e6 100644 --- a/src/providers/twitch/eventsub/Controller.cpp +++ b/src/providers/twitch/eventsub/Controller.cpp @@ -40,8 +40,35 @@ const auto &LOG = chatterinoTwitchEventSub; namespace chatterino::eventsub { +class QLogProxy : public lib::Logger +{ +public: + QLogProxy(const QLoggingCategory &loggingCategory_) + : loggingCategory(loggingCategory_) + { + } + + void debug(std::string_view msg) override + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + qCDebug(this->loggingCategory).noquote() << msg; +#endif + } + + void warn(std::string_view msg) override + { +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + qCWarning(this->loggingCategory).noquote() << msg; +#endif + } + +private: + const QLoggingCategory &loggingCategory; +}; + Controller::Controller() - : userAgent(QStringLiteral("chatterino/%1 (%2)") + : logProxy(std::shared_ptr(new QLogProxy(LOG()))) + , userAgent(QStringLiteral("chatterino/%1 (%2)") .arg(Version::instance().version(), Version::instance().commitHash()) .toUtf8() @@ -456,7 +483,7 @@ void Controller::createConnection(std::string host, std::string port, } auto connection = std::make_shared( - this->ioContext, sslContext, std::move(listener)); + this->ioContext, sslContext, std::move(listener), this->logProxy); this->registerConnection(connection); diff --git a/src/providers/twitch/eventsub/Controller.hpp b/src/providers/twitch/eventsub/Controller.hpp index e484996e..0b878610 100644 --- a/src/providers/twitch/eventsub/Controller.hpp +++ b/src/providers/twitch/eventsub/Controller.hpp @@ -2,6 +2,7 @@ #include "providers/twitch/eventsub/SubscriptionHandle.hpp" #include "providers/twitch/eventsub/SubscriptionRequest.hpp" +#include "twitch-eventsub-ws/logger.hpp" #include "twitch-eventsub-ws/session.hpp" #include "util/ExponentialBackoff.hpp" #include "util/ThreadGuard.hpp" @@ -91,6 +92,8 @@ private: void clearConnections(); + std::shared_ptr logProxy; + const std::string userAgent; std::string eventSubHost; diff --git a/src/widgets/settingspages/AboutPage.cpp b/src/widgets/settingspages/AboutPage.cpp index c1df7a67..5f19a5ce 100644 --- a/src/widgets/settingspages/AboutPage.cpp +++ b/src/widgets/settingspages/AboutPage.cpp @@ -132,6 +132,8 @@ AboutPage::AboutPage() addLicense(form.getElement(), "Howard Hinnant's date.h", "https://github.com/HowardHinnant/date", ":/licenses/howard-hinnant-date.txt"); + addLicense(form.getElement(), "{fmt}", "https://fmt.dev", + ":/licenses/fmtlib.txt"); } // Attributions diff --git a/tests/src/EventSubMessages.cpp b/tests/src/EventSubMessages.cpp index 5064cbe4..3a6c6db5 100644 --- a/tests/src/EventSubMessages.cpp +++ b/tests/src/EventSubMessages.cpp @@ -304,13 +304,14 @@ TEST_P(TestEventSubMessagesP, Run) input = snapshot->input().toArray(); } + auto log = std::make_shared(); std::unique_ptr listener = std::make_unique(); boost::asio::io_context ioc; boost::asio::ssl::context ssl( boost::asio::ssl::context::method::tls_client); - auto sess = - std::make_shared(ioc, ssl, std::move(listener)); + auto sess = std::make_shared( + ioc, ssl, std::move(listener), log); for (const auto inputRef : input) {