diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 88e3843a8..e2cc08f6a 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -166,6 +166,9 @@ void Config::ReadValues() { "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry"); Settings::values.verify_endpoint_url = sdl2_config->Get( "WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile"); + Settings::values.announce_multiplayer_room_endpoint_url = + sdl2_config->Get("WebService", "announce_multiplayer_room_endpoint_url", + "https://services.citra-emu.org/api/multiplayer/rooms"); Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", ""); Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", ""); } diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index 2d9a8d614..015aacefc 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -191,6 +191,8 @@ enable_telemetry = telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry # Endpoint URL to verify the username and token verify_endpoint_url = https://services.citra-emu.org/api/profile +# Endpoint URL for announcing public rooms +announce_multiplayer_room_endpoint_url = https://services.citra-emu.org/api/multiplayer/rooms # Username and token for Citra Web Service # See https://services.citra-emu.org/ for more info citra_username = diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 87738ef61..d136213cb 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -26,6 +26,7 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOU add_library(common STATIC alignment.h + announce_multiplayer_room.h assert.h bit_field.h bit_set.h diff --git a/src/common/announce_multiplayer_room.h b/src/common/announce_multiplayer_room.h new file mode 100644 index 000000000..60d7933f7 --- /dev/null +++ b/src/common/announce_multiplayer_room.h @@ -0,0 +1,143 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "common/common_types.h" + +namespace Common { +struct WebResult { + enum class Code : u32 { + Success, + InvalidURL, + CredentialsMissing, + CprError, + HttpError, + WrongContent, + NoWebservice, + }; + Code result_code; + std::string result_string; +}; +} // namespace Common + +namespace AnnounceMultiplayerRoom { + +using MacAddress = std::array; + +struct Room { + struct Member { + std::string name; + MacAddress mac_address; + std::string game_name; + u64 game_id; + }; + std::string name; + std::string UID; + std::string owner; + std::string ip; + u16 port; + u32 max_player; + u32 net_version; + bool has_password; + std::string preferred_game; + u64 preferred_game_id; + + std::vector members; +}; +using RoomList = std::vector; + +/** + * A AnnounceMultiplayerRoom interface class. A backend to submit/get to/from a web service should + * implement this interface. + */ +class Backend : NonCopyable { +public: + virtual ~Backend() = default; + + /** + * Sets the Information that gets used for the announce + * @param uid The Id of the room + * @param name The name of the room + * @param port The port of the room + * @param net_version The version of the libNetwork that gets used + * @param has_password True if the room is passowrd protected + * @param preferred_game The preferred game of the room + * @param preferred_game_id The title id of the preferred game + */ + virtual void SetRoomInformation(const std::string& uid, const std::string& name, const u16 port, + const u32 max_player, const u32 net_version, + const bool has_password, const std::string& preferred_game, + const u64 preferred_game_id) = 0; + /** + * Adds a player information to the data that gets announced + * @param nickname The nickname of the player + * @param mac_address The MAC Address of the player + * @param game_id The title id of the game the player plays + * @param game_name The name of the game the player plays + */ + virtual void AddPlayer(const std::string& nickname, const MacAddress& mac_address, + const u64 game_id, const std::string& game_name) = 0; + + /** + * Send the data to the announce service + * @result The result of the announce attempt + */ + virtual std::future Announce() = 0; + + /** + * Empties the stored players + */ + virtual void ClearPlayers() = 0; + + /** + * Get the room information from the announce service + * @param func a function that gets exectued when the get finished. + * Can be used as a callback + * @result A list of all rooms the announce service has + */ + virtual std::future GetRoomList(std::function func) = 0; + + /** + * Sends a delete message to the announce service + */ + virtual void Delete() = 0; +}; + +/** + * Empty implementation of AnnounceMultiplayerRoom interface that drops all data. Used when a + * functional backend implementation is not available. + */ +class NullBackend : public Backend { +public: + ~NullBackend() = default; + void SetRoomInformation(const std::string& /*uid*/, const std::string& /*name*/, + const u16 /*port*/, const u32 /*max_player*/, const u32 /*net_version*/, + const bool /*has_password*/, const std::string& /*preferred_game*/, + const u64 /*preferred_game_id*/) override {} + void AddPlayer(const std::string& /*nickname*/, const MacAddress& /*mac_address*/, + const u64 /*game_id*/, const std::string& /*game_name*/) override {} + std::future Announce() override { + return std::async(std::launch::deferred, []() { + return Common::WebResult{Common::WebResult::Code::NoWebservice, + "WebService is missing"}; + }); + } + void ClearPlayers() override {} + std::future GetRoomList(std::function func) override { + return std::async(std::launch::deferred, [func]() { + func(); + return RoomList{}; + }); + } + + void Delete() override {} +}; + +} // namespace AnnounceMultiplayerRoom diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 74d4bfbee..f838b6b04 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,5 +1,7 @@ add_library(core STATIC 3ds.h + announce_multiplayer_session.cpp + announce_multiplayer_session.h arm/arm_interface.h arm/dyncom/arm_dyncom.cpp arm/dyncom/arm_dyncom.h diff --git a/src/core/announce_multiplayer_session.cpp b/src/core/announce_multiplayer_session.cpp new file mode 100644 index 000000000..b6a224ad6 --- /dev/null +++ b/src/core/announce_multiplayer_session.cpp @@ -0,0 +1,108 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "announce_multiplayer_session.h" +#include "common/announce_multiplayer_room.h" +#include "common/assert.h" +#include "core/settings.h" +#include "network/network.h" + +#ifdef ENABLE_WEB_SERVICE +#include "web_service/announce_room_json.h" +#endif + +namespace Core { + +// Time between room is announced to web_service +static constexpr std::chrono::seconds announce_time_interval(15); + +AnnounceMultiplayerSession::AnnounceMultiplayerSession() { +#ifdef ENABLE_WEB_SERVICE + backend = std::make_unique( + Settings::values.announce_multiplayer_room_endpoint_url, Settings::values.citra_username, + Settings::values.citra_token); +#else + backend = std::make_unique(); +#endif +} + +void AnnounceMultiplayerSession::Start() { + if (announce_multiplayer_thread) { + Stop(); + } + shutdown_event.Reset(); + announce_multiplayer_thread = + std::make_unique(&AnnounceMultiplayerSession::AnnounceMultiplayerLoop, this); +} + +void AnnounceMultiplayerSession::Stop() { + if (announce_multiplayer_thread) { + shutdown_event.Set(); + announce_multiplayer_thread->join(); + announce_multiplayer_thread.reset(); + backend->Delete(); + } +} + +AnnounceMultiplayerSession::CallbackHandle AnnounceMultiplayerSession::BindErrorCallback( + std::function function) { + std::lock_guard lock(callback_mutex); + auto handle = std::make_shared>(function); + error_callbacks.insert(handle); + return handle; +} + +void AnnounceMultiplayerSession::UnbindErrorCallback(CallbackHandle handle) { + std::lock_guard lock(callback_mutex); + error_callbacks.erase(handle); +} + +AnnounceMultiplayerSession::~AnnounceMultiplayerSession() { + Stop(); +} + +void AnnounceMultiplayerSession::AnnounceMultiplayerLoop() { + auto update_time = std::chrono::steady_clock::now(); + std::future future; + while (!shutdown_event.WaitUntil(update_time)) { + update_time += announce_time_interval; + std::shared_ptr room = Network::GetRoom().lock(); + if (!room) { + break; + } + if (room->GetState() != Network::Room::State::Open) { + break; + } + Network::RoomInformation room_information = room->GetRoomInformation(); + std::vector memberlist = room->GetRoomMemberList(); + backend->SetRoomInformation( + room_information.uid, room_information.name, room_information.port, + room_information.member_slots, Network::network_version, room->HasPassword(), + room_information.preferred_game, room_information.preferred_game_id); + backend->ClearPlayers(); + for (const auto& member : memberlist) { + backend->AddPlayer(member.nickname, member.mac_address, member.game_info.id, + member.game_info.name); + } + future = backend->Announce(); + if (future.valid()) { + Common::WebResult result = future.get(); + if (result.result_code != Common::WebResult::Code::Success) { + std::lock_guard lock(callback_mutex); + for (auto callback : error_callbacks) { + (*callback)(result); + } + } + } + } +} + +std::future AnnounceMultiplayerSession::GetRoomList( + std::function func) { + return backend->GetRoomList(func); +} + +} // namespace Core diff --git a/src/core/announce_multiplayer_session.h b/src/core/announce_multiplayer_session.h new file mode 100644 index 000000000..0ea357e3a --- /dev/null +++ b/src/core/announce_multiplayer_session.h @@ -0,0 +1,71 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "common/announce_multiplayer_room.h" +#include "common/common_types.h" +#include "common/thread.h" + +namespace Core { + +/** + * Instruments AnnounceMultiplayerRoom::Backend. + * Creates a thread that regularly updates the room information and submits them + * An async get of room information is also possible + */ +class AnnounceMultiplayerSession : NonCopyable { +public: + using CallbackHandle = std::shared_ptr>; + AnnounceMultiplayerSession(); + ~AnnounceMultiplayerSession(); + + /** + * Allows to bind a function that will get called if the announce encounters an error + * @param function The function that gets called + * @return A handle that can be used the unbind the function + */ + CallbackHandle BindErrorCallback(std::function function); + + /** + * Unbind a function from the error callbacks + * @param handle The handle for the function that should get unbind + */ + void UnbindErrorCallback(CallbackHandle handle); + + /** + * Starts the announce of a room to web services + */ + void Start(); + + /** + * Stops the announce to web services + */ + void Stop(); + + /** + * Returns a list of all room information the backend got + * @param func A function that gets executed when the async get finished, e.g. a signal + * @return a list of rooms received from the web service + */ + std::future GetRoomList(std::function func); + +private: + Common::Event shutdown_event; + std::mutex callback_mutex; + std::set error_callbacks; + std::unique_ptr announce_multiplayer_thread; + + /// Backend interface that logs fields + std::unique_ptr backend; + + void AnnounceMultiplayerLoop(); +}; + +} // namespace Core diff --git a/src/core/settings.h b/src/core/settings.h index cb6049cc0..c0896b940 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -139,6 +139,7 @@ struct Values { bool enable_telemetry; std::string telemetry_endpoint_url; std::string verify_endpoint_url; + std::string announce_multiplayer_room_endpoint_url; std::string citra_username; std::string citra_token; } extern values; diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt index b6de2632b..911fbe26b 100644 --- a/src/web_service/CMakeLists.txt +++ b/src/web_service/CMakeLists.txt @@ -1,4 +1,6 @@ add_library(web_service STATIC + announce_room_json.cpp + announce_room_json.h telemetry_json.cpp telemetry_json.h verify_login.cpp diff --git a/src/web_service/announce_room_json.cpp b/src/web_service/announce_room_json.cpp new file mode 100644 index 000000000..d2b79cbae --- /dev/null +++ b/src/web_service/announce_room_json.cpp @@ -0,0 +1,112 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "common/logging/log.h" +#include "web_service/announce_room_json.h" +#include "web_service/web_backend.h" + +namespace AnnounceMultiplayerRoom { + +void to_json(nlohmann::json& json, const Room::Member& member) { + json["name"] = member.name; + json["gameName"] = member.game_name; + json["gameId"] = member.game_id; +} + +void from_json(const nlohmann::json& json, Room::Member& member) { + member.name = json.at("name").get(); + member.game_name = json.at("gameName").get(); + member.game_id = json.at("gameId").get(); +} + +void to_json(nlohmann::json& json, const Room& room) { + json["id"] = room.UID; + json["port"] = room.port; + json["name"] = room.name; + json["preferredGameName"] = room.preferred_game; + json["preferredGameId"] = room.preferred_game_id; + json["maxPlayers"] = room.max_player; + json["netVersion"] = room.net_version; + json["hasPassword"] = room.has_password; + if (room.members.size() > 0) { + nlohmann::json member_json = room.members; + json["players"] = member_json; + } +} + +void from_json(const nlohmann::json& json, Room& room) { + room.ip = json.at("address").get(); + room.name = json.at("name").get(); + room.owner = json.at("owner").get(); + room.port = json.at("port").get(); + room.preferred_game = json.at("preferredGameName").get(); + room.preferred_game_id = json.at("preferredGameId").get(); + room.max_player = json.at("maxPlayers").get(); + room.net_version = json.at("netVersion").get(); + room.has_password = json.at("hasPassword").get(); + try { + room.members = json.at("players").get>(); + } catch (const nlohmann::detail::out_of_range& e) { + LOG_DEBUG(Network, "Out of range %s", e.what()); + } +} + +} // namespace AnnounceMultiplayerRoom + +namespace WebService { + +void RoomJson::SetRoomInformation(const std::string& uid, const std::string& name, const u16 port, + const u32 max_player, const u32 net_version, + const bool has_password, const std::string& preferred_game, + const u64 preferred_game_id) { + room.name = name; + room.UID = uid; + room.port = port; + room.max_player = max_player; + room.net_version = net_version; + room.has_password = has_password; + room.preferred_game = preferred_game; + room.preferred_game_id = preferred_game_id; +} +void RoomJson::AddPlayer(const std::string& nickname, + const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id, + const std::string& game_name) { + AnnounceMultiplayerRoom::Room::Member member; + member.name = nickname; + member.mac_address = mac_address; + member.game_id = game_id; + member.game_name = game_name; + room.members.push_back(member); +} + +std::future RoomJson::Announce() { + nlohmann::json json = room; + return PostJson(endpoint_url, json.dump(), false, username, token); +} + +void RoomJson::ClearPlayers() { + room.members.clear(); +} + +std::future RoomJson::GetRoomList(std::function func) { + auto DeSerialize = [func](const std::string& reply) -> AnnounceMultiplayerRoom::RoomList { + nlohmann::json json = nlohmann::json::parse(reply); + AnnounceMultiplayerRoom::RoomList room_list = + json.at("rooms").get(); + func(); + return room_list; + }; + return GetJson(DeSerialize, endpoint_url, true, username, + token); +} + +void RoomJson::Delete() { + nlohmann::json json; + json["id"] = room.UID; + DeleteJson(endpoint_url, json.dump(), username, token); +} + +} // namespace WebService diff --git a/src/web_service/announce_room_json.h b/src/web_service/announce_room_json.h new file mode 100644 index 000000000..85550e838 --- /dev/null +++ b/src/web_service/announce_room_json.h @@ -0,0 +1,42 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include "common/announce_multiplayer_room.h" + +namespace WebService { + +/** + * Implementation of AnnounceMultiplayerRoom::Backend that (de)serializes room information into/from + * JSON, and submits/gets it to/from the Citra web service + */ +class RoomJson : public AnnounceMultiplayerRoom::Backend { +public: + RoomJson(const std::string& endpoint_url, const std::string& username, const std::string& token) + : endpoint_url(endpoint_url), username(username), token(token) {} + ~RoomJson() = default; + void SetRoomInformation(const std::string& uid, const std::string& name, const u16 port, + const u32 max_player, const u32 net_version, const bool has_password, + const std::string& preferred_game, + const u64 preferred_game_id) override; + void AddPlayer(const std::string& nickname, + const AnnounceMultiplayerRoom::MacAddress& mac_address, const u64 game_id, + const std::string& game_name) override; + std::future Announce() override; + void ClearPlayers() override; + std::future GetRoomList(std::function func) override; + void Delete() override; + +private: + AnnounceMultiplayerRoom::Room room; + std::string endpoint_url; + std::string username; + std::string token; +}; + +} // namespace WebService diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp index 6ad2ffcd4..28ae0d34e 100644 --- a/src/web_service/telemetry_json.cpp +++ b/src/web_service/telemetry_json.cpp @@ -80,7 +80,9 @@ void TelemetryJson::Complete() { SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); - PostJson(endpoint_url, TopSection().dump(), true, username, token); + + // Send the telemetry async but don't handle the errors since they were written to the log + future = PostJson(endpoint_url, TopSection().dump(), true, username, token); } } // namespace WebService diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h index 9e78c6803..ae4a6f3c7 100644 --- a/src/web_service/telemetry_json.h +++ b/src/web_service/telemetry_json.h @@ -5,8 +5,10 @@ #pragma once #include +#include #include #include +#include "common/announce_multiplayer_room.h" #include "common/telemetry.h" namespace WebService { @@ -54,6 +56,7 @@ private: std::string endpoint_url; std::string username; std::string token; + std::future future; }; } // namespace WebService diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp index b17d82f9c..64f6a69bd 100644 --- a/src/web_service/web_backend.cpp +++ b/src/web_service/web_backend.cpp @@ -9,6 +9,7 @@ #include #include #include +#include "common/announce_multiplayer_room.h" #include "common/logging/log.h" #include "web_service/web_backend.h" @@ -31,17 +32,23 @@ void Win32WSAStartup() { #endif } -void PostJson(const std::string& url, const std::string& data, bool allow_anonymous, - const std::string& username, const std::string& token) { +std::future PostJson(const std::string& url, const std::string& data, + bool allow_anonymous, const std::string& username, + const std::string& token) { if (url.empty()) { LOG_ERROR(WebService, "URL is invalid"); - return; + return std::async(std::launch::deferred, []() { + return Common::WebResult{Common::WebResult::Code::InvalidURL, "URL is invalid"}; + }); } const bool are_credentials_provided{!token.empty() && !username.empty()}; if (!allow_anonymous && !are_credentials_provided) { LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); - return; + return std::async(std::launch::deferred, []() { + return Common::WebResult{Common::WebResult::Code::CredentialsMissing, + "Credentials needed"}; + }); } Win32WSAStartup(); @@ -60,23 +67,26 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym } // Post JSON asynchronously - static std::future future; - future = cpr::PostCallback( + return cpr::PostCallback( [](cpr::Response r) { if (r.error) { - LOG_ERROR(WebService, "POST returned cpr error: %u:%s", + LOG_ERROR(WebService, "POST to %s returned cpr error: %u:%s", r.url.c_str(), static_cast(r.error.code), r.error.message.c_str()); - return; + return Common::WebResult{Common::WebResult::Code::CprError, r.error.message}; } if (r.status_code >= 400) { - LOG_ERROR(WebService, "POST returned error status code: %u", r.status_code); - return; + LOG_ERROR(WebService, "POST to %s returned error status code: %u", r.url.c_str(), + r.status_code); + return Common::WebResult{Common::WebResult::Code::HttpError, + std::to_string(r.status_code)}; } if (r.header["content-type"].find("application/json") == std::string::npos) { - LOG_ERROR(WebService, "POST returned wrong content: %s", + LOG_ERROR(WebService, "POST to %s returned wrong content: %s", r.url.c_str(), r.header["content-type"].c_str()); - return; + return Common::WebResult{Common::WebResult::Code::WrongContent, + r.header["content-type"]}; } + return Common::WebResult{Common::WebResult::Code::Success, ""}; }, cpr::Url{url}, cpr::Body{data}, header); } @@ -87,13 +97,13 @@ std::future GetJson(std::function func, const std::str const std::string& token) { if (url.empty()) { LOG_ERROR(WebService, "URL is invalid"); - return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); }); + return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); }); } const bool are_credentials_provided{!token.empty() && !username.empty()}; if (!allow_anonymous && !are_credentials_provided) { LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); - return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); }); + return std::async(std::launch::deferred, [func{std::move(func)}]() { return func(""); }); } Win32WSAStartup(); @@ -115,16 +125,17 @@ std::future GetJson(std::function func, const std::str return cpr::GetCallback( [func{std::move(func)}](cpr::Response r) { if (r.error) { - LOG_ERROR(WebService, "GET returned cpr error: %u:%s", + LOG_ERROR(WebService, "GET to %s returned cpr error: %u:%s", r.url.c_str(), static_cast(r.error.code), r.error.message.c_str()); return func(""); } if (r.status_code >= 400) { - LOG_ERROR(WebService, "GET returned error code: %u", r.status_code); + LOG_ERROR(WebService, "GET to %s returned error code: %u", r.url.c_str(), + r.status_code); return func(""); } if (r.header["content-type"].find("application/json") == std::string::npos) { - LOG_ERROR(WebService, "GET returned wrong content: %s", + LOG_ERROR(WebService, "GET to %s returned wrong content: %s", r.url.c_str(), r.header["content-type"].c_str()); return func(""); } @@ -136,5 +147,52 @@ std::future GetJson(std::function func, const std::str template std::future GetJson(std::function func, const std::string& url, bool allow_anonymous, const std::string& username, const std::string& token); +template std::future GetJson( + std::function func, + const std::string& url, bool allow_anonymous, const std::string& username, + const std::string& token); + +void DeleteJson(const std::string& url, const std::string& data, const std::string& username, + const std::string& token) { + if (url.empty()) { + LOG_ERROR(WebService, "URL is invalid"); + return; + } + + if (token.empty() || username.empty()) { + LOG_ERROR(WebService, "Credentials must be provided for authenticated requests"); + return; + } + + Win32WSAStartup(); + + // Built request header + cpr::Header header = {{"Content-Type", "application/json"}, + {"x-username", username.c_str()}, + {"x-token", token.c_str()}, + {"api-version", API_VERSION}}; + + // Delete JSON asynchronously + static std::future future; + future = cpr::DeleteCallback( + [](cpr::Response r) { + if (r.error) { + LOG_ERROR(WebService, "Delete to %s returned cpr error: %u:%s", r.url.c_str(), + static_cast(r.error.code), r.error.message.c_str()); + return; + } + if (r.status_code >= 400) { + LOG_ERROR(WebService, "Delete to %s returned error status code: %u", r.url.c_str(), + r.status_code); + return; + } + if (r.header["content-type"].find("application/json") == std::string::npos) { + LOG_ERROR(WebService, "Delete to %s returned wrong content: %s", r.url.c_str(), + r.header["content-type"].c_str()); + return; + } + }, + cpr::Url{url}, cpr::Body{data}, header); +} } // namespace WebService diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h index a63c75d13..29aab00cb 100644 --- a/src/web_service/web_backend.h +++ b/src/web_service/web_backend.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include "common/announce_multiplayer_room.h" #include "common/common_types.h" namespace WebService { @@ -18,9 +20,11 @@ namespace WebService { * @param allow_anonymous If true, allow anonymous unauthenticated requests. * @param username Citra username to use for authentication. * @param token Citra token to use for authentication. + * @return future with the error or result of the POST */ -void PostJson(const std::string& url, const std::string& data, bool allow_anonymous, - const std::string& username = {}, const std::string& token = {}); +std::future PostJson(const std::string& url, const std::string& data, + bool allow_anonymous, const std::string& username = {}, + const std::string& token = {}); /** * Gets JSON from services.citra-emu.org. @@ -36,4 +40,14 @@ std::future GetJson(std::function func, const std::str bool allow_anonymous, const std::string& username = {}, const std::string& token = {}); +/** + * Delete JSON to services.citra-emu.org. + * @param url URL of the services.citra-emu.org endpoint to post data to. + * @param data String of JSON data to use for the body of the DELETE request. + * @param username Citra username to use for authentication. + * @param token Citra token to use for authentication. + */ +void DeleteJson(const std::string& url, const std::string& data, const std::string& username = {}, + const std::string& token = {}); + } // namespace WebService