fix(eventsub): check keepalive (#6058)
This commit is contained in:
+1
-1
@@ -45,7 +45,7 @@
|
|||||||
- Bugfix: Fixed a thick border on Windows 11. (#5836)
|
- Bugfix: Fixed a thick border on Windows 11. (#5836)
|
||||||
- Bugfix: Fixed some windows not immediately closing. (#6054)
|
- Bugfix: Fixed some windows not immediately closing. (#6054)
|
||||||
- Dev: Subscriptions to PubSub channel points redemption topics now use no auth token, making it continue to work during PubSub shutdown. (#5947)
|
- Dev: Subscriptions to PubSub channel points redemption topics now use no auth token, making it continue to work during PubSub shutdown. (#5947)
|
||||||
- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916, #5930, #5935, #5932, #5943, #5952, #5953, #5968, #5973, #5974, #5980, #5981, #5985, #5990, #5992, #5993, #5996, #5995, #6000, #6001, #6002, #6003, #6005, #6007, #6010, #6008, #6012, #6013, #6015, #6017, #6027, #6028, #6035, #6036, #6040, #6041, #6048)
|
- Dev: Add initial experimental EventSub support. (#5837, #5895, #5897, #5904, #5910, #5903, #5915, #5916, #5930, #5935, #5932, #5943, #5952, #5953, #5968, #5973, #5974, #5980, #5981, #5985, #5990, #5992, #5993, #5996, #5995, #6000, #6001, #6002, #6003, #6005, #6007, #6010, #6008, #6012, #6013, #6015, #6017, #6027, #6028, #6035, #6036, #6040, #6041, #6048, #6058)
|
||||||
- Dev: Remove unneeded platform specifier for toasts. (#5914)
|
- Dev: Remove unneeded platform specifier for toasts. (#5914)
|
||||||
- Dev: Cleanly shutdown on `SIGINT`/`SIGTERM` on Linux & macOS. (#6053)
|
- Dev: Cleanly shutdown on `SIGINT`/`SIGTERM` on Linux & macOS. (#6053)
|
||||||
- Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784)
|
- Dev: Highlight checks now use non-capturing groups for the boundaries. (#5784)
|
||||||
|
|||||||
@@ -171,12 +171,16 @@ void BM_ParseAndHandleMessages(benchmark::State &state)
|
|||||||
auto messages = readMessages();
|
auto messages = readMessages();
|
||||||
|
|
||||||
std::unique_ptr<Listener> listener = std::make_unique<NoopListener>();
|
std::unique_ptr<Listener> listener = std::make_unique<NoopListener>();
|
||||||
|
boost::asio::io_context ioc;
|
||||||
|
boost::asio::ssl::context ssl(
|
||||||
|
boost::asio::ssl::context::method::tls_client);
|
||||||
|
Session sess(ioc, ssl, std::move(listener));
|
||||||
|
|
||||||
for (auto _ : state)
|
for (auto _ : state)
|
||||||
{
|
{
|
||||||
for (const auto &msg : messages)
|
for (const auto &msg : messages)
|
||||||
{
|
{
|
||||||
boost::system::error_code ec = handleMessage(listener, msg);
|
boost::system::error_code ec = sess.handleMessage(msg);
|
||||||
assert(!ec);
|
assert(!ec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ namespace chatterino::eventsub::lib::payload::session_welcome {
|
|||||||
/// json_inner=session
|
/// json_inner=session
|
||||||
struct Payload {
|
struct Payload {
|
||||||
std::string id;
|
std::string id;
|
||||||
std::optional<std::string> reconnectURL;
|
std::optional<std::string> reconnectURL; // null in welcome message
|
||||||
|
std::optional<int> keepaliveTimeoutSeconds; // null in reconnect message
|
||||||
};
|
};
|
||||||
|
|
||||||
#include "twitch-eventsub-ws/payloads/session-welcome.inc"
|
#include "twitch-eventsub-ws/payloads/session-welcome.inc"
|
||||||
|
|||||||
@@ -7,21 +7,16 @@
|
|||||||
#include <boost/beast/websocket/ssl.hpp>
|
#include <boost/beast/websocket/ssl.hpp>
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
|
|
||||||
|
namespace chatterino::eventsub::lib::messages {
|
||||||
|
|
||||||
|
struct Metadata;
|
||||||
|
|
||||||
|
} // namespace chatterino::eventsub::lib::messages
|
||||||
|
|
||||||
namespace chatterino::eventsub::lib {
|
namespace chatterino::eventsub::lib {
|
||||||
|
|
||||||
class Listener;
|
class Listener;
|
||||||
|
|
||||||
/**
|
|
||||||
* handleMessage takes the incoming message in the buffer, parses it
|
|
||||||
* as JSON then forwards it to the listener, if applicable.
|
|
||||||
*
|
|
||||||
* This is called from the Session, and is only provided if you are interested
|
|
||||||
* in building your own boost asio framework thing
|
|
||||||
**/
|
|
||||||
boost::system::error_code handleMessage(
|
|
||||||
std::unique_ptr<Listener> &listener,
|
|
||||||
const boost::beast::flat_buffer &buffer);
|
|
||||||
|
|
||||||
class Session : public std::enable_shared_from_this<Session>
|
class Session : public std::enable_shared_from_this<Session>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -38,13 +33,17 @@ public:
|
|||||||
|
|
||||||
Listener *getListener();
|
Listener *getListener();
|
||||||
|
|
||||||
|
// public for testing
|
||||||
|
boost::system::error_code handleMessage(
|
||||||
|
const boost::beast::flat_buffer &buffer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onResolve(boost::beast::error_code ec,
|
void onResolve(boost::beast::error_code ec,
|
||||||
boost::asio::ip::tcp::resolver::results_type results);
|
const boost::asio::ip::tcp::resolver::results_type &results);
|
||||||
|
|
||||||
void onConnect(
|
void onConnect(
|
||||||
boost::beast::error_code ec,
|
boost::beast::error_code ec,
|
||||||
boost::asio::ip::tcp::resolver::results_type::endpoint_type ep);
|
const boost::asio::ip::tcp::resolver::results_type::endpoint_type &ep);
|
||||||
|
|
||||||
void onSSLHandshake(boost::beast::error_code ec);
|
void onSSLHandshake(boost::beast::error_code ec);
|
||||||
|
|
||||||
@@ -56,6 +55,14 @@ private:
|
|||||||
|
|
||||||
void fail(boost::beast::error_code ec, std::string_view op);
|
void fail(boost::beast::error_code ec, std::string_view op);
|
||||||
|
|
||||||
|
boost::system::error_code onSessionWelcome(
|
||||||
|
const messages::Metadata &metadata, const boost::json::value &jv);
|
||||||
|
boost::system::error_code onSessionReconnect(const boost::json::value &jv);
|
||||||
|
boost::system::error_code onNotification(const messages::Metadata &metadata,
|
||||||
|
const boost::json::value &jv);
|
||||||
|
|
||||||
|
void checkKeepalive();
|
||||||
|
|
||||||
boost::asio::ip::tcp::resolver resolver;
|
boost::asio::ip::tcp::resolver resolver;
|
||||||
boost::beast::websocket::stream<
|
boost::beast::websocket::stream<
|
||||||
boost::beast::ssl_stream<boost::beast::tcp_stream>>
|
boost::beast::ssl_stream<boost::beast::tcp_stream>>
|
||||||
@@ -66,6 +73,10 @@ private:
|
|||||||
std::string path;
|
std::string path;
|
||||||
std::string userAgent;
|
std::string userAgent;
|
||||||
std::unique_ptr<Listener> listener;
|
std::unique_ptr<Listener> listener;
|
||||||
|
|
||||||
|
std::chrono::seconds keepaliveTimeout{0};
|
||||||
|
bool receivedMessage = false;
|
||||||
|
std::unique_ptr<boost::asio::system_timer> keepaliveTimer;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace chatterino::eventsub::lib
|
} // namespace chatterino::eventsub::lib
|
||||||
|
|||||||
@@ -56,9 +56,29 @@ boost::json::result_for<Payload, boost::json::value>::type tag_invoke(
|
|||||||
reconnectURL = std::move(treconnectURL.value());
|
reconnectURL = std::move(treconnectURL.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static_assert(
|
||||||
|
std::is_trivially_copyable_v<std::remove_reference_t<
|
||||||
|
decltype(std::declval<Payload>().keepaliveTimeoutSeconds)>>);
|
||||||
|
std::optional<int> keepaliveTimeoutSeconds = std::nullopt;
|
||||||
|
const auto *jvkeepaliveTimeoutSeconds =
|
||||||
|
root.if_contains("keepalive_timeout_seconds");
|
||||||
|
if (jvkeepaliveTimeoutSeconds != nullptr &&
|
||||||
|
!jvkeepaliveTimeoutSeconds->is_null())
|
||||||
|
{
|
||||||
|
auto tkeepaliveTimeoutSeconds =
|
||||||
|
boost::json::try_value_to<int>(*jvkeepaliveTimeoutSeconds);
|
||||||
|
|
||||||
|
if (tkeepaliveTimeoutSeconds.has_error())
|
||||||
|
{
|
||||||
|
return tkeepaliveTimeoutSeconds.error();
|
||||||
|
}
|
||||||
|
keepaliveTimeoutSeconds = tkeepaliveTimeoutSeconds.value();
|
||||||
|
}
|
||||||
|
|
||||||
return Payload{
|
return Payload{
|
||||||
.id = std::move(id.value()),
|
.id = std::move(id.value()),
|
||||||
.reconnectURL = std::move(reconnectURL),
|
.reconnectURL = std::move(reconnectURL),
|
||||||
|
.keepaliveTimeoutSeconds = keepaliveTimeoutSeconds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,9 @@
|
|||||||
#include <boost/container_hash/hash.hpp>
|
#include <boost/container_hash/hash.hpp>
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sstream>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace beast = boost::beast;
|
namespace beast = boost::beast;
|
||||||
@@ -37,12 +35,6 @@ using NotificationHandlers = std::unordered_map<
|
|||||||
std::unique_ptr<Listener> &)>,
|
std::unique_ptr<Listener> &)>,
|
||||||
boost::hash<EventSubSubscription>>;
|
boost::hash<EventSubSubscription>>;
|
||||||
|
|
||||||
using MessageHandlers = std::unordered_map<
|
|
||||||
std::string,
|
|
||||||
std::function<boost::system::error_code(
|
|
||||||
const messages::Metadata &, const boost::json::value &,
|
|
||||||
std::unique_ptr<Listener> &, const NotificationHandlers &)>>;
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
@@ -234,124 +226,8 @@ namespace {
|
|||||||
// Add your new subscription types above this line
|
// Add your new subscription types above this line
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageHandlers MESSAGE_HANDLERS{
|
|
||||||
{
|
|
||||||
"session_welcome",
|
|
||||||
[](const auto &metadata, const auto &jv, auto &listener,
|
|
||||||
const auto & /*notificationHandlers*/) {
|
|
||||||
auto oPayload =
|
|
||||||
parsePayload<payload::session_welcome::Payload>(jv);
|
|
||||||
if (!oPayload)
|
|
||||||
{
|
|
||||||
// TODO: error handling
|
|
||||||
return oPayload.error();
|
|
||||||
}
|
|
||||||
const auto &payload = *oPayload;
|
|
||||||
|
|
||||||
listener->onSessionWelcome(metadata, payload);
|
|
||||||
return boost::system::error_code{};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"session_keepalive",
|
|
||||||
[](const auto &metadata, const auto &jv, auto &listener,
|
|
||||||
const auto ¬ificationHandlers) {
|
|
||||||
// TODO: should we do something here?
|
|
||||||
return boost::system::error_code{};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{"session_reconnect",
|
|
||||||
[](const auto & /*metadata*/, const auto &jv, auto &listener,
|
|
||||||
const auto & /*notificationHandlers*/) {
|
|
||||||
auto oPayload =
|
|
||||||
parsePayload<payload::session_welcome::Payload>(jv);
|
|
||||||
if (!oPayload)
|
|
||||||
{
|
|
||||||
return oPayload.error();
|
|
||||||
}
|
|
||||||
const auto &payload = *oPayload;
|
|
||||||
auto *listenerPtr = listener.get();
|
|
||||||
|
|
||||||
listenerPtr->onClose(std::move(listener), payload.reconnectURL);
|
|
||||||
return boost::system::error_code{};
|
|
||||||
}},
|
|
||||||
{
|
|
||||||
"notification",
|
|
||||||
[](const auto &metadata, const auto &jv, auto &listener,
|
|
||||||
const auto ¬ificationHandlers) {
|
|
||||||
listener->onNotification(metadata, jv);
|
|
||||||
|
|
||||||
if (!metadata.subscriptionType || !metadata.subscriptionVersion)
|
|
||||||
{
|
|
||||||
// TODO: error handling
|
|
||||||
return boost::system::error_code{};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto it =
|
|
||||||
notificationHandlers.find({*metadata.subscriptionType,
|
|
||||||
*metadata.subscriptionVersion});
|
|
||||||
if (it == notificationHandlers.end())
|
|
||||||
{
|
|
||||||
EVENTSUB_BAIL_HERE(error::Kind::NoMessageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
return it->second(metadata, jv, listener);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
boost::system::error_code handleMessage(std::unique_ptr<Listener> &listener,
|
|
||||||
const beast::flat_buffer &buffer)
|
|
||||||
{
|
|
||||||
boost::system::error_code parseError;
|
|
||||||
auto jv =
|
|
||||||
boost::json::parse(beast::buffers_to_string(buffer.data()), parseError);
|
|
||||||
if (parseError)
|
|
||||||
{
|
|
||||||
// TODO: wrap error?
|
|
||||||
return parseError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto *jvObject = jv.if_object();
|
|
||||||
if (jvObject == nullptr)
|
|
||||||
{
|
|
||||||
EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto *metadataV = jvObject->if_contains("metadata");
|
|
||||||
if (metadataV == nullptr)
|
|
||||||
{
|
|
||||||
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
|
||||||
}
|
|
||||||
auto metadataResult =
|
|
||||||
boost::json::try_value_to<messages::Metadata>(*metadataV);
|
|
||||||
if (metadataResult.has_error())
|
|
||||||
{
|
|
||||||
// TODO: wrap error?
|
|
||||||
return metadataResult.error();
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto &metadata = metadataResult.value();
|
|
||||||
|
|
||||||
auto handler = MESSAGE_HANDLERS.find(metadata.messageType);
|
|
||||||
|
|
||||||
if (handler == MESSAGE_HANDLERS.end())
|
|
||||||
{
|
|
||||||
EVENTSUB_BAIL_HERE(error::Kind::NoMessageHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto *payloadV = jvObject->if_contains("payload");
|
|
||||||
if (payloadV == nullptr)
|
|
||||||
{
|
|
||||||
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler->second(metadata, *payloadV, listener,
|
|
||||||
NOTIFICATION_HANDLERS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolver and socket require an io_context
|
// Resolver and socket require an io_context
|
||||||
Session::Session(boost::asio::io_context &ioc, boost::asio::ssl::context &ctx,
|
Session::Session(boost::asio::io_context &ioc, boost::asio::ssl::context &ctx,
|
||||||
std::unique_ptr<Listener> listener)
|
std::unique_ptr<Listener> listener)
|
||||||
@@ -392,8 +268,9 @@ Listener *Session::getListener()
|
|||||||
return this->listener.get();
|
return this->listener.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::onResolve(beast::error_code ec,
|
void Session::onResolve(
|
||||||
boost::asio::ip::tcp::resolver::results_type results)
|
beast::error_code ec,
|
||||||
|
const boost::asio::ip::tcp::resolver::results_type &results)
|
||||||
{
|
{
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
@@ -412,7 +289,7 @@ void Session::onResolve(beast::error_code ec,
|
|||||||
|
|
||||||
void Session::onConnect(
|
void Session::onConnect(
|
||||||
beast::error_code ec,
|
beast::error_code ec,
|
||||||
boost::asio::ip::tcp::resolver::results_type::endpoint_type ep)
|
const boost::asio::ip::tcp::resolver::results_type::endpoint_type &ep)
|
||||||
{
|
{
|
||||||
if (ec)
|
if (ec)
|
||||||
{
|
{
|
||||||
@@ -500,7 +377,8 @@ void Session::onRead(beast::error_code ec, std::size_t bytes_transferred)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto messageError = handleMessage(this->listener, this->buffer);
|
this->receivedMessage = true;
|
||||||
|
auto messageError = this->handleMessage(this->buffer);
|
||||||
if (messageError)
|
if (messageError)
|
||||||
{
|
{
|
||||||
this->fail(messageError, "handleMessage");
|
this->fail(messageError, "handleMessage");
|
||||||
@@ -543,8 +421,161 @@ void Session::fail(beast::error_code ec, std::string_view op)
|
|||||||
std::cerr << op << ": " << ec.message() << " (" << ec.location() << ")\n";
|
std::cerr << op << ": " << ec.message() << " (" << ec.location() << ")\n";
|
||||||
if (!this->ws.is_open() && this->listener)
|
if (!this->ws.is_open() && this->listener)
|
||||||
{
|
{
|
||||||
|
if (this->keepaliveTimer)
|
||||||
|
{
|
||||||
|
this->keepaliveTimer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
this->listener->onClose(std::move(this->listener), {});
|
this->listener->onClose(std::move(this->listener), {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boost::system::error_code Session::handleMessage(
|
||||||
|
const beast::flat_buffer &buffer)
|
||||||
|
{
|
||||||
|
boost::system::error_code parseError;
|
||||||
|
auto jv =
|
||||||
|
boost::json::parse(beast::buffers_to_string(buffer.data()), parseError);
|
||||||
|
if (parseError)
|
||||||
|
{
|
||||||
|
// TODO: wrap error?
|
||||||
|
return parseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto *jvObject = jv.if_object();
|
||||||
|
if (jvObject == nullptr)
|
||||||
|
{
|
||||||
|
EVENTSUB_BAIL_HERE(error::Kind::ExpectedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto *metadataV = jvObject->if_contains("metadata");
|
||||||
|
if (metadataV == nullptr)
|
||||||
|
{
|
||||||
|
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
||||||
|
}
|
||||||
|
auto metadataResult =
|
||||||
|
boost::json::try_value_to<messages::Metadata>(*metadataV);
|
||||||
|
if (metadataResult.has_error())
|
||||||
|
{
|
||||||
|
// TODO: wrap error?
|
||||||
|
return metadataResult.error();
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &metadata = metadataResult.value();
|
||||||
|
|
||||||
|
const auto *payloadV = jvObject->if_contains("payload");
|
||||||
|
if (payloadV == nullptr)
|
||||||
|
{
|
||||||
|
EVENTSUB_BAIL_HERE(error::Kind::FieldMissing);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata.messageType == "notification")
|
||||||
|
{
|
||||||
|
return this->onNotification(metadata, *payloadV);
|
||||||
|
}
|
||||||
|
if (metadata.messageType == "session_welcome")
|
||||||
|
{
|
||||||
|
return this->onSessionWelcome(metadata, *payloadV);
|
||||||
|
}
|
||||||
|
if (metadata.messageType == "session_keepalive")
|
||||||
|
{
|
||||||
|
return {}; // nothing to do
|
||||||
|
}
|
||||||
|
if (metadata.messageType == "session_reconnect")
|
||||||
|
{
|
||||||
|
return this->onSessionReconnect(*payloadV);
|
||||||
|
}
|
||||||
|
EVENTSUB_BAIL_HERE(error::Kind::NoMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::system::error_code Session::onSessionWelcome(
|
||||||
|
const messages::Metadata &metadata, const boost::json::value &jv)
|
||||||
|
{
|
||||||
|
auto oPayload = parsePayload<payload::session_welcome::Payload>(jv);
|
||||||
|
if (!oPayload)
|
||||||
|
{
|
||||||
|
// TODO: error handling
|
||||||
|
return oPayload.error();
|
||||||
|
}
|
||||||
|
const auto &payload = *oPayload;
|
||||||
|
|
||||||
|
listener->onSessionWelcome(metadata, payload);
|
||||||
|
|
||||||
|
// we're graceful with the keepalive timeout
|
||||||
|
this->keepaliveTimeout =
|
||||||
|
std::chrono::seconds{payload.keepaliveTimeoutSeconds.value_or(60)} * 2;
|
||||||
|
assert(!this->keepaliveTimer);
|
||||||
|
std::cerr << "Keepalive: " << this->keepaliveTimeout.count() << 's';
|
||||||
|
this->checkKeepalive();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::system::error_code Session::onSessionReconnect(
|
||||||
|
const boost::json::value &jv)
|
||||||
|
{
|
||||||
|
auto oPayload = parsePayload<payload::session_welcome::Payload>(jv);
|
||||||
|
if (!oPayload)
|
||||||
|
{
|
||||||
|
return oPayload.error();
|
||||||
|
}
|
||||||
|
const auto &payload = *oPayload;
|
||||||
|
auto *listenerPtr = listener.get();
|
||||||
|
|
||||||
|
listenerPtr->onClose(std::move(listener), payload.reconnectURL);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::system::error_code Session::onNotification(
|
||||||
|
const messages::Metadata &metadata, const boost::json::value &jv)
|
||||||
|
{
|
||||||
|
listener->onNotification(metadata, jv);
|
||||||
|
|
||||||
|
if (!metadata.subscriptionType || !metadata.subscriptionVersion)
|
||||||
|
{
|
||||||
|
// TODO: error handling
|
||||||
|
return boost::system::error_code{};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = NOTIFICATION_HANDLERS.find(
|
||||||
|
{*metadata.subscriptionType, *metadata.subscriptionVersion});
|
||||||
|
if (it == NOTIFICATION_HANDLERS.end())
|
||||||
|
{
|
||||||
|
EVENTSUB_BAIL_HERE(error::Kind::NoMessageHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second(metadata, jv, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Session::checkKeepalive()
|
||||||
|
{
|
||||||
|
if (!this->receivedMessage)
|
||||||
|
{
|
||||||
|
std::cerr << "Keepalive timeout, closing\n";
|
||||||
|
if (this->listener)
|
||||||
|
{
|
||||||
|
this->listener->onClose(std::move(this->listener), {});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->receivedMessage = false;
|
||||||
|
|
||||||
|
if (this->keepaliveTimeout.count() == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->keepaliveTimer =
|
||||||
|
std::make_unique<boost::asio::system_timer>(this->ws.get_executor());
|
||||||
|
this->keepaliveTimer->expires_after(this->keepaliveTimeout);
|
||||||
|
this->keepaliveTimer->async_wait([this](boost::system::error_code ec) {
|
||||||
|
if (ec)
|
||||||
|
{
|
||||||
|
std::cerr << "Keepalive timer cancelled: " << ec.message() << '\n';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->checkKeepalive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace chatterino::eventsub::lib
|
} // namespace chatterino::eventsub::lib
|
||||||
|
|||||||
@@ -154,7 +154,11 @@ TEST_P(TestHandleMessageP, Run)
|
|||||||
auto buf = readToFlatBuffer(filePath(GetParam() + ".json"));
|
auto buf = readToFlatBuffer(filePath(GetParam() + ".json"));
|
||||||
|
|
||||||
std::unique_ptr<Listener> listener = std::make_unique<NoOpListener>();
|
std::unique_ptr<Listener> listener = std::make_unique<NoOpListener>();
|
||||||
auto ec = handleMessage(listener, buf);
|
boost::asio::io_context ioc;
|
||||||
|
boost::asio::ssl::context ssl(
|
||||||
|
boost::asio::ssl::context::method::tls_client);
|
||||||
|
Session sess(ioc, ssl, std::move(listener));
|
||||||
|
auto ec = sess.handleMessage(buf);
|
||||||
ASSERT_FALSE(ec.failed())
|
ASSERT_FALSE(ec.failed())
|
||||||
<< ec.what() << ec.message() << ec.location().to_string();
|
<< ec.what() << ec.message() << ec.location().to_string();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,6 +304,13 @@ TEST_P(TestEventSubMessagesP, Run)
|
|||||||
input = snapshot->input().toArray();
|
input = snapshot->input().toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<eventsub::lib::Listener> listener =
|
||||||
|
std::make_unique<eventsub::Connection>();
|
||||||
|
boost::asio::io_context ioc;
|
||||||
|
boost::asio::ssl::context ssl(
|
||||||
|
boost::asio::ssl::context::method::tls_client);
|
||||||
|
eventsub::lib::Session sess(ioc, ssl, std::move(listener));
|
||||||
|
|
||||||
for (const auto inputRef : input)
|
for (const auto inputRef : input)
|
||||||
{
|
{
|
||||||
auto inputObj = inputRef.toObject();
|
auto inputObj = inputRef.toObject();
|
||||||
@@ -321,9 +328,7 @@ TEST_P(TestEventSubMessagesP, Run)
|
|||||||
|
|
||||||
auto json = makePayload(eventSubscription->second, inputObj);
|
auto json = makePayload(eventSubscription->second, inputObj);
|
||||||
|
|
||||||
std::unique_ptr<eventsub::lib::Listener> listener =
|
auto ec = sess.handleMessage(json);
|
||||||
std::make_unique<eventsub::Connection>();
|
|
||||||
auto ec = eventsub::lib::handleMessage(listener, json);
|
|
||||||
ASSERT_FALSE(ec.failed())
|
ASSERT_FALSE(ec.failed())
|
||||||
<< ec.what() << ec.message() << ec.location().to_string();
|
<< ec.what() << ec.message() << ec.location().to_string();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user