refactor: move Twitch PubSub to use liveupdates (#6638)
Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
@@ -15,6 +15,8 @@ Checks: "-*,
|
|||||||
cert-*,
|
cert-*,
|
||||||
clazy-*,
|
clazy-*,
|
||||||
-clazy-qstring-allocations,
|
-clazy-qstring-allocations,
|
||||||
|
-clazy-missing-qobject-macro,
|
||||||
|
-clazy-ctor-missing-parent-argument,
|
||||||
cppcoreguidelines-*,
|
cppcoreguidelines-*,
|
||||||
-cppcoreguidelines-pro-type-cstyle-cast,
|
-cppcoreguidelines-pro-type-cstyle-cast,
|
||||||
-cppcoreguidelines-pro-type-vararg,
|
-cppcoreguidelines-pro-type-vararg,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ ignore:
|
|||||||
- "**/lib/settings/*"
|
- "**/lib/settings/*"
|
||||||
- "**/lib/signals/*"
|
- "**/lib/signals/*"
|
||||||
- "**/lib/sol2/*"
|
- "**/lib/sol2/*"
|
||||||
- "**/lib/websocketpp/*"
|
|
||||||
- "**/lib/WinToast/*"
|
- "**/lib/WinToast/*"
|
||||||
- "**/build*/"
|
- "**/build*/"
|
||||||
- "**/ui_*.h"
|
- "**/ui_*.h"
|
||||||
|
|||||||
@@ -20,10 +20,6 @@
|
|||||||
[submodule "lib/qtkeychain"]
|
[submodule "lib/qtkeychain"]
|
||||||
path = lib/qtkeychain
|
path = lib/qtkeychain
|
||||||
url = https://github.com/frankosterfeld/qtkeychain
|
url = https://github.com/frankosterfeld/qtkeychain
|
||||||
[submodule "lib/websocketpp"]
|
|
||||||
path = lib/websocketpp
|
|
||||||
url = https://github.com/Chatterino/websocketpp
|
|
||||||
branch = chatterino
|
|
||||||
[submodule "cmake/sanitizers-cmake"]
|
[submodule "cmake/sanitizers-cmake"]
|
||||||
path = cmake/sanitizers-cmake
|
path = cmake/sanitizers-cmake
|
||||||
url = https://github.com/arsenm/sanitizers-cmake
|
url = https://github.com/arsenm/sanitizers-cmake
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
- Dev: Disabled `llvm-prefer-static-over-anonymous-namespace` in clang-tidy. (#6610)
|
- Dev: Disabled `llvm-prefer-static-over-anonymous-namespace` in clang-tidy. (#6610)
|
||||||
- Dev: Added Clazy linting in CI. (#6623)
|
- Dev: Added Clazy linting in CI. (#6623)
|
||||||
- Dev: Added custom clang-tidy module linting in CI. (#6626)
|
- Dev: Added custom clang-tidy module linting in CI. (#6626)
|
||||||
|
- Dev: Moved Twitch PubSub to liveupdates. (#6638)
|
||||||
|
|
||||||
## 2.5.4
|
## 2.5.4
|
||||||
|
|
||||||
|
|||||||
@@ -197,8 +197,6 @@ if (BUILD_WITH_QTKEYCHAIN)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Websocketpp REQUIRED)
|
|
||||||
|
|
||||||
if (BUILD_TESTS)
|
if (BUILD_TESTS)
|
||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
# For MSVC: Prevent overriding the parent project's compiler/linker settings
|
# For MSVC: Prevent overriding the parent project's compiler/linker settings
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
|
|
||||||
find_path(Websocketpp_INCLUDE_DIR websocketpp/version.hpp HINTS ${CMAKE_SOURCE_DIR}/lib/websocketpp)
|
|
||||||
|
|
||||||
find_package_handle_standard_args(Websocketpp DEFAULT_MSG Websocketpp_INCLUDE_DIR)
|
|
||||||
|
|
||||||
if (Websocketpp_FOUND)
|
|
||||||
add_library(websocketpp::websocketpp INTERFACE IMPORTED)
|
|
||||||
set_target_properties(websocketpp::websocketpp PROPERTIES
|
|
||||||
INTERFACE_INCLUDE_DIRECTORIES "${Websocketpp_INCLUDE_DIR}"
|
|
||||||
)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
mark_as_advanced(Websocketpp_INCLUDE_DIR)
|
|
||||||
Submodule lib/websocketpp deleted from f1736a8e72
@@ -1,145 +0,0 @@
|
|||||||
Main Library:
|
|
||||||
|
|
||||||
Copyright (c) 2014, Peter Thorson. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the WebSocket++ Project nor the
|
|
||||||
names of its contributors may be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
Bundled Libraries:
|
|
||||||
|
|
||||||
****** Base 64 Library (base64/base64.hpp) ******
|
|
||||||
base64.hpp is a repackaging of the base64.cpp and base64.h files into a
|
|
||||||
single header suitable for use as a header only library. This conversion was
|
|
||||||
done by Peter Thorson (webmaster@zaphoyd.com) in 2012. All modifications to
|
|
||||||
the code are redistributed under the same license as the original, which is
|
|
||||||
listed below.
|
|
||||||
|
|
||||||
base64.cpp and base64.h
|
|
||||||
|
|
||||||
Copyright (C) 2004-2008 René Nyffenegger
|
|
||||||
|
|
||||||
This source code is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the author be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this source code must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original source code. If you use this source code
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original source code.
|
|
||||||
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
|
||||||
|
|
||||||
****** SHA1 Library (sha1/sha1.hpp) ******
|
|
||||||
sha1.hpp is a repackaging of the sha1.cpp and sha1.h files from the shallsha1
|
|
||||||
library (http://code.google.com/p/smallsha1/) into a single header suitable for
|
|
||||||
use as a header only library. This conversion was done by Peter Thorson
|
|
||||||
(webmaster@zaphoyd.com) in 2013. All modifications to the code are redistributed
|
|
||||||
under the same license as the original, which is listed below.
|
|
||||||
|
|
||||||
Copyright (c) 2011, Micael Hildenborg
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of Micael Hildenborg nor the
|
|
||||||
names of its contributors may be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY Micael Hildenborg ''AS IS'' AND ANY
|
|
||||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL Micael Hildenborg BE LIABLE FOR ANY
|
|
||||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
****** MD5 Library (common/md5.hpp) ******
|
|
||||||
md5.hpp is a reformulation of the md5.h and md5.c code from
|
|
||||||
http://www.opensource.apple.com/source/cups/cups-59/cups/md5.c to allow it to
|
|
||||||
function as a component of a header only library. This conversion was done by
|
|
||||||
Peter Thorson (webmaster@zaphoyd.com) in 2012 for the WebSocket++ project. The
|
|
||||||
changes are released under the same license as the original (listed below)
|
|
||||||
|
|
||||||
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
|
|
||||||
|
|
||||||
This software is provided 'as-is', without any express or implied
|
|
||||||
warranty. In no event will the authors be held liable for any damages
|
|
||||||
arising from the use of this software.
|
|
||||||
|
|
||||||
Permission is granted to anyone to use this software for any purpose,
|
|
||||||
including commercial applications, and to alter it and redistribute it
|
|
||||||
freely, subject to the following restrictions:
|
|
||||||
|
|
||||||
1. The origin of this software must not be misrepresented; you must not
|
|
||||||
claim that you wrote the original software. If you use this software
|
|
||||||
in a product, an acknowledgment in the product documentation would be
|
|
||||||
appreciated but is not required.
|
|
||||||
2. Altered source versions must be plainly marked as such, and must not be
|
|
||||||
misrepresented as being the original software.
|
|
||||||
3. This notice may not be removed or altered from any source distribution.
|
|
||||||
|
|
||||||
L. Peter Deutsch
|
|
||||||
ghost@aladdin.com
|
|
||||||
|
|
||||||
****** UTF8 Validation logic (utf8_validation.hpp) ******
|
|
||||||
utf8_validation.hpp is adapted from code originally written by Bjoern Hoehrmann
|
|
||||||
<bjoern@hoehrmann.de>. See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for
|
|
||||||
details.
|
|
||||||
|
|
||||||
The original license:
|
|
||||||
|
|
||||||
Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>
|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -291,7 +291,6 @@ void Application::initialize(Settings &settings, const Paths &paths)
|
|||||||
{
|
{
|
||||||
this->initNm(paths);
|
this->initNm(paths);
|
||||||
}
|
}
|
||||||
this->twitchPubSub->initialize();
|
|
||||||
|
|
||||||
this->twitch->initEventAPIs(this->bttvLiveUpdates.get(),
|
this->twitch->initEventAPIs(this->bttvLiveUpdates.get(),
|
||||||
this->seventvEventAPI.get());
|
this->seventvEventAPI.get());
|
||||||
|
|||||||
@@ -433,12 +433,9 @@ set(SOURCE_FILES
|
|||||||
providers/twitch/IrcMessageHandler.hpp
|
providers/twitch/IrcMessageHandler.hpp
|
||||||
providers/twitch/PubSubClient.cpp
|
providers/twitch/PubSubClient.cpp
|
||||||
providers/twitch/PubSubClient.hpp
|
providers/twitch/PubSubClient.hpp
|
||||||
providers/twitch/PubSubClientOptions.hpp
|
|
||||||
providers/twitch/PubSubHelpers.hpp
|
|
||||||
providers/twitch/PubSubManager.cpp
|
providers/twitch/PubSubManager.cpp
|
||||||
providers/twitch/PubSubManager.hpp
|
providers/twitch/PubSubManager.hpp
|
||||||
providers/twitch/PubSubMessages.hpp
|
providers/twitch/PubSubMessages.hpp
|
||||||
providers/twitch/PubSubWebsocket.hpp
|
|
||||||
providers/twitch/TwitchAccount.cpp
|
providers/twitch/TwitchAccount.cpp
|
||||||
providers/twitch/TwitchAccount.hpp
|
providers/twitch/TwitchAccount.hpp
|
||||||
providers/twitch/TwitchAccountManager.cpp
|
providers/twitch/TwitchAccountManager.cpp
|
||||||
@@ -886,7 +883,6 @@ if (CHATTERINO_GENERATE_COVERAGE)
|
|||||||
EXCLUDE "lib/settings/*"
|
EXCLUDE "lib/settings/*"
|
||||||
EXCLUDE "lib/signals/*"
|
EXCLUDE "lib/signals/*"
|
||||||
EXCLUDE "lib/sol2/*"
|
EXCLUDE "lib/sol2/*"
|
||||||
EXCLUDE "lib/websocketpp/*"
|
|
||||||
EXCLUDE "lib/WinToast/*"
|
EXCLUDE "lib/WinToast/*"
|
||||||
|
|
||||||
EXCLUDE "*/ui_*.h"
|
EXCLUDE "*/ui_*.h"
|
||||||
@@ -918,7 +914,6 @@ target_link_libraries(${LIBRARY_PROJECT}
|
|||||||
Pajlada::Serialize
|
Pajlada::Serialize
|
||||||
Pajlada::Settings
|
Pajlada::Settings
|
||||||
Pajlada::Signals
|
Pajlada::Signals
|
||||||
websocketpp::websocketpp
|
|
||||||
Threads::Threads
|
Threads::Threads
|
||||||
LRUCache
|
LRUCache
|
||||||
MagicEnum
|
MagicEnum
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
|
||||||
|
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <websocketpp/error.hpp>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
@@ -24,36 +19,6 @@ public:
|
|||||||
* Currently a proxy is applied if configured.
|
* Currently a proxy is applied if configured.
|
||||||
*/
|
*/
|
||||||
static void applyFromEnv(const Env &env);
|
static void applyFromEnv(const Env &env);
|
||||||
|
|
||||||
static void applyToWebSocket(const auto &connection)
|
|
||||||
{
|
|
||||||
const auto applicationProxy = QNetworkProxy::applicationProxy();
|
|
||||||
if (applicationProxy.type() != QNetworkProxy::HttpProxy)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
std::string url = "http://";
|
|
||||||
url += applicationProxy.hostName().toStdString();
|
|
||||||
url += ":";
|
|
||||||
url += std::to_string(applicationProxy.port());
|
|
||||||
websocketpp::lib::error_code ec;
|
|
||||||
connection->set_proxy(url, ec);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoNetwork)
|
|
||||||
<< "Couldn't set websocket proxy:" << ec.value();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
connection->set_proxy_basic_auth(
|
|
||||||
applicationProxy.user().toStdString(),
|
|
||||||
applicationProxy.password().toStdString(), ec);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoNetwork)
|
|
||||||
<< "Couldn't set websocket proxy auth:" << ec.value();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
BttvLiveUpdateClient::BttvLiveUpdateClient(BttvLiveUpdates &manager)
|
BttvLiveUpdateClient::BttvLiveUpdateClient(BttvLiveUpdates &manager)
|
||||||
: BasicPubSubClient<BttvLiveUpdateSubscription>(100)
|
: BasicPubSubClient(100)
|
||||||
, manager(manager)
|
, manager(manager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace chatterino {
|
|||||||
class BttvLiveUpdates;
|
class BttvLiveUpdates;
|
||||||
|
|
||||||
class BttvLiveUpdateClient
|
class BttvLiveUpdateClient
|
||||||
: public BasicPubSubClient<BttvLiveUpdateSubscription>
|
: public BasicPubSubClient<BttvLiveUpdateSubscription, BttvLiveUpdateClient>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BttvLiveUpdateClient(BttvLiveUpdates &manager);
|
BttvLiveUpdateClient(BttvLiveUpdates &manager);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace chatterino {
|
|||||||
*
|
*
|
||||||
* @tparam Subscription see BasicPubSubManager
|
* @tparam Subscription see BasicPubSubManager
|
||||||
*/
|
*/
|
||||||
template <typename SubscriptionT>
|
template <typename SubscriptionT, typename Derived>
|
||||||
class BasicPubSubClient
|
class BasicPubSubClient
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -66,6 +66,16 @@ public:
|
|||||||
this->ws_.sendText(data);
|
this->ws_.sendText(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray encodeSubscription(const Subscription &subscription)
|
||||||
|
{
|
||||||
|
return subscription.encodeSubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray encodeUnsubscription(const Subscription &subscription)
|
||||||
|
{
|
||||||
|
return subscription.encodeUnsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/**
|
/**
|
||||||
* @return true if this client subscribed to this subscription
|
* @return true if this client subscribed to this subscription
|
||||||
@@ -93,7 +103,8 @@ protected:
|
|||||||
qCDebug(chatterinoLiveupdates) << "Subscribing to" << subscription;
|
qCDebug(chatterinoLiveupdates) << "Subscribing to" << subscription;
|
||||||
DebugCount::increase("LiveUpdates subscriptions");
|
DebugCount::increase("LiveUpdates subscriptions");
|
||||||
|
|
||||||
QByteArray encoded = subscription.encodeSubscribe();
|
QByteArray encoded =
|
||||||
|
static_cast<Derived *>(this)->encodeSubscription(subscription);
|
||||||
this->ws_.sendText(encoded);
|
this->ws_.sendText(encoded);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -113,7 +124,8 @@ protected:
|
|||||||
qCDebug(chatterinoLiveupdates) << "Unsubscribing from" << subscription;
|
qCDebug(chatterinoLiveupdates) << "Unsubscribing from" << subscription;
|
||||||
DebugCount::decrease("LiveUpdates subscriptions");
|
DebugCount::decrease("LiveUpdates subscriptions");
|
||||||
|
|
||||||
QByteArray encoded = subscription.encodeUnsubscribe();
|
QByteArray encoded =
|
||||||
|
static_cast<Derived *>(this)->encodeUnsubscription(subscription);
|
||||||
this->ws_.sendText(encoded);
|
this->ws_.sendText(encoded);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace chatterino::seventv::eventapi {
|
|||||||
|
|
||||||
Client::Client(SeventvEventAPI &manager,
|
Client::Client(SeventvEventAPI &manager,
|
||||||
std::chrono::milliseconds heartbeatInterval)
|
std::chrono::milliseconds heartbeatInterval)
|
||||||
: BasicPubSubClient<Subscription>(100)
|
: BasicPubSubClient(100)
|
||||||
, lastHeartbeat_(std::chrono::steady_clock::now())
|
, lastHeartbeat_(std::chrono::steady_clock::now())
|
||||||
, heartbeatInterval_(heartbeatInterval)
|
, heartbeatInterval_(heartbeatInterval)
|
||||||
, manager_(manager)
|
, manager_(manager)
|
||||||
@@ -23,7 +23,7 @@ Client::Client(SeventvEventAPI &manager,
|
|||||||
|
|
||||||
void Client::onOpen()
|
void Client::onOpen()
|
||||||
{
|
{
|
||||||
BasicPubSubClient<Subscription>::onOpen();
|
BasicPubSubClient::onOpen();
|
||||||
this->lastHeartbeat_.store(std::chrono::steady_clock::now(),
|
this->lastHeartbeat_.store(std::chrono::steady_clock::now(),
|
||||||
std::memory_order::relaxed);
|
std::memory_order::relaxed);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ struct Dispatch;
|
|||||||
struct CosmeticCreateDispatch;
|
struct CosmeticCreateDispatch;
|
||||||
struct EntitlementCreateDeleteDispatch;
|
struct EntitlementCreateDeleteDispatch;
|
||||||
|
|
||||||
class Client : public BasicPubSubClient<Subscription>,
|
class Client : public BasicPubSubClient<Subscription, Client>,
|
||||||
std::enable_shared_from_this<Client>
|
public std::enable_shared_from_this<Client>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Client(SeventvEventAPI &manager,
|
Client(SeventvEventAPI &manager,
|
||||||
|
|||||||
@@ -1,194 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2020, Peter Thorson, Steve Wills. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
* * Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
* * Neither the name of the WebSocket++ Project nor the
|
|
||||||
* names of its contributors may be used to endorse or promote products
|
|
||||||
* derived from this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL PETER THORSON BE LIABLE FOR ANY
|
|
||||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
||||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
|
||||||
|
|
||||||
#include <websocketpp/common/cpp11.hpp>
|
|
||||||
#include <websocketpp/logger/basic.hpp>
|
|
||||||
#include <websocketpp/logger/levels.hpp>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace websocketpp::log {
|
|
||||||
|
|
||||||
template <typename concurrency, typename names>
|
|
||||||
class chatterinowebsocketpplogger : public basic<concurrency, names>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using base = chatterinowebsocketpplogger<concurrency, names>;
|
|
||||||
|
|
||||||
chatterinowebsocketpplogger(channel_type_hint::value)
|
|
||||||
: m_static_channels(0xffffffff)
|
|
||||||
, m_dynamic_channels(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
chatterinowebsocketpplogger(std::ostream *)
|
|
||||||
: m_static_channels(0xffffffff)
|
|
||||||
, m_dynamic_channels(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
chatterinowebsocketpplogger(level c, channel_type_hint::value)
|
|
||||||
: m_static_channels(c)
|
|
||||||
, m_dynamic_channels(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
chatterinowebsocketpplogger(level c, std::ostream *)
|
|
||||||
: m_static_channels(c)
|
|
||||||
, m_dynamic_channels(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
~chatterinowebsocketpplogger()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
chatterinowebsocketpplogger(
|
|
||||||
chatterinowebsocketpplogger<concurrency, names> const &other)
|
|
||||||
: m_static_channels(other.m_static_channels)
|
|
||||||
, m_dynamic_channels(other.m_dynamic_channels)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
|
||||||
chatterinowebsocketpplogger<concurrency, names> &operator=(
|
|
||||||
chatterinowebsocketpplogger<concurrency, names> const &) = delete;
|
|
||||||
#endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
|
||||||
|
|
||||||
#ifdef _WEBSOCKETPP_MOVE_SEMANTICS_
|
|
||||||
/// Move constructor
|
|
||||||
chatterinowebsocketpplogger(
|
|
||||||
chatterinowebsocketpplogger<concurrency, names> &&other)
|
|
||||||
: m_static_channels(other.m_static_channels)
|
|
||||||
, m_dynamic_channels(other.m_dynamic_channels)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
# ifdef _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
|
||||||
// no move assignment operator because of const member variables
|
|
||||||
chatterinowebsocketpplogger<concurrency, names> &operator=(
|
|
||||||
chatterinowebsocketpplogger<concurrency, names> &&) = delete;
|
|
||||||
# endif // _WEBSOCKETPP_DEFAULT_DELETE_FUNCTIONS_
|
|
||||||
#endif // _WEBSOCKETPP_MOVE_SEMANTICS_
|
|
||||||
|
|
||||||
/// Explicitly do nothing, this logger doesn't support changing ostream
|
|
||||||
void set_ostream(std::ostream *)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dynamically enable the given list of channels
|
|
||||||
/**
|
|
||||||
* @param channels The package of channels to enable
|
|
||||||
*/
|
|
||||||
void set_channels(level channels)
|
|
||||||
{
|
|
||||||
if (channels == names::none)
|
|
||||||
{
|
|
||||||
clear_channels(names::all);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scoped_lock_type lock(m_lock);
|
|
||||||
m_dynamic_channels |= (channels & m_static_channels);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Dynamically disable the given list of channels
|
|
||||||
/**
|
|
||||||
* @param channels The package of channels to disable
|
|
||||||
*/
|
|
||||||
void clear_channels(level channels)
|
|
||||||
{
|
|
||||||
scoped_lock_type lock(m_lock);
|
|
||||||
m_dynamic_channels &= ~channels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a string message to the given channel
|
|
||||||
/**
|
|
||||||
* @param channel The channel to write to
|
|
||||||
* @param msg The message to write
|
|
||||||
*/
|
|
||||||
void write(level channel, std::string const &msg)
|
|
||||||
{
|
|
||||||
scoped_lock_type lock(m_lock);
|
|
||||||
if (!this->dynamic_test(channel))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCDebug(chatterinoWebsocket).nospace()
|
|
||||||
<< names::channel_name(channel) << ": "
|
|
||||||
<< QString::fromStdString(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a cstring message to the given channel
|
|
||||||
/**
|
|
||||||
* @param channel The channel to write to
|
|
||||||
* @param msg The message to write
|
|
||||||
*/
|
|
||||||
void write(level channel, char const *msg)
|
|
||||||
{
|
|
||||||
scoped_lock_type lock(m_lock);
|
|
||||||
if (!this->dynamic_test(channel))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCDebug(chatterinoWebsocket).nospace()
|
|
||||||
<< names::channel_name(channel) << ": " << msg;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test whether a channel is statically enabled
|
|
||||||
/**
|
|
||||||
* @param channel The package of channels to test
|
|
||||||
*/
|
|
||||||
|
|
||||||
_WEBSOCKETPP_CONSTEXPR_TOKEN_ bool static_test(level channel) const
|
|
||||||
{
|
|
||||||
return ((channel & m_static_channels) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Test whether a channel is dynamically enabled
|
|
||||||
/**
|
|
||||||
* @param channel The package of channels to test
|
|
||||||
*/
|
|
||||||
bool dynamic_test(level channel)
|
|
||||||
{
|
|
||||||
return ((channel & m_dynamic_channels) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
using scoped_lock_type = typename concurrency::scoped_lock_type;
|
|
||||||
using mutex_type = typename concurrency::mutex_type;
|
|
||||||
mutex_type m_lock;
|
|
||||||
|
|
||||||
private:
|
|
||||||
level const m_static_channels;
|
|
||||||
level m_dynamic_channels;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace websocketpp::log
|
|
||||||
@@ -1,224 +1,196 @@
|
|||||||
#include "providers/twitch/PubSubClient.hpp"
|
#include "providers/twitch/PubSubClient.hpp"
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "providers/twitch/PubSubHelpers.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/PubSubMessages.hpp"
|
#include "providers/twitch/PubSubMessages.hpp"
|
||||||
#include "util/DebugCount.hpp"
|
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
static const char *PING_PAYLOAD = R"({"type":"PING"})";
|
using namespace Qt::Literals;
|
||||||
|
|
||||||
PubSubClient::PubSubClient(WebsocketClient &websocketClient,
|
QDebug operator<<(QDebug debug, const TopicData &data)
|
||||||
WebsocketHandle handle,
|
{
|
||||||
const PubSubClientOptions &clientOptions)
|
QDebugStateSaver saver(debug);
|
||||||
: websocketClient_(websocketClient)
|
debug.nospace() << "TopicData(" << data.topic << ')';
|
||||||
, handle_(handle)
|
|
||||||
, heartbeatTimer_(std::make_shared<boost::asio::steady_timer>(
|
return debug;
|
||||||
this->websocketClient_.get_io_service()))
|
}
|
||||||
, clientOptions_(clientOptions)
|
|
||||||
|
PubSubClient::PubSubClient(PubSub &manager,
|
||||||
|
std::chrono::milliseconds heartbeatInterval)
|
||||||
|
: BasicPubSubClient(MAX_LISTENS)
|
||||||
|
, heartbeatInterval_(heartbeatInterval)
|
||||||
|
, manager_(manager)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSubClient::start()
|
void PubSubClient::onOpen()
|
||||||
{
|
{
|
||||||
assert(!this->started_);
|
BasicPubSubClient::onOpen();
|
||||||
|
this->isOpen_ = true;
|
||||||
this->started_ = true;
|
this->lastHeartbeat_ = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
this->ping();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSubClient::stop()
|
void PubSubClient::onMessage(const QByteArray &msg)
|
||||||
{
|
{
|
||||||
assert(this->started_);
|
this->manager_.diag.messagesReceived++;
|
||||||
|
|
||||||
this->started_ = false;
|
auto optMessage = parsePubSubBaseMessage(msg);
|
||||||
this->heartbeatTimer_->cancel();
|
if (!optMessage)
|
||||||
}
|
|
||||||
|
|
||||||
void PubSubClient::close(const std::string &reason,
|
|
||||||
websocketpp::close::status::value code)
|
|
||||||
{
|
|
||||||
boost::asio::post(
|
|
||||||
this->websocketClient_.get_io_service().get_executor(),
|
|
||||||
[this, reason, code] {
|
|
||||||
// We need to post this request to the io service executor
|
|
||||||
// to ensure the weak pointer used in get_con_from_hdl is used in a safe way
|
|
||||||
WebsocketErrorCode ec;
|
|
||||||
|
|
||||||
auto conn =
|
|
||||||
this->websocketClient_.get_con_from_hdl(this->handle_, ec);
|
|
||||||
if (ec)
|
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoPubSub)
|
qCDebug(chatterinoPubSub)
|
||||||
<< "Error getting con:" << ec.message().c_str();
|
<< "Unable to parse incoming pubsub message" << msg;
|
||||||
|
this->manager_.diag.messagesFailedToParse += 1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
conn->close(code, reason, ec);
|
auto message = *optMessage;
|
||||||
if (ec)
|
|
||||||
|
switch (message.type)
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoPubSub)
|
case PubSubMessage::Type::Pong: {
|
||||||
<< "Error closing:" << ec.message().c_str();
|
this->lastHeartbeat_.store(std::chrono::steady_clock::now());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PubSubMessage::Type::Response: {
|
||||||
|
this->handleResponse(message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PubSubMessage::Type::Message: {
|
||||||
|
auto oMessageMessage = message.toInner<PubSubMessageMessage>();
|
||||||
|
if (!oMessageMessage)
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoPubSub) << "Malformed MESSAGE:" << msg;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PubSubClient::listen(const PubSubListenMessage &msg)
|
this->handleMessageResponse(*oMessageMessage);
|
||||||
{
|
|
||||||
auto numRequestedListens = msg.topics.size();
|
|
||||||
|
|
||||||
if (this->numListens_ + numRequestedListens > PubSubClient::MAX_LISTENS)
|
|
||||||
{
|
|
||||||
// This PubSubClient is already at its peak listens
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this->numListens_ += numRequestedListens;
|
|
||||||
DebugCount::increase("PubSub topic pending listens",
|
|
||||||
static_cast<int64_t>(numRequestedListens));
|
|
||||||
|
|
||||||
for (const auto &topic : msg.topics)
|
|
||||||
{
|
|
||||||
this->listeners_.emplace_back(Listener{
|
|
||||||
TopicData{
|
|
||||||
topic,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PubSubMessage::Type::INVALID:
|
||||||
|
default: {
|
||||||
qCDebug(chatterinoPubSub)
|
qCDebug(chatterinoPubSub)
|
||||||
<< "Subscribing to" << numRequestedListens << "topics";
|
<< "Unknown message type:" << message.typeString;
|
||||||
|
}
|
||||||
this->send(msg.toJson());
|
break;
|
||||||
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PubSubClient::UnlistenPrefixResponse PubSubClient::unlistenPrefix(
|
void PubSubClient::checkHeartbeat()
|
||||||
const QString &prefix)
|
|
||||||
{
|
{
|
||||||
std::vector<QString> topics;
|
if (!this->isOpen_)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto it = this->listeners_.begin(); it != this->listeners_.end();)
|
auto dur = std::chrono::steady_clock::now() - this->lastHeartbeat_.load();
|
||||||
|
if (dur > this->heartbeatInterval_ * 1.5)
|
||||||
{
|
{
|
||||||
const auto &listener = *it;
|
qCDebug(chatterinoPubSub) << "Heartbeat timed out";
|
||||||
if (listener.topic.startsWith(prefix))
|
this->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->sendText(R"({"type":"PING"})"_ba);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray PubSubClient::encodeSubscription(const Subscription &subscription)
|
||||||
|
{
|
||||||
|
PubSubListenMessage listen({subscription.topic});
|
||||||
|
this->nonces_[listen.nonce] = NonceInfo{
|
||||||
|
.isListen = true,
|
||||||
|
};
|
||||||
|
return listen.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray PubSubClient::encodeUnsubscription(const Subscription &subscription)
|
||||||
|
{
|
||||||
|
PubSubUnlistenMessage unlisten({subscription.topic});
|
||||||
|
this->nonces_[unlisten.nonce] = NonceInfo{
|
||||||
|
.isListen = true,
|
||||||
|
};
|
||||||
|
return unlisten.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PubSubClient::handleResponse(const PubSubMessage &message)
|
||||||
|
{
|
||||||
|
const bool failed = !message.error.isEmpty();
|
||||||
|
|
||||||
|
if (failed)
|
||||||
{
|
{
|
||||||
topics.push_back(listener.topic);
|
qCDebug(chatterinoPubSub)
|
||||||
it = this->listeners_.erase(it);
|
<< "Error" << message.error << "on nonce" << message.nonce;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.nonce.isEmpty())
|
||||||
|
{
|
||||||
|
// Can't do any specific handling since no nonce was specified
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto nonceInfoIt = this->nonces_.find(message.nonce);
|
||||||
|
if (nonceInfoIt == this->nonces_.end())
|
||||||
|
{
|
||||||
|
qCDebug(chatterinoPubSub) << "Unknown nonce:" << message.nonce;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonceInfoIt->second.isListen)
|
||||||
|
{
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
this->manager_.diag.failedListenResponses++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
++it;
|
this->manager_.diag.listenResponses++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (topics.empty())
|
|
||||||
{
|
{
|
||||||
return {{}, ""};
|
this->manager_.diag.unlistenResponses++;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto numRequestedUnlistens = topics.size();
|
this->nonces_.erase(nonceInfoIt);
|
||||||
|
|
||||||
this->numListens_ -= numRequestedUnlistens;
|
|
||||||
DebugCount::increase("PubSub topic pending unlistens",
|
|
||||||
static_cast<int64_t>(numRequestedUnlistens));
|
|
||||||
|
|
||||||
PubSubUnlistenMessage message(topics);
|
|
||||||
|
|
||||||
this->send(message.toJson());
|
|
||||||
|
|
||||||
return {message.topics, message.nonce};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSubClient::handleListenResponse(const PubSubMessage &message)
|
void PubSubClient::handleMessageResponse(const PubSubMessageMessage &message)
|
||||||
{
|
{
|
||||||
}
|
if (!message.topic.startsWith("community-points-channel-v1."))
|
||||||
|
|
||||||
void PubSubClient::handleUnlistenResponse(const PubSubMessage &message)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSubClient::handlePong()
|
|
||||||
{
|
|
||||||
assert(this->awaitingPong_);
|
|
||||||
|
|
||||||
this->awaitingPong_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PubSubClient::isListeningToTopic(const QString &topic)
|
|
||||||
{
|
|
||||||
for (const auto &listener : this->listeners_)
|
|
||||||
{
|
|
||||||
if (listener.topic == topic)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Listener> PubSubClient::getListeners() const
|
|
||||||
{
|
|
||||||
return this->listeners_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSubClient::ping()
|
|
||||||
{
|
|
||||||
assert(this->started_);
|
|
||||||
|
|
||||||
if (this->awaitingPong_)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "No pong response, disconnect!";
|
|
||||||
this->close("Didn't respond to ping");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this->send(PING_PAYLOAD))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this->awaitingPong_ = true;
|
auto oInnerMessage =
|
||||||
|
message.toInner<PubSubCommunityPointsChannelV1Message>();
|
||||||
auto self = this->shared_from_this();
|
if (!oInnerMessage)
|
||||||
|
|
||||||
runAfter(this->heartbeatTimer_, this->clientOptions_.pingInterval_,
|
|
||||||
[self](auto timer) {
|
|
||||||
(void)timer;
|
|
||||||
if (!self->started_)
|
|
||||||
{
|
{
|
||||||
|
qCDebug(chatterinoPubSub)
|
||||||
|
<< "Malformed community-points-channel-v1 message";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->ping();
|
const auto &innerMessage = *oInnerMessage;
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PubSubClient::send(const char *payload)
|
switch (innerMessage.type)
|
||||||
{
|
|
||||||
WebsocketErrorCode ec;
|
|
||||||
this->websocketClient_.send(this->handle_, payload,
|
|
||||||
websocketpp::frame::opcode::text, ec);
|
|
||||||
|
|
||||||
if (ec)
|
|
||||||
{
|
{
|
||||||
qCDebug(chatterinoPubSub) << "Error sending message" << payload << ":"
|
case PubSubCommunityPointsChannelV1Message::Type::
|
||||||
<< ec.message().c_str();
|
AutomaticRewardRedeemed:
|
||||||
// TODO(pajlada): Check which error code happened and maybe
|
case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: {
|
||||||
// gracefully handle it
|
auto redemption = innerMessage.data.value("redemption").toObject();
|
||||||
|
this->manager_.pointReward.redeemed.invoke(redemption);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
return true;
|
case PubSubCommunityPointsChannelV1Message::Type::INVALID:
|
||||||
|
default: {
|
||||||
|
qCDebug(chatterinoPubSub)
|
||||||
|
<< "Invalid point event type:" << innerMessage.typeString;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "providers/twitch/PubSubClientOptions.hpp"
|
#include "providers/liveupdates/BasicPubSubClient.hpp"
|
||||||
#include "providers/twitch/PubSubWebsocket.hpp"
|
#include "providers/twitch/pubsubmessages/Message.hpp"
|
||||||
|
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
@@ -11,67 +11,71 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
struct PubSubMessage;
|
|
||||||
struct PubSubListenMessage;
|
|
||||||
|
|
||||||
struct TopicData {
|
struct TopicData {
|
||||||
QString topic;
|
QString topic;
|
||||||
bool authed{false};
|
|
||||||
bool persistent{false};
|
bool operator==(const TopicData &other) const
|
||||||
|
{
|
||||||
|
return this->topic == other.topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
friend QDebug operator<<(QDebug debug, const TopicData &data);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Listener : TopicData {
|
} // namespace chatterino
|
||||||
bool confirmed{false};
|
|
||||||
|
template <>
|
||||||
|
struct std::hash<chatterino::TopicData> {
|
||||||
|
std::size_t operator()(const chatterino::TopicData &data) const noexcept
|
||||||
|
{
|
||||||
|
return std::hash<QString>{}(data.topic);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class PubSubClient : public std::enable_shared_from_this<PubSubClient>
|
namespace chatterino {
|
||||||
|
|
||||||
|
struct PubSubMessage;
|
||||||
|
class PubSub;
|
||||||
|
struct PubSubListenMessage;
|
||||||
|
|
||||||
|
class PubSubClient : public BasicPubSubClient<TopicData, PubSubClient>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// The max amount of topics we may listen to with a single connection
|
||||||
|
static constexpr size_t MAX_LISTENS = 50;
|
||||||
|
|
||||||
struct UnlistenPrefixResponse {
|
struct UnlistenPrefixResponse {
|
||||||
std::vector<QString> topics;
|
std::vector<QString> topics;
|
||||||
QString nonce;
|
QString nonce;
|
||||||
};
|
};
|
||||||
|
|
||||||
// The max amount of topics we may listen to with a single connection
|
PubSubClient(PubSub &manager, std::chrono::milliseconds heartbeatInterval);
|
||||||
static constexpr std::vector<QString>::size_type MAX_LISTENS = 50;
|
|
||||||
|
|
||||||
PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle,
|
void onOpen() /* override */;
|
||||||
const PubSubClientOptions &clientOptions);
|
void onMessage(const QByteArray &msg) /* override */;
|
||||||
|
|
||||||
void start();
|
void checkHeartbeat();
|
||||||
void stop();
|
|
||||||
|
|
||||||
void close(const std::string &reason,
|
QByteArray encodeSubscription(
|
||||||
websocketpp::close::status::value code =
|
const Subscription &subscription) /* override */;
|
||||||
websocketpp::close::status::normal);
|
QByteArray encodeUnsubscription(
|
||||||
|
const Subscription &subscription) /* override */;
|
||||||
bool listen(const PubSubListenMessage &msg);
|
|
||||||
UnlistenPrefixResponse unlistenPrefix(const QString &prefix);
|
|
||||||
|
|
||||||
void handleListenResponse(const PubSubMessage &message);
|
|
||||||
void handleUnlistenResponse(const PubSubMessage &message);
|
|
||||||
|
|
||||||
void handlePong();
|
|
||||||
|
|
||||||
bool isListeningToTopic(const QString &topic);
|
|
||||||
|
|
||||||
std::vector<Listener> getListeners() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ping();
|
struct NonceInfo {
|
||||||
bool send(const char *payload);
|
bool isListen;
|
||||||
|
};
|
||||||
|
|
||||||
WebsocketClient &websocketClient_;
|
void handleResponse(const PubSubMessage &message);
|
||||||
WebsocketHandle handle_;
|
void handleMessageResponse(const PubSubMessageMessage &message);
|
||||||
uint16_t numListens_ = 0;
|
|
||||||
|
|
||||||
std::vector<Listener> listeners_;
|
std::unordered_map<QString, NonceInfo> nonces_;
|
||||||
|
|
||||||
std::atomic<bool> awaitingPong_{false};
|
std::atomic<std::chrono::time_point<std::chrono::steady_clock>>
|
||||||
std::atomic<bool> started_{false};
|
lastHeartbeat_;
|
||||||
|
std::chrono::milliseconds heartbeatInterval_;
|
||||||
std::shared_ptr<boost::asio::steady_timer> heartbeatTimer_;
|
bool isOpen_ = false;
|
||||||
const PubSubClientOptions &clientOptions_;
|
PubSub &manager_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Options to change the behaviour of the underlying websocket clients
|
|
||||||
**/
|
|
||||||
struct PubSubClientOptions {
|
|
||||||
std::chrono::seconds pingInterval_;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/QLogging.hpp"
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/asio/steady_timer.hpp>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
class TwitchAccount;
|
|
||||||
struct ActionUser;
|
|
||||||
|
|
||||||
// Create timer using given ioService
|
|
||||||
template <typename Duration, typename Callback>
|
|
||||||
void runAfter(boost::asio::io_context &ioc, Duration duration, Callback cb)
|
|
||||||
{
|
|
||||||
auto timer = std::make_shared<boost::asio::steady_timer>(ioc);
|
|
||||||
timer->expires_after(duration);
|
|
||||||
|
|
||||||
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Error in runAfter:" << ec.message().c_str();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(timer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use provided timer
|
|
||||||
template <typename Duration, typename Callback>
|
|
||||||
void runAfter(std::shared_ptr<boost::asio::steady_timer> timer,
|
|
||||||
Duration duration, Callback cb)
|
|
||||||
{
|
|
||||||
timer->expires_after(duration);
|
|
||||||
|
|
||||||
timer->async_wait([timer, cb](const boost::system::error_code &ec) {
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Error in runAfter:" << ec.message().c_str();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(timer);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -2,148 +2,89 @@
|
|||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "common/QLogging.hpp"
|
#include "common/QLogging.hpp"
|
||||||
#include "providers/NetworkConfigurationProvider.hpp"
|
#include "providers/liveupdates/BasicPubSubManager.hpp"
|
||||||
#include "providers/twitch/PubSubClient.hpp"
|
#include "providers/twitch/PubSubClient.hpp"
|
||||||
#include "providers/twitch/PubSubHelpers.hpp"
|
|
||||||
#include "providers/twitch/PubSubMessages.hpp"
|
|
||||||
#include "util/DebugCount.hpp"
|
|
||||||
#include "util/RenameThread.hpp"
|
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QScopeGuard>
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <exception>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <thread>
|
#include <utility>
|
||||||
|
|
||||||
using websocketpp::lib::bind;
|
|
||||||
using websocketpp::lib::placeholders::_1;
|
|
||||||
using websocketpp::lib::placeholders::_2;
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
PubSub::PubSub(const QString &host, std::chrono::seconds pingInterval)
|
class PubSubManagerPrivate
|
||||||
: host_(host)
|
: public BasicPubSubManager<PubSubManagerPrivate, PubSubClient>
|
||||||
, clientOptions_({
|
|
||||||
pingInterval,
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
this->websocketClient.set_access_channels(websocketpp::log::alevel::all);
|
public:
|
||||||
this->websocketClient.clear_access_channels(
|
PubSubManagerPrivate(PubSub &parent, QString host,
|
||||||
websocketpp::log::alevel::frame_payload |
|
std::chrono::milliseconds heartbeatInterval);
|
||||||
websocketpp::log::alevel::frame_header);
|
~PubSubManagerPrivate() override;
|
||||||
|
PubSubManagerPrivate(const PubSubManagerPrivate &) = delete;
|
||||||
|
PubSubManagerPrivate(PubSubManagerPrivate &&) = delete;
|
||||||
|
PubSubManagerPrivate &operator=(const PubSubManagerPrivate &) = delete;
|
||||||
|
PubSubManagerPrivate &operator=(PubSubManagerPrivate &&) = delete;
|
||||||
|
|
||||||
this->websocketClient.init_asio();
|
std::shared_ptr<PubSubClient> makeClient();
|
||||||
|
void checkHeartbeats();
|
||||||
|
|
||||||
// SSL Handshake
|
std::chrono::milliseconds heartbeatInterval;
|
||||||
this->websocketClient.set_tls_init_handler(
|
QTimer heartbeatTimer;
|
||||||
bind(&PubSub::onTLSInit, this, ::_1));
|
|
||||||
|
|
||||||
this->websocketClient.set_message_handler(
|
PubSub &parent;
|
||||||
bind(&PubSub::onMessage, this, ::_1, ::_2));
|
|
||||||
this->websocketClient.set_open_handler(
|
friend BasicPubSubManager<PubSubManagerPrivate, PubSubClient>;
|
||||||
bind(&PubSub::onConnectionOpen, this, ::_1));
|
friend PubSub;
|
||||||
this->websocketClient.set_close_handler(
|
};
|
||||||
bind(&PubSub::onConnectionClose, this, ::_1));
|
|
||||||
this->websocketClient.set_fail_handler(
|
PubSubManagerPrivate::PubSubManagerPrivate(
|
||||||
bind(&PubSub::onConnectionFail, this, ::_1));
|
PubSub &parent, QString host, std::chrono::milliseconds heartbeatInterval)
|
||||||
|
: BasicPubSubManager(std::move(host), "PubSub")
|
||||||
|
, heartbeatInterval(heartbeatInterval)
|
||||||
|
, parent(parent)
|
||||||
|
{
|
||||||
|
QObject::connect(&this->heartbeatTimer, &QTimer::timeout, this,
|
||||||
|
&PubSubManagerPrivate::checkHeartbeats);
|
||||||
|
this->heartbeatTimer.setInterval(this->heartbeatInterval);
|
||||||
|
this->heartbeatTimer.setSingleShot(false);
|
||||||
|
this->heartbeatTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
PubSub::~PubSub()
|
PubSubManagerPrivate::~PubSubManagerPrivate()
|
||||||
{
|
{
|
||||||
this->stop();
|
this->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::initialize()
|
void PubSubManagerPrivate::checkHeartbeats()
|
||||||
{
|
{
|
||||||
this->start();
|
for (const auto &[id, client] : this->clients())
|
||||||
|
{
|
||||||
|
client->checkHeartbeat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::addClient()
|
std::shared_ptr<PubSubClient> PubSubManagerPrivate::makeClient()
|
||||||
{
|
{
|
||||||
if (this->addingClient)
|
return std::make_shared<PubSubClient>(this->parent,
|
||||||
{
|
this->heartbeatInterval);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(chatterinoPubSub) << "Adding an additional client";
|
|
||||||
|
|
||||||
this->addingClient = true;
|
|
||||||
|
|
||||||
websocketpp::lib::error_code ec;
|
|
||||||
auto con =
|
|
||||||
this->websocketClient.get_connection(this->host_.toStdString(), ec);
|
|
||||||
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Unable to establish connection:" << ec.message().c_str();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NetworkConfigurationProvider::applyToWebSocket(con);
|
|
||||||
|
|
||||||
this->websocketClient.connect(con);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::start()
|
PubSub::PubSub(const QString &host, std::chrono::seconds heartbeatInterval)
|
||||||
|
: private_(new PubSubManagerPrivate(*this, host, heartbeatInterval))
|
||||||
{
|
{
|
||||||
this->work = std::make_shared<boost::asio::executor_work_guard<
|
}
|
||||||
boost::asio::io_context::executor_type>>(
|
|
||||||
this->websocketClient.get_io_service().get_executor());
|
|
||||||
this->thread = std::make_unique<std::thread>([this] {
|
|
||||||
// make sure we set in any case, even exceptions
|
|
||||||
auto guard = qScopeGuard([&] {
|
|
||||||
this->stoppedFlag_.set();
|
|
||||||
});
|
|
||||||
|
|
||||||
runThread();
|
PubSub::~PubSub() = default;
|
||||||
});
|
|
||||||
renameThread(*this->thread, "PubSub");
|
const liveupdates::Diag &PubSub::wsDiag() const
|
||||||
|
{
|
||||||
|
return this->private_->diag;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::stop()
|
void PubSub::stop()
|
||||||
{
|
{
|
||||||
this->stopping_ = true;
|
this->private_->stop();
|
||||||
|
|
||||||
for (const auto &[hdl, client] : this->clients)
|
|
||||||
{
|
|
||||||
(void)hdl;
|
|
||||||
|
|
||||||
client->close("Shutting down");
|
|
||||||
}
|
|
||||||
|
|
||||||
this->work.reset();
|
|
||||||
|
|
||||||
if (!this->thread->joinable())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE:
|
|
||||||
// There is a case where a new client was initiated but not added to the clients list.
|
|
||||||
// We just don't join the thread & let the operating system nuke the thread if joining fails
|
|
||||||
// within 1s.
|
|
||||||
// We could fix the underlying bug, but this is easier & we realistically won't use this exact code
|
|
||||||
// for super much longer.
|
|
||||||
if (this->stoppedFlag_.waitFor(std::chrono::milliseconds{100}))
|
|
||||||
{
|
|
||||||
this->thread->join();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCWarning(chatterinoLiveupdates)
|
|
||||||
<< "Thread didn't finish within 100ms, force-stop the client";
|
|
||||||
this->websocketClient.stop();
|
|
||||||
if (this->stoppedFlag_.waitFor(std::chrono::milliseconds{20}))
|
|
||||||
{
|
|
||||||
this->thread->join();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCWarning(chatterinoLiveupdates) << "Thread didn't finish after stopping";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PubSub::listenToChannelPointRewards(const QString &channelID)
|
void PubSub::listenToChannelPointRewards(const QString &channelID)
|
||||||
@@ -153,426 +94,8 @@ void PubSub::listenToChannelPointRewards(const QString &channelID)
|
|||||||
|
|
||||||
auto topic = topicFormat.arg(channelID);
|
auto topic = topicFormat.arg(channelID);
|
||||||
|
|
||||||
if (this->isListeningToTopic(topic))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
qCDebug(chatterinoPubSub) << "Listen to topic" << topic;
|
||||||
|
this->private_->subscribe(TopicData{.topic = std::move(topic)});
|
||||||
this->listenToTopic(topic);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::unlistenChannelPointRewards()
|
|
||||||
{
|
|
||||||
this->unlistenPrefix("community-points-channel-v1.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::unlistenPrefix(const QString &prefix)
|
|
||||||
{
|
|
||||||
for (const auto &p : this->clients)
|
|
||||||
{
|
|
||||||
const auto &client = p.second;
|
|
||||||
if (const auto &[topics, nonce] = client->unlistenPrefix(prefix);
|
|
||||||
!topics.empty())
|
|
||||||
{
|
|
||||||
NonceInfo nonceInfo{
|
|
||||||
client,
|
|
||||||
"UNLISTEN",
|
|
||||||
topics,
|
|
||||||
topics.size(),
|
|
||||||
};
|
|
||||||
this->registerNonce(nonce, nonceInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::listen(PubSubListenMessage msg)
|
|
||||||
{
|
|
||||||
if (this->tryListen(msg))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->addClient();
|
|
||||||
|
|
||||||
std::copy(msg.topics.begin(), msg.topics.end(),
|
|
||||||
std::back_inserter(this->requests));
|
|
||||||
|
|
||||||
DebugCount::increase("PubSub topic backlog", msg.topics.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PubSub::tryListen(PubSubListenMessage msg)
|
|
||||||
{
|
|
||||||
for (const auto &p : this->clients)
|
|
||||||
{
|
|
||||||
const auto &client = p.second;
|
|
||||||
if (auto success = client->listen(msg); success)
|
|
||||||
{
|
|
||||||
this->registerNonce(msg.nonce, {
|
|
||||||
client,
|
|
||||||
"LISTEN",
|
|
||||||
msg.topics,
|
|
||||||
msg.topics.size(),
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::registerNonce(QString nonce, NonceInfo info)
|
|
||||||
{
|
|
||||||
this->nonces_[nonce] = std::move(info);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<PubSub::NonceInfo> PubSub::findNonceInfo(QString nonce)
|
|
||||||
{
|
|
||||||
// TODO: This should also DELETE the nonceinfo from the map
|
|
||||||
auto it = this->nonces_.find(nonce);
|
|
||||||
|
|
||||||
if (it == this->nonces_.end())
|
|
||||||
{
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool PubSub::isListeningToTopic(const QString &topic)
|
|
||||||
{
|
|
||||||
for (const auto &p : this->clients)
|
|
||||||
{
|
|
||||||
const auto &client = p.second;
|
|
||||||
if (client->isListeningToTopic(topic))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::onMessage(websocketpp::connection_hdl hdl,
|
|
||||||
WebsocketMessagePtr websocketMessage)
|
|
||||||
{
|
|
||||||
this->diag.messagesReceived += 1;
|
|
||||||
|
|
||||||
const auto &payload =
|
|
||||||
QString::fromStdString(websocketMessage->get_payload());
|
|
||||||
|
|
||||||
auto oMessage = parsePubSubBaseMessage(payload);
|
|
||||||
|
|
||||||
if (!oMessage)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Unable to parse incoming pubsub message" << payload;
|
|
||||||
this->diag.messagesFailedToParse += 1;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto message = *oMessage;
|
|
||||||
|
|
||||||
switch (message.type)
|
|
||||||
{
|
|
||||||
case PubSubMessage::Type::Pong: {
|
|
||||||
auto clientIt = this->clients.find(hdl);
|
|
||||||
|
|
||||||
// If this assert goes off, there's something wrong with the connection
|
|
||||||
// creation/preserving code KKona
|
|
||||||
assert(clientIt != this->clients.end());
|
|
||||||
|
|
||||||
auto &client = *clientIt;
|
|
||||||
|
|
||||||
client.second->handlePong();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PubSubMessage::Type::Response: {
|
|
||||||
this->handleResponse(message);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PubSubMessage::Type::Message: {
|
|
||||||
auto oMessageMessage = message.toInner<PubSubMessageMessage>();
|
|
||||||
if (!oMessageMessage)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Malformed MESSAGE:" << payload;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->handleMessageResponse(*oMessageMessage);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PubSubMessage::Type::INVALID:
|
|
||||||
default: {
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Unknown message type:" << message.typeString;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::onConnectionOpen(WebsocketHandle hdl)
|
|
||||||
{
|
|
||||||
this->diag.connectionsOpened += 1;
|
|
||||||
|
|
||||||
DebugCount::increase("PubSub connections");
|
|
||||||
this->addingClient = false;
|
|
||||||
|
|
||||||
this->connectBackoff.reset();
|
|
||||||
|
|
||||||
auto client = std::make_shared<PubSubClient>(this->websocketClient, hdl,
|
|
||||||
this->clientOptions_);
|
|
||||||
|
|
||||||
// We separate the starting from the constructor because we will want to use
|
|
||||||
// shared_from_this
|
|
||||||
client->start();
|
|
||||||
|
|
||||||
this->clients.emplace(hdl, client);
|
|
||||||
|
|
||||||
qCDebug(chatterinoPubSub) << "PubSub connection opened!";
|
|
||||||
|
|
||||||
const auto topicsToTake =
|
|
||||||
std::min(this->requests.size(), PubSubClient::MAX_LISTENS);
|
|
||||||
|
|
||||||
std::vector<QString> newTopics(
|
|
||||||
std::make_move_iterator(this->requests.begin()),
|
|
||||||
std::make_move_iterator(this->requests.begin() + topicsToTake));
|
|
||||||
|
|
||||||
this->requests.erase(this->requests.begin(),
|
|
||||||
this->requests.begin() + topicsToTake);
|
|
||||||
|
|
||||||
PubSubListenMessage msg(newTopics);
|
|
||||||
|
|
||||||
if (auto success = client->listen(msg); !success)
|
|
||||||
{
|
|
||||||
qCWarning(chatterinoPubSub) << "Failed to listen to " << topicsToTake
|
|
||||||
<< "new topics on new client";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
DebugCount::decrease("PubSub topic backlog", msg.topics.size());
|
|
||||||
|
|
||||||
this->registerNonce(msg.nonce, {
|
|
||||||
client,
|
|
||||||
"LISTEN",
|
|
||||||
msg.topics,
|
|
||||||
topicsToTake,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!this->requests.empty())
|
|
||||||
{
|
|
||||||
this->addClient();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::onConnectionFail(WebsocketHandle hdl)
|
|
||||||
{
|
|
||||||
this->diag.connectionsFailed += 1;
|
|
||||||
|
|
||||||
DebugCount::increase("PubSub failed connections");
|
|
||||||
if (auto conn = this->websocketClient.get_con_from_hdl(std::move(hdl)))
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "PubSub connection attempt failed (error: "
|
|
||||||
<< conn->get_ec().message().c_str() << ")";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "PubSub connection attempt failed but we can't "
|
|
||||||
"get the connection from a handle.";
|
|
||||||
}
|
|
||||||
|
|
||||||
this->addingClient = false;
|
|
||||||
if (!this->requests.empty())
|
|
||||||
{
|
|
||||||
runAfter(this->websocketClient.get_io_service(),
|
|
||||||
this->connectBackoff.next(), [this](auto timer) {
|
|
||||||
this->addClient(); //
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::onConnectionClose(WebsocketHandle hdl)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Connection closed";
|
|
||||||
this->diag.connectionsClosed += 1;
|
|
||||||
|
|
||||||
DebugCount::decrease("PubSub connections");
|
|
||||||
auto clientIt = this->clients.find(hdl);
|
|
||||||
|
|
||||||
// If this assert goes off, there's something wrong with the connection
|
|
||||||
// creation/preserving code KKona
|
|
||||||
assert(clientIt != this->clients.end());
|
|
||||||
|
|
||||||
auto client = clientIt->second;
|
|
||||||
|
|
||||||
this->clients.erase(clientIt);
|
|
||||||
|
|
||||||
client->stop();
|
|
||||||
|
|
||||||
if (!this->stopping_)
|
|
||||||
{
|
|
||||||
auto clientListeners = client->getListeners();
|
|
||||||
for (const auto &listener : clientListeners)
|
|
||||||
{
|
|
||||||
this->listenToTopic(listener.topic);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
|
|
||||||
{
|
|
||||||
WebsocketContextPtr ctx(
|
|
||||||
new boost::asio::ssl::context(boost::asio::ssl::context::tlsv12));
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
ctx->set_options(boost::asio::ssl::context::default_workarounds |
|
|
||||||
boost::asio::ssl::context::no_sslv2 |
|
|
||||||
boost::asio::ssl::context::single_dh_use);
|
|
||||||
}
|
|
||||||
catch (const std::exception &e)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Exception caught in OnTLSInit:" << e.what();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::handleResponse(const PubSubMessage &message)
|
|
||||||
{
|
|
||||||
const bool failed = !message.error.isEmpty();
|
|
||||||
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Error" << message.error << "on nonce" << message.nonce;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.nonce.isEmpty())
|
|
||||||
{
|
|
||||||
// Can't do any specific handling since no nonce was specified
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto oInfo = this->findNonceInfo(message.nonce); oInfo)
|
|
||||||
{
|
|
||||||
const auto info = *oInfo;
|
|
||||||
auto client = info.client.lock();
|
|
||||||
if (!client)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Client associated with nonce"
|
|
||||||
<< message.nonce << "is no longer alive";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (info.messageType == "LISTEN")
|
|
||||||
{
|
|
||||||
client->handleListenResponse(message);
|
|
||||||
this->handleListenResponse(info, failed);
|
|
||||||
}
|
|
||||||
else if (info.messageType == "UNLISTEN")
|
|
||||||
{
|
|
||||||
client->handleUnlistenResponse(message);
|
|
||||||
this->handleUnlistenResponse(info, failed);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Unhandled nonce message type" << info.messageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qCDebug(chatterinoPubSub) << "Response on unused" << message.nonce
|
|
||||||
<< "client/topic listener mismatch?";
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::handleListenResponse(const NonceInfo &info, bool failed)
|
|
||||||
{
|
|
||||||
DebugCount::decrease("PubSub topic pending listens", info.topicCount);
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
this->diag.failedListenResponses++;
|
|
||||||
DebugCount::increase("PubSub topic failed listens", info.topicCount);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this->diag.listenResponses++;
|
|
||||||
DebugCount::increase("PubSub topic listening", info.topicCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::handleUnlistenResponse(const NonceInfo &info, bool failed)
|
|
||||||
{
|
|
||||||
this->diag.unlistenResponses++;
|
|
||||||
DebugCount::decrease("PubSub topic pending unlistens", info.topicCount);
|
|
||||||
if (failed)
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Failed unlistening to" << info.topics;
|
|
||||||
DebugCount::increase("PubSub topic failed unlistens", info.topicCount);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Successful unlistened to" << info.topics;
|
|
||||||
DebugCount::decrease("PubSub topic listening", info.topicCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::handleMessageResponse(const PubSubMessageMessage &message)
|
|
||||||
{
|
|
||||||
QString topic = message.topic;
|
|
||||||
|
|
||||||
if (topic.startsWith("community-points-channel-v1."))
|
|
||||||
{
|
|
||||||
auto oInnerMessage =
|
|
||||||
message.toInner<PubSubCommunityPointsChannelV1Message>();
|
|
||||||
if (!oInnerMessage)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto innerMessage = *oInnerMessage;
|
|
||||||
|
|
||||||
switch (innerMessage.type)
|
|
||||||
{
|
|
||||||
case PubSubCommunityPointsChannelV1Message::Type::
|
|
||||||
AutomaticRewardRedeemed:
|
|
||||||
case PubSubCommunityPointsChannelV1Message::Type::RewardRedeemed: {
|
|
||||||
auto redemption =
|
|
||||||
innerMessage.data.value("redemption").toObject();
|
|
||||||
this->pointReward.redeemed.invoke(redemption);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PubSubCommunityPointsChannelV1Message::Type::INVALID:
|
|
||||||
default: {
|
|
||||||
qCDebug(chatterinoPubSub)
|
|
||||||
<< "Invalid point event type:" << innerMessage.typeString;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Unknown topic:" << topic;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::runThread()
|
|
||||||
{
|
|
||||||
qCDebug(chatterinoPubSub) << "Start pubsub manager thread";
|
|
||||||
this->websocketClient.run();
|
|
||||||
qCDebug(chatterinoPubSub) << "Done with pubsub manager thread";
|
|
||||||
}
|
|
||||||
|
|
||||||
void PubSub::listenToTopic(const QString &topic)
|
|
||||||
{
|
|
||||||
this->listen(PubSubListenMessage({topic}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
@@ -1,29 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "providers/twitch/PubSubClientOptions.hpp"
|
#include "providers/liveupdates/Diag.hpp"
|
||||||
#include "providers/twitch/PubSubWebsocket.hpp"
|
|
||||||
#include "util/ExponentialBackoff.hpp"
|
|
||||||
#include "util/OnceFlag.hpp"
|
|
||||||
|
|
||||||
#include <boost/asio/io_context.hpp>
|
|
||||||
#include <boost/asio/ssl/context.hpp>
|
|
||||||
#include <pajlada/signals/signal.hpp>
|
#include <pajlada/signals/signal.hpp>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <websocketpp/client.hpp>
|
|
||||||
#include <websocketpp/common/connection_hdl.hpp>
|
|
||||||
#include <websocketpp/common/memory.hpp>
|
|
||||||
#include <websocketpp/config/asio_client.hpp>
|
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
#include <thread>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#if __has_include(<gtest/gtest_prod.h>)
|
#if __has_include(<gtest/gtest_prod.h>)
|
||||||
# include <gtest/gtest_prod.h>
|
# include <gtest/gtest_prod.h>
|
||||||
@@ -31,12 +17,7 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
class TwitchAccount;
|
class PubSubManagerPrivate;
|
||||||
class PubSubClient;
|
|
||||||
|
|
||||||
struct PubSubListenMessage;
|
|
||||||
struct PubSubMessage;
|
|
||||||
struct PubSubMessageMessage;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This handles the Twitch PubSub connection
|
* This handles the Twitch PubSub connection
|
||||||
@@ -47,28 +28,12 @@ struct PubSubMessageMessage;
|
|||||||
*/
|
*/
|
||||||
class PubSub
|
class PubSub
|
||||||
{
|
{
|
||||||
using WebsocketMessagePtr =
|
|
||||||
websocketpp::config::asio_tls_client::message_type::ptr;
|
|
||||||
using WebsocketContextPtr =
|
|
||||||
websocketpp::lib::shared_ptr<boost::asio::ssl::context>;
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
using Signal =
|
using Signal = pajlada::Signals::Signal<T>;
|
||||||
pajlada::Signals::Signal<T>; // type-id is vector<T, Alloc<T>>
|
|
||||||
|
|
||||||
struct NonceInfo {
|
|
||||||
std::weak_ptr<PubSubClient> client;
|
|
||||||
QString messageType; // e.g. LISTEN or UNLISTEN
|
|
||||||
std::vector<QString> topics;
|
|
||||||
std::vector<QString>::size_type topicCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
WebsocketClient websocketClient;
|
|
||||||
std::unique_ptr<std::thread> thread;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PubSub(const QString &host,
|
PubSub(const QString &host,
|
||||||
std::chrono::seconds pingInterval = std::chrono::seconds(15));
|
std::chrono::seconds heartbeatInterval = std::chrono::seconds(15));
|
||||||
~PubSub();
|
~PubSub();
|
||||||
|
|
||||||
PubSub(const PubSub &) = delete;
|
PubSub(const PubSub &) = delete;
|
||||||
@@ -76,9 +41,6 @@ public:
|
|||||||
PubSub &operator=(const PubSub &) = delete;
|
PubSub &operator=(const PubSub &) = delete;
|
||||||
PubSub &operator=(PubSub &&) = delete;
|
PubSub &operator=(PubSub &&) = delete;
|
||||||
|
|
||||||
/// Set up connections between itself & other parts of the application
|
|
||||||
void initialize();
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
Signal<const QJsonObject &> redeemed;
|
Signal<const QJsonObject &> redeemed;
|
||||||
} pointReward;
|
} pointReward;
|
||||||
@@ -90,12 +52,8 @@ public:
|
|||||||
* PubSub topic: community-points-channel-v1.{channelID}
|
* PubSub topic: community-points-channel-v1.{channelID}
|
||||||
*/
|
*/
|
||||||
void listenToChannelPointRewards(const QString &channelID);
|
void listenToChannelPointRewards(const QString &channelID);
|
||||||
void unlistenChannelPointRewards();
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
std::atomic<uint32_t> connectionsClosed{0};
|
|
||||||
std::atomic<uint32_t> connectionsOpened{0};
|
|
||||||
std::atomic<uint32_t> connectionsFailed{0};
|
|
||||||
std::atomic<uint32_t> messagesReceived{0};
|
std::atomic<uint32_t> messagesReceived{0};
|
||||||
std::atomic<uint32_t> messagesFailedToParse{0};
|
std::atomic<uint32_t> messagesFailedToParse{0};
|
||||||
std::atomic<uint32_t> failedListenResponses{0};
|
std::atomic<uint32_t> failedListenResponses{0};
|
||||||
@@ -103,64 +61,15 @@ public:
|
|||||||
std::atomic<uint32_t> unlistenResponses{0};
|
std::atomic<uint32_t> unlistenResponses{0};
|
||||||
} diag;
|
} diag;
|
||||||
|
|
||||||
|
/// Statistics about the opened/closed connections and received messages
|
||||||
|
///
|
||||||
|
/// Used in tests.
|
||||||
|
const liveupdates::Diag &wsDiag() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void start();
|
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
/**
|
std::unique_ptr<PubSubManagerPrivate> private_;
|
||||||
* Unlistens to all topics matching the prefix in all clients
|
|
||||||
*/
|
|
||||||
void unlistenPrefix(const QString &prefix);
|
|
||||||
|
|
||||||
void listenToTopic(const QString &topic);
|
|
||||||
|
|
||||||
void listen(PubSubListenMessage msg);
|
|
||||||
bool tryListen(PubSubListenMessage msg);
|
|
||||||
|
|
||||||
bool isListeningToTopic(const QString &topic);
|
|
||||||
|
|
||||||
void addClient();
|
|
||||||
|
|
||||||
std::vector<QString> requests;
|
|
||||||
|
|
||||||
std::atomic<bool> addingClient{false};
|
|
||||||
ExponentialBackoff<5> connectBackoff{std::chrono::milliseconds(1000)};
|
|
||||||
|
|
||||||
std::map<WebsocketHandle, std::shared_ptr<PubSubClient>,
|
|
||||||
std::owner_less<WebsocketHandle>>
|
|
||||||
clients;
|
|
||||||
|
|
||||||
void onMessage(websocketpp::connection_hdl hdl, WebsocketMessagePtr msg);
|
|
||||||
void onConnectionOpen(websocketpp::connection_hdl hdl);
|
|
||||||
void onConnectionFail(websocketpp::connection_hdl hdl);
|
|
||||||
void onConnectionClose(websocketpp::connection_hdl hdl);
|
|
||||||
WebsocketContextPtr onTLSInit(websocketpp::connection_hdl hdl);
|
|
||||||
|
|
||||||
void handleResponse(const PubSubMessage &message);
|
|
||||||
void handleListenResponse(const NonceInfo &info, bool failed);
|
|
||||||
void handleUnlistenResponse(const NonceInfo &info, bool failed);
|
|
||||||
void handleMessageResponse(const PubSubMessageMessage &message);
|
|
||||||
|
|
||||||
// Register a nonce for a specific client
|
|
||||||
void registerNonce(QString nonce, NonceInfo nonceInfo);
|
|
||||||
|
|
||||||
// Find client associated with a nonce
|
|
||||||
std::optional<NonceInfo> findNonceInfo(QString nonce);
|
|
||||||
|
|
||||||
std::unordered_map<QString, NonceInfo> nonces_;
|
|
||||||
|
|
||||||
void runThread();
|
|
||||||
|
|
||||||
std::shared_ptr<boost::asio::executor_work_guard<
|
|
||||||
boost::asio::io_context::executor_type>>
|
|
||||||
work{nullptr};
|
|
||||||
|
|
||||||
const QString host_;
|
|
||||||
const PubSubClientOptions clientOptions_;
|
|
||||||
|
|
||||||
OnceFlag stoppedFlag_;
|
|
||||||
|
|
||||||
bool stopping_{false};
|
|
||||||
|
|
||||||
#ifdef FRIEND_TEST
|
#ifdef FRIEND_TEST
|
||||||
friend class FTest;
|
friend class FTest;
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "providers/twitch/ChatterinoWebSocketppLogger.hpp"
|
|
||||||
|
|
||||||
#include <websocketpp/client.hpp>
|
|
||||||
#include <websocketpp/config/asio_client.hpp>
|
|
||||||
#include <websocketpp/extensions/permessage_deflate/disabled.hpp>
|
|
||||||
#include <websocketpp/logger/basic.hpp>
|
|
||||||
|
|
||||||
namespace chatterino {
|
|
||||||
|
|
||||||
struct chatterinoconfig : public websocketpp::config::asio_tls_client {
|
|
||||||
typedef websocketpp::log::chatterinowebsocketpplogger<
|
|
||||||
concurrency_type, websocketpp::log::elevel>
|
|
||||||
elog_type;
|
|
||||||
typedef websocketpp::log::chatterinowebsocketpplogger<
|
|
||||||
concurrency_type, websocketpp::log::alevel>
|
|
||||||
alog_type;
|
|
||||||
|
|
||||||
struct permessage_deflate_config {
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef websocketpp::extensions::permessage_deflate::disabled<
|
|
||||||
permessage_deflate_config>
|
|
||||||
permessage_deflate_type;
|
|
||||||
};
|
|
||||||
|
|
||||||
using WebsocketClient = websocketpp::client<chatterinoconfig>;
|
|
||||||
using WebsocketHandle = websocketpp::connection_hdl;
|
|
||||||
using WebsocketErrorCode = websocketpp::lib::error_code;
|
|
||||||
|
|
||||||
} // namespace chatterino
|
|
||||||
@@ -18,9 +18,9 @@ PubSubMessage::PubSubMessage(QJsonObject _object)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<PubSubMessage> parsePubSubBaseMessage(const QString &blob)
|
std::optional<PubSubMessage> parsePubSubBaseMessage(const QByteArray &blob)
|
||||||
{
|
{
|
||||||
QJsonDocument jsonDoc(QJsonDocument::fromJson(blob.toUtf8()));
|
QJsonDocument jsonDoc(QJsonDocument::fromJson(blob));
|
||||||
|
|
||||||
if (jsonDoc.isNull())
|
if (jsonDoc.isNull())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ std::optional<InnerClass> PubSubMessage::toInner()
|
|||||||
return InnerClass{this->nonce, data};
|
return InnerClass{this->nonce, data};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<PubSubMessage> parsePubSubBaseMessage(const QString &blob);
|
std::optional<PubSubMessage> parsePubSubBaseMessage(const QByteArray &blob);
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
||||||
|
|||||||
+6
-10
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
|
using namespace Qt::Literals;
|
||||||
|
|
||||||
/// Sample messages coming from IRC
|
/// Sample messages coming from IRC
|
||||||
|
|
||||||
const QStringList &getSampleCheerMessages()
|
const QStringList &getSampleCheerMessages()
|
||||||
@@ -163,20 +165,14 @@ const QStringList &getSampleEmoteTestMessages()
|
|||||||
|
|
||||||
/// Channel point reward tests
|
/// Channel point reward tests
|
||||||
|
|
||||||
const QString &getSampleChannelRewardMessage()
|
QByteArray getSampleChannelRewardMessage()
|
||||||
{
|
{
|
||||||
static QString str{
|
return R"({"type":"MESSAGE","data":{"topic":"community-points-channel-v1.11148817","message":"{\"type\":\"reward-redeemed\",\"data\":{\"timestamp\":\"2022-11-19T13:36:49.536938653Z\",\"redemption\":{\"id\":\"fd8af65d-3532-4e91-b30e-3995cefe576b\",\"user\":{\"id\":\"11148817\",\"login\":\"pajlada\",\"display_name\":\"pajlada\"},\"channel_id\":\"11148817\",\"redeemed_at\":\"2022-11-19T13:36:49.536938653Z\",\"reward\":{\"id\":\"44c3554e-58bc-4753-bf94-b615931d1072\",\"channel_id\":\"11148817\",\"title\":\"Test\",\"prompt\":\"\",\"cost\":1,\"is_user_input_required\":false,\"is_sub_only\":false,\"image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/11148817/44c3554e-58bc-4753-bf94-b615931d1072/effc017c-861d-410d-abff-8bd98f1937eb/custom-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/11148817/44c3554e-58bc-4753-bf94-b615931d1072/effc017c-861d-410d-abff-8bd98f1937eb/custom-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/11148817/44c3554e-58bc-4753-bf94-b615931d1072/effc017c-861d-410d-abff-8bd98f1937eb/custom-4.png\"},\"default_image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\"},\"background_color\":\"#9147FF\",\"is_enabled\":true,\"is_paused\":false,\"is_in_stock\":true,\"max_per_stream\":{\"is_enabled\":false,\"max_per_stream\":0},\"should_redemptions_skip_request_queue\":false,\"template_id\":null,\"updated_for_indicator_at\":\"2021-03-16T00:40:29.385327044Z\",\"max_per_user_per_stream\":{\"is_enabled\":false,\"max_per_user_per_stream\":0},\"global_cooldown\":{\"is_enabled\":false,\"global_cooldown_seconds\":0},\"redemptions_redeemed_current_stream\":null,\"cooldown_expires_at\":null},\"status\":\"UNFULFILLED\",\"cursor\":\"ZmQ4YWY2NWQtMzUzMi00ZTkxLWIzMGUtMzk5NWNlZmU1NzZiX18yMDIyLTExLTE5VDEzOjM2OjQ5LjUzNjkzODY1M1o=\"}}}"}})"_ba;
|
||||||
R"({"type":"MESSAGE","data":{"topic":"community-points-channel-v1.11148817","message":"{\"type\":\"reward-redeemed\",\"data\":{\"timestamp\":\"2022-11-19T13:36:49.536938653Z\",\"redemption\":{\"id\":\"fd8af65d-3532-4e91-b30e-3995cefe576b\",\"user\":{\"id\":\"11148817\",\"login\":\"pajlada\",\"display_name\":\"pajlada\"},\"channel_id\":\"11148817\",\"redeemed_at\":\"2022-11-19T13:36:49.536938653Z\",\"reward\":{\"id\":\"44c3554e-58bc-4753-bf94-b615931d1072\",\"channel_id\":\"11148817\",\"title\":\"Test\",\"prompt\":\"\",\"cost\":1,\"is_user_input_required\":false,\"is_sub_only\":false,\"image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/11148817/44c3554e-58bc-4753-bf94-b615931d1072/effc017c-861d-410d-abff-8bd98f1937eb/custom-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/11148817/44c3554e-58bc-4753-bf94-b615931d1072/effc017c-861d-410d-abff-8bd98f1937eb/custom-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/11148817/44c3554e-58bc-4753-bf94-b615931d1072/effc017c-861d-410d-abff-8bd98f1937eb/custom-4.png\"},\"default_image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\"},\"background_color\":\"#9147FF\",\"is_enabled\":true,\"is_paused\":false,\"is_in_stock\":true,\"max_per_stream\":{\"is_enabled\":false,\"max_per_stream\":0},\"should_redemptions_skip_request_queue\":false,\"template_id\":null,\"updated_for_indicator_at\":\"2021-03-16T00:40:29.385327044Z\",\"max_per_user_per_stream\":{\"is_enabled\":false,\"max_per_user_per_stream\":0},\"global_cooldown\":{\"is_enabled\":false,\"global_cooldown_seconds\":0},\"redemptions_redeemed_current_stream\":null,\"cooldown_expires_at\":null},\"status\":\"UNFULFILLED\",\"cursor\":\"ZmQ4YWY2NWQtMzUzMi00ZTkxLWIzMGUtMzk5NWNlZmU1NzZiX18yMDIyLTExLTE5VDEzOjM2OjQ5LjUzNjkzODY1M1o=\"}}}"}})",
|
|
||||||
};
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &getSampleChannelRewardMessage2()
|
QByteArray getSampleChannelRewardMessage2()
|
||||||
{
|
{
|
||||||
static QString str{
|
return R"({"type":"MESSAGE","data":{"topic":"community-points-channel-v1.11148817","message":"{\"type\":\"reward-redeemed\",\"data\":{\"timestamp\":\"2022-11-19T13:37:55.860377616Z\",\"redemption\":{\"id\":\"e8712d29-7564-40e9-a972-d70997665605\",\"user\":{\"id\":\"11148817\",\"login\":\"pajlada\",\"display_name\":\"pajlada\"},\"channel_id\":\"11148817\",\"redeemed_at\":\"2022-11-19T13:37:55.860377616Z\",\"reward\":{\"id\":\"649ecb1f-3fa8-491e-a48d-bd50720929d9\",\"channel_id\":\"11148817\",\"title\":\"asdd\",\"prompt\":\"asd\",\"cost\":5,\"is_user_input_required\":true,\"is_sub_only\":false,\"image\":null,\"default_image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\"},\"background_color\":\"#0013A3\",\"is_enabled\":true,\"is_paused\":false,\"is_in_stock\":true,\"max_per_stream\":{\"is_enabled\":false,\"max_per_stream\":0},\"should_redemptions_skip_request_queue\":false,\"template_id\":null,\"updated_for_indicator_at\":\"2021-03-16T00:45:25.236177469Z\",\"max_per_user_per_stream\":{\"is_enabled\":false,\"max_per_user_per_stream\":0},\"global_cooldown\":{\"is_enabled\":false,\"global_cooldown_seconds\":0},\"redemptions_redeemed_current_stream\":null,\"cooldown_expires_at\":null},\"user_input\":\"TEST\",\"status\":\"UNFULFILLED\",\"cursor\":\"ZTg3MTJkMjktNzU2NC00MGU5LWE5NzItZDcwOTk3NjY1NjA1X18yMDIyLTExLTE5VDEzOjM3OjU1Ljg2MDM3NzYxNlo=\"}}}"}})"_ba;
|
||||||
R"({"type":"MESSAGE","data":{"topic":"community-points-channel-v1.11148817","message":"{\"type\":\"reward-redeemed\",\"data\":{\"timestamp\":\"2022-11-19T13:37:55.860377616Z\",\"redemption\":{\"id\":\"e8712d29-7564-40e9-a972-d70997665605\",\"user\":{\"id\":\"11148817\",\"login\":\"pajlada\",\"display_name\":\"pajlada\"},\"channel_id\":\"11148817\",\"redeemed_at\":\"2022-11-19T13:37:55.860377616Z\",\"reward\":{\"id\":\"649ecb1f-3fa8-491e-a48d-bd50720929d9\",\"channel_id\":\"11148817\",\"title\":\"asdd\",\"prompt\":\"asd\",\"cost\":5,\"is_user_input_required\":true,\"is_sub_only\":false,\"image\":null,\"default_image\":{\"url_1x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-1.png\",\"url_2x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-2.png\",\"url_4x\":\"https://static-cdn.jtvnw.net/custom-reward-images/default-4.png\"},\"background_color\":\"#0013A3\",\"is_enabled\":true,\"is_paused\":false,\"is_in_stock\":true,\"max_per_stream\":{\"is_enabled\":false,\"max_per_stream\":0},\"should_redemptions_skip_request_queue\":false,\"template_id\":null,\"updated_for_indicator_at\":\"2021-03-16T00:45:25.236177469Z\",\"max_per_user_per_stream\":{\"is_enabled\":false,\"max_per_user_per_stream\":0},\"global_cooldown\":{\"is_enabled\":false,\"global_cooldown_seconds\":0},\"redemptions_redeemed_current_stream\":null,\"cooldown_expires_at\":null},\"user_input\":\"TEST\",\"status\":\"UNFULFILLED\",\"cursor\":\"ZTg3MTJkMjktNzU2NC00MGU5LWE5NzItZDcwOTk3NjY1NjA1X18yMDIyLTExLTE5VDEzOjM3OjU1Ljg2MDM3NzYxNlo=\"}}}"}})",
|
|
||||||
};
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString &getSampleChannelRewardIRCMessage()
|
const QString &getSampleChannelRewardIRCMessage()
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ const QStringList &getSampleEmoteTestMessages();
|
|||||||
|
|
||||||
/// Channel point reward tests
|
/// Channel point reward tests
|
||||||
|
|
||||||
const QString &getSampleChannelRewardMessage();
|
QByteArray getSampleChannelRewardMessage();
|
||||||
const QString &getSampleChannelRewardMessage2();
|
QByteArray getSampleChannelRewardMessage2();
|
||||||
const QString &getSampleChannelRewardIRCMessage();
|
const QString &getSampleChannelRewardIRCMessage();
|
||||||
|
|
||||||
/// Links
|
/// Links
|
||||||
|
|||||||
@@ -98,9 +98,6 @@ AboutPage::AboutPage()
|
|||||||
addLicense(form.getElement(), "Pajlada/Serialize",
|
addLicense(form.getElement(), "Pajlada/Serialize",
|
||||||
"https://github.com/pajlada/serialize",
|
"https://github.com/pajlada/serialize",
|
||||||
":/licenses/pajlada_serialize.txt");
|
":/licenses/pajlada_serialize.txt");
|
||||||
addLicense(form.getElement(), "Websocketpp",
|
|
||||||
"https://www.zaphoyd.com/websocketpp/",
|
|
||||||
":/licenses/websocketpp.txt");
|
|
||||||
#ifndef NO_QTKEYCHAIN
|
#ifndef NO_QTKEYCHAIN
|
||||||
addLicense(form.getElement(), "QtKeychain",
|
addLicense(form.getElement(), "QtKeychain",
|
||||||
"https://github.com/frankosterfeld/qtkeychain",
|
"https://github.com/frankosterfeld/qtkeychain",
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ struct hash<DummySubscription> {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
class MyManager;
|
class MyManager;
|
||||||
class MyClient : public BasicPubSubClient<DummySubscription>
|
class MyClient : public BasicPubSubClient<DummySubscription, MyClient>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MyClient(MyManager &manager)
|
MyClient(MyManager &manager)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include "mocks/BaseApplication.hpp"
|
#include "mocks/BaseApplication.hpp"
|
||||||
#include "providers/twitch/PubSubClient.hpp"
|
#include "providers/twitch/PubSubClient.hpp"
|
||||||
#include "providers/twitch/PubSubManager.hpp"
|
#include "providers/twitch/PubSubManager.hpp"
|
||||||
#include "providers/twitch/TwitchAccount.hpp"
|
|
||||||
#include "Test.hpp"
|
#include "Test.hpp"
|
||||||
|
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QtCore/qtestsupport_core.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -107,38 +107,40 @@ TEST(TwitchPubSubClient, ServerRespondsToPings)
|
|||||||
MockApplication a("", 1s);
|
MockApplication a("", 1s);
|
||||||
auto &pubSub = a.pubSub;
|
auto &pubSub = a.pubSub;
|
||||||
|
|
||||||
pubSub.start();
|
QTest::qWait(50);
|
||||||
|
|
||||||
std::this_thread::sleep_for(50ms);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 0);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||||
|
|
||||||
pubSub.listenToChannelPointRewards("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
|
|
||||||
std::this_thread::sleep_for(150ms);
|
QTest::qWait(150);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 1); // LISTEN
|
||||||
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
||||||
|
|
||||||
std::this_thread::sleep_for(2s);
|
QTest::qWait(2 * 1000);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 4);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 3); // LISTEN + 2 * PONG
|
||||||
|
|
||||||
pubSub.stop();
|
pubSub.stop();
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
// after exactly one event loop iteration, we should see updated counters
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 4);
|
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 1);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 1);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
|
ASSERT_EQ(pubSub.diag.messagesReceived, 3);
|
||||||
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,28 +149,31 @@ TEST(TwitchPubSubClient, ServerDoesntRespondToPings)
|
|||||||
MockApplication a("/dont-respond-to-ping", 1s);
|
MockApplication a("/dont-respond-to-ping", 1s);
|
||||||
auto &pubSub = a.pubSub;
|
auto &pubSub = a.pubSub;
|
||||||
|
|
||||||
pubSub.start();
|
|
||||||
pubSub.listenToChannelPointRewards("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
|
|
||||||
std::this_thread::sleep_for(750ms);
|
QTest::qWait(750);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 1);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 1);
|
||||||
|
|
||||||
std::this_thread::sleep_for(500ms);
|
QTest::qWait(1500); // we need to wait for two rounds of pings
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 2);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
||||||
|
|
||||||
pubSub.stop();
|
pubSub.stop();
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
// after exactly one event loop iteration, we should see updated counters
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 2);
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||||
|
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 2);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 2);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,33 +182,31 @@ TEST(TwitchPubSubClient, DisconnectedAfter1s)
|
|||||||
MockApplication a("/disconnect-client-after-1s", 10s);
|
MockApplication a("/disconnect-client-after-1s", 10s);
|
||||||
auto &pubSub = a.pubSub;
|
auto &pubSub = a.pubSub;
|
||||||
|
|
||||||
pubSub.start();
|
QTest::qWait(50);
|
||||||
|
|
||||||
std::this_thread::sleep_for(50ms);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 0);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||||
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
ASSERT_EQ(pubSub.diag.listenResponses, 0);
|
||||||
|
|
||||||
pubSub.listenToChannelPointRewards("123456");
|
pubSub.listenToChannelPointRewards("123456");
|
||||||
|
|
||||||
std::this_thread::sleep_for(500ms);
|
QTest::qWait(500);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 2); // Listen RESPONSE & Pong
|
ASSERT_EQ(pubSub.diag.messagesReceived, 1); // Listen RESPONSE
|
||||||
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
ASSERT_EQ(pubSub.diag.listenResponses, 1);
|
||||||
|
|
||||||
std::this_thread::sleep_for(950ms);
|
QTest::qWait(950);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 2);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.listenResponses, 2);
|
ASSERT_EQ(pubSub.diag.listenResponses, 2);
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 4); // new listen & new pong
|
ASSERT_EQ(pubSub.diag.messagesReceived, 2); // new listen
|
||||||
|
|
||||||
pubSub.stop();
|
pubSub.stop();
|
||||||
}
|
}
|
||||||
@@ -213,11 +216,9 @@ TEST(TwitchPubSubClient, ExceedTopicLimit)
|
|||||||
MockApplication a("", 1s);
|
MockApplication a("", 1s);
|
||||||
auto &pubSub = a.pubSub;
|
auto &pubSub = a.pubSub;
|
||||||
|
|
||||||
pubSub.start();
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 0);
|
||||||
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
for (size_t i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
||||||
@@ -225,58 +226,32 @@ TEST(TwitchPubSubClient, ExceedTopicLimit)
|
|||||||
pubSub.listenToChannelPointRewards(QString("1%1").arg(i));
|
pubSub.listenToChannelPointRewards(QString("1%1").arg(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(50ms);
|
QTest::qWait(100);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 1);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 1);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
|
|
||||||
for (size_t i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
for (size_t i = 0; i < PubSubClient::MAX_LISTENS; ++i)
|
||||||
{
|
{
|
||||||
pubSub.listenToChannelPointRewards(QString("2%1").arg(i));
|
pubSub.listenToChannelPointRewards(QString("2%1").arg(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::this_thread::sleep_for(50ms);
|
QTest::qWait(200);
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 2);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 0);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
|
|
||||||
pubSub.stop();
|
pubSub.stop();
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
// after exactly one event loop iteration, we should see updated counters
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 2);
|
QCoreApplication::processEvents(QEventLoop::AllEvents);
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
|
||||||
}
|
|
||||||
|
|
||||||
TEST(TwitchPubSubClient, ExceedTopicLimitSingleStep)
|
ASSERT_EQ(pubSub.wsDiag().connectionsOpened, 2);
|
||||||
{
|
ASSERT_EQ(pubSub.wsDiag().connectionsClosed, 2);
|
||||||
MockApplication a("", 1s);
|
ASSERT_EQ(pubSub.wsDiag().connectionsFailed, 0);
|
||||||
auto &pubSub = a.pubSub;
|
|
||||||
|
|
||||||
pubSub.start();
|
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.messagesReceived, 0);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < PubSubClient::MAX_LISTENS * 2; ++i)
|
|
||||||
{
|
|
||||||
pubSub.listenToChannelPointRewards("123456");
|
|
||||||
}
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(150ms);
|
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 0);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
|
||||||
|
|
||||||
pubSub.stop();
|
|
||||||
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsOpened, 2);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsClosed, 2);
|
|
||||||
ASSERT_EQ(pubSub.diag.connectionsFailed, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|||||||
Reference in New Issue
Block a user