mirror of
https://github.com/RGBCube/serenity
synced 2025-09-13 07:17:34 +00:00
Everywhere: Merge the WebSocket service into RequestServer
This keeps the APIs separate as they are wildly different, a future improvement could be to somehow unify the APIs (if possible). Closes #23080.
This commit is contained in:
parent
daf5484d6b
commit
6dfb2f9dc8
56 changed files with 231 additions and 845 deletions
|
@ -56,11 +56,6 @@
|
|||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":RequestServer" />
|
||||
<service
|
||||
android:name=".WebSocketService"
|
||||
android:enabled="true"
|
||||
android:exported="false"
|
||||
android:process=":WebSocket" />
|
||||
<service
|
||||
android:name=".ImageDecoderService"
|
||||
android:enabled="true"
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#include <LibWeb/Platform/AudioCodecPluginAgnostic.h>
|
||||
#include <LibWeb/Platform/EventLoopPluginSerenity.h>
|
||||
#include <LibWebView/RequestServerAdapter.h>
|
||||
#include <LibWebView/WebSocketClientAdapter.h>
|
||||
#include <WebContent/ConnectionFromClient.h>
|
||||
#include <WebContent/PageHost.h>
|
||||
|
||||
|
@ -37,11 +36,6 @@ static ErrorOr<NonnullRefPtr<Protocol::RequestClient>> bind_request_server_servi
|
|||
return bind_service<Protocol::RequestClient>(&bind_request_server_java);
|
||||
}
|
||||
|
||||
static ErrorOr<NonnullRefPtr<Protocol::WebSocketClient>> bind_web_socket_service()
|
||||
{
|
||||
return bind_service<Protocol::WebSocketClient>(&bind_web_socket_java);
|
||||
}
|
||||
|
||||
template ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>, Error>
|
||||
bind_service<ImageDecoderClient::Client>(void (*)(int, int));
|
||||
|
||||
|
@ -64,9 +58,6 @@ ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket)
|
|||
auto request_server_client = TRY(bind_request_server_service());
|
||||
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(request_server_client))));
|
||||
|
||||
auto web_socket_client = TRY(bind_web_socket_service());
|
||||
Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create(move(web_socket_client))));
|
||||
|
||||
bool is_layout_test_mode = false;
|
||||
|
||||
Web::HTML::Window::set_internals_object_exposed(is_layout_test_mode);
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
jobject global_instance;
|
||||
jclass global_class_reference;
|
||||
jmethodID bind_request_server_method;
|
||||
jmethodID bind_web_socket_method;
|
||||
jmethodID bind_image_decoder_method;
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
|
@ -30,11 +29,6 @@ Java_org_serenityos_ladybird_WebContentService_nativeInit(JNIEnv* env, jobject t
|
|||
TODO();
|
||||
bind_request_server_method = method;
|
||||
|
||||
method = env->GetMethodID(global_class_reference, "bindWebSocket", "(II)V");
|
||||
if (!method)
|
||||
TODO();
|
||||
bind_web_socket_method = method;
|
||||
|
||||
method = env->GetMethodID(global_class_reference, "bindImageDecoder", "(II)V");
|
||||
if (!method)
|
||||
TODO();
|
||||
|
@ -47,12 +41,6 @@ void bind_request_server_java(int ipc_socket, int fd_passing_socket)
|
|||
env.get()->CallVoidMethod(global_instance, bind_request_server_method, ipc_socket, fd_passing_socket);
|
||||
}
|
||||
|
||||
void bind_web_socket_java(int ipc_socket, int fd_passing_socket)
|
||||
{
|
||||
Ladybird::JavaEnvironment env(global_vm);
|
||||
env.get()->CallVoidMethod(global_instance, bind_web_socket_method, ipc_socket, fd_passing_socket);
|
||||
}
|
||||
|
||||
void bind_image_decoder_java(int ipc_socket, int fd_passing_socket)
|
||||
{
|
||||
Ladybird::JavaEnvironment env(global_vm);
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
|
||||
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <Ladybird/Utilities.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibIPC/SingleServer.h>
|
||||
#include <LibTLS/Certificate.h>
|
||||
#include <WebSocket/ConnectionFromClient.h>
|
||||
|
||||
// FIXME: Share b/w RequestServer and WebSocket
|
||||
ErrorOr<ByteString> find_certificates(StringView serenity_resource_root)
|
||||
{
|
||||
auto cert_path = ByteString::formatted("{}/ladybird/cacert.pem", serenity_resource_root);
|
||||
if (!FileSystem::exists(cert_path))
|
||||
return Error::from_string_view("Don't know how to load certs!"sv);
|
||||
return cert_path;
|
||||
}
|
||||
|
||||
ErrorOr<int> service_main(int ipc_socket, int fd_passing_socket)
|
||||
{
|
||||
// Ensure the certificates are read out here.
|
||||
DefaultRootCACertificates::set_default_certificate_paths(Vector { TRY(find_certificates(s_serenity_resource_root)) });
|
||||
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
|
||||
|
||||
Core::EventLoop event_loop;
|
||||
|
||||
auto socket = TRY(Core::LocalSocket::adopt_fd(ipc_socket));
|
||||
auto client = TRY(WebSocket::ConnectionFromClient::try_create(move(socket)));
|
||||
client->set_fd_passing_socket(TRY(Core::LocalSocket::adopt_fd(fd_passing_socket)));
|
||||
|
||||
return event_loop.exec();
|
||||
}
|
|
@ -35,21 +35,6 @@ class WebContentService : LadybirdServiceBase("WebContentService") {
|
|||
)
|
||||
}
|
||||
|
||||
private fun bindWebSocket(ipcFd: Int, fdPassingFd: Int)
|
||||
{
|
||||
val connector = LadybirdServiceConnection(ipcFd, fdPassingFd, resourceDir)
|
||||
connector.onDisconnect = {
|
||||
// FIXME: Notify impl that service is dead and might need restarted
|
||||
Log.e(TAG, "WebSocket Died! :(")
|
||||
}
|
||||
// FIXME: Unbind this at some point maybe
|
||||
bindService(
|
||||
Intent(this, WebSocketService::class.java),
|
||||
connector,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
private fun bindImageDecoder(ipcFd: Int, fdPassingFd: Int)
|
||||
{
|
||||
val connector = LadybirdServiceConnection(ipcFd, fdPassingFd, resourceDir)
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
package org.serenityos.ladybird
|
||||
|
||||
import android.os.Message
|
||||
|
||||
class WebSocketService : LadybirdServiceBase("WebSocketService") {
|
||||
override fun handleServiceSpecificMessage(msg: Message): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
init {
|
||||
System.loadLibrary("websocket")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -226,10 +226,9 @@ add_subdirectory(RequestServer)
|
|||
add_subdirectory(SQLServer)
|
||||
add_subdirectory(WebContent)
|
||||
add_subdirectory(WebDriver)
|
||||
add_subdirectory(WebSocket)
|
||||
add_subdirectory(WebWorker)
|
||||
|
||||
set(ladybird_helper_processes ImageDecoder RequestServer SQLServer WebContent WebDriver WebSocketServer WebWorker headless-browser)
|
||||
set(ladybird_helper_processes ImageDecoder RequestServer SQLServer WebContent WebDriver WebWorker headless-browser)
|
||||
add_dependencies(ladybird ${ladybird_helper_processes})
|
||||
|
||||
function(create_ladybird_bundle target_name)
|
||||
|
|
|
@ -178,8 +178,3 @@ ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(Re
|
|||
{
|
||||
return launch_generic_server_process<Protocol::RequestClient>(candidate_request_server_paths, serenity_resource_root, certificates, "RequestServer"sv);
|
||||
}
|
||||
|
||||
ErrorOr<NonnullRefPtr<Protocol::WebSocketClient>> launch_web_socket_process(ReadonlySpan<ByteString> candidate_web_socket_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates)
|
||||
{
|
||||
return launch_generic_server_process<Protocol::WebSocketClient>(candidate_web_socket_paths, serenity_resource_root, certificates, "WebSocket"sv);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include <AK/StringView.h>
|
||||
#include <LibImageDecoderClient/Client.h>
|
||||
#include <LibProtocol/RequestClient.h>
|
||||
#include <LibProtocol/WebSocketClient.h>
|
||||
#include <LibWeb/Worker/WebWorkerClient.h>
|
||||
#include <LibWebView/ViewImplementation.h>
|
||||
#include <LibWebView/WebContentClient.h>
|
||||
|
@ -25,4 +24,3 @@ ErrorOr<NonnullRefPtr<WebView::WebContentClient>> launch_web_content_process(
|
|||
ErrorOr<NonnullRefPtr<ImageDecoderClient::Client>> launch_image_decoder_process(ReadonlySpan<ByteString> candidate_image_decoder_paths);
|
||||
ErrorOr<NonnullRefPtr<Web::HTML::WebWorkerClient>> launch_web_worker_process(ReadonlySpan<ByteString> candidate_web_worker_paths, Vector<ByteString> const& certificates);
|
||||
ErrorOr<NonnullRefPtr<Protocol::RequestClient>> launch_request_server_process(ReadonlySpan<ByteString> candidate_request_server_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates);
|
||||
ErrorOr<NonnullRefPtr<Protocol::WebSocketClient>> launch_web_socket_process(ReadonlySpan<ByteString> candidate_web_socket_paths, StringView serenity_resource_root, Vector<ByteString> const& certificates);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
#include "RequestManagerQt.h"
|
||||
#include "WebSocketImplQt.h"
|
||||
#include "WebSocketQt.h"
|
||||
#include <AK/JsonObject.h>
|
||||
#include <QNetworkCookie>
|
||||
|
||||
|
@ -76,6 +78,18 @@ ErrorOr<NonnullRefPtr<RequestManagerQt::Request>> RequestManagerQt::Request::cre
|
|||
return adopt_ref(*new Request(*reply));
|
||||
}
|
||||
|
||||
RefPtr<Web::WebSockets::WebSocketClientSocket> RequestManagerQt::websocket_connect(AK::URL const& url, AK::ByteString const& origin, Vector<AK::ByteString> const& protocols)
|
||||
{
|
||||
WebSocket::ConnectionInfo connection_info(url);
|
||||
connection_info.set_origin(origin);
|
||||
connection_info.set_protocols(protocols);
|
||||
|
||||
auto impl = adopt_ref(*new WebSocketImplQt);
|
||||
auto web_socket = WebSocket::WebSocket::create(move(connection_info), move(impl));
|
||||
web_socket->start();
|
||||
return WebSocketQt::create(web_socket);
|
||||
}
|
||||
|
||||
RequestManagerQt::Request::Request(QNetworkReply& reply)
|
||||
: m_reply(reply)
|
||||
{
|
||||
|
|
|
@ -28,6 +28,7 @@ public:
|
|||
virtual void preconnect(URL const&) override { }
|
||||
|
||||
virtual RefPtr<Web::ResourceLoaderConnectorRequest> start_request(ByteString const& method, URL const&, HashMap<ByteString, ByteString> const& request_headers, ReadonlyBytes request_body, Core::ProxyData const&) override;
|
||||
virtual RefPtr<Web::WebSockets::WebSocketClientSocket> websocket_connect(const URL&, ByteString const& origin, Vector<ByteString> const& protocols) override;
|
||||
|
||||
private slots:
|
||||
void reply_finished(QNetworkReply*);
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Dex♪ <dexes.ttp@gmail.com>
|
||||
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "WebSocketClientManagerQt.h"
|
||||
#include "WebSocketImplQt.h"
|
||||
#include "WebSocketQt.h"
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
NonnullRefPtr<WebSocketClientManagerQt> WebSocketClientManagerQt::create()
|
||||
{
|
||||
return adopt_ref(*new WebSocketClientManagerQt());
|
||||
}
|
||||
|
||||
WebSocketClientManagerQt::WebSocketClientManagerQt() = default;
|
||||
WebSocketClientManagerQt::~WebSocketClientManagerQt() = default;
|
||||
|
||||
RefPtr<Web::WebSockets::WebSocketClientSocket> WebSocketClientManagerQt::connect(URL const& url, ByteString const& origin, Vector<ByteString> const& protocols)
|
||||
{
|
||||
WebSocket::ConnectionInfo connection_info(url);
|
||||
connection_info.set_origin(origin);
|
||||
connection_info.set_protocols(protocols);
|
||||
|
||||
auto impl = adopt_ref(*new WebSocketImplQt);
|
||||
auto web_socket = WebSocket::WebSocket::create(move(connection_info), move(impl));
|
||||
web_socket->start();
|
||||
return WebSocketQt::create(web_socket);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Dex♪ <dexes.ttp@gmail.com>
|
||||
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/WebSockets/WebSocket.h>
|
||||
#include <LibWebSocket/ConnectionInfo.h>
|
||||
#include <LibWebSocket/Message.h>
|
||||
#include <LibWebSocket/WebSocket.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Ladybird {
|
||||
|
||||
class WebSocketClientManagerQt : public Web::WebSockets::WebSocketClientManager {
|
||||
public:
|
||||
static NonnullRefPtr<WebSocketClientManagerQt> create();
|
||||
|
||||
virtual ~WebSocketClientManagerQt() override;
|
||||
virtual RefPtr<Web::WebSockets::WebSocketClientSocket> connect(URL const&, ByteString const& origin, Vector<ByteString> const& protocols) override;
|
||||
|
||||
private:
|
||||
WebSocketClientManagerQt();
|
||||
};
|
||||
|
||||
}
|
|
@ -33,7 +33,7 @@ target_link_libraries(RequestServer PRIVATE requestserver)
|
|||
|
||||
target_include_directories(requestserver PRIVATE ${SERENITY_SOURCE_DIR}/Userland/Services/)
|
||||
target_include_directories(requestserver PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/..)
|
||||
target_link_libraries(requestserver PUBLIC LibCore LibMain LibCrypto LibFileSystem LibGemini LibHTTP LibIPC LibMain LibTLS LibWebView)
|
||||
target_link_libraries(requestserver PUBLIC LibCore LibMain LibCrypto LibFileSystem LibGemini LibHTTP LibIPC LibMain LibTLS LibWebView LibWebSocket)
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
|
||||
# Solaris has socket and networking related functions in two extra libraries
|
||||
target_link_libraries(requestserver PUBLIC nsl socket)
|
||||
|
|
|
@ -21,7 +21,6 @@ if (ENABLE_QT)
|
|||
../Qt/EventLoopImplementationQtEventTarget.cpp
|
||||
../Qt/RequestManagerQt.cpp
|
||||
../Qt/StringUtils.cpp
|
||||
../Qt/WebSocketClientManagerQt.cpp
|
||||
../Qt/WebSocketQt.cpp
|
||||
../Qt/WebSocketImplQt.cpp
|
||||
main.cpp
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
#if defined(HAVE_QT)
|
||||
# include <Ladybird/Qt/EventLoopImplementationQt.h>
|
||||
# include <Ladybird/Qt/RequestManagerQt.h>
|
||||
# include <Ladybird/Qt/WebSocketClientManagerQt.h>
|
||||
# include <QCoreApplication>
|
||||
|
||||
# if defined(HAVE_QT_MULTIMEDIA)
|
||||
|
@ -109,14 +108,11 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
}
|
||||
|
||||
#if defined(HAVE_QT)
|
||||
if (!use_lagom_networking) {
|
||||
if (!use_lagom_networking)
|
||||
Web::ResourceLoader::initialize(Ladybird::RequestManagerQt::create());
|
||||
Web::WebSockets::WebSocketClientManager::initialize(Ladybird::WebSocketClientManagerQt::create());
|
||||
} else
|
||||
else
|
||||
#endif
|
||||
{
|
||||
TRY(initialize_lagom_networking(certificates));
|
||||
}
|
||||
|
||||
Web::HTML::Window::set_internals_object_exposed(is_layout_test_mode);
|
||||
|
||||
|
@ -195,9 +191,5 @@ static ErrorOr<void> initialize_lagom_networking(Vector<ByteString> const& certi
|
|||
auto request_server_client = TRY(launch_request_server_process(candidate_request_server_paths, s_serenity_resource_root, certificates));
|
||||
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(request_server_client))));
|
||||
|
||||
auto candidate_web_socket_paths = TRY(get_paths_for_helper_process("WebSocket"sv));
|
||||
auto web_socket_client = TRY(launch_web_socket_process(candidate_web_socket_paths, s_serenity_resource_root, certificates));
|
||||
Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create(move(web_socket_client))));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
set(SOURCES
|
||||
"${SERENITY_SOURCE_DIR}/Userland/Services/WebSocket/ConnectionFromClient.cpp"
|
||||
)
|
||||
|
||||
if (ANDROID)
|
||||
add_library(websocket SHARED
|
||||
${SOURCES}
|
||||
../Android/src/main/cpp/WebSocketService.cpp
|
||||
../Android/src/main/cpp/LadybirdServiceBaseJNI.cpp
|
||||
../Utilities.cpp
|
||||
)
|
||||
else()
|
||||
add_library(websocket STATIC ${SOURCES})
|
||||
endif()
|
||||
add_executable(WebSocketServer main.cpp)
|
||||
target_link_libraries(WebSocketServer PRIVATE websocket)
|
||||
set_target_properties(WebSocketServer PROPERTIES OUTPUT_NAME WebSocket)
|
||||
target_link_libraries(websocket PUBLIC LibCore LibFileSystem LibIPC LibMain LibTLS LibWebSocket LibWebView)
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Dex♪ <dexes.ttp@gmail.com>
|
||||
* Copyright (c) 2023, Andrew Kaster <akaster@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/LocalServer.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibIPC/SingleServer.h>
|
||||
#include <LibMain/Main.h>
|
||||
#include <LibTLS/Certificate.h>
|
||||
#include <WebSocket/ConnectionFromClient.h>
|
||||
|
||||
// FIXME: Share b/w RequestServer and WebSocket
|
||||
ErrorOr<ByteString> find_certificates(StringView serenity_resource_root)
|
||||
{
|
||||
auto cert_path = ByteString::formatted("{}/ladybird/cacert.pem", serenity_resource_root);
|
||||
if (!FileSystem::exists(cert_path))
|
||||
return Error::from_string_view("Don't know how to load certs!"sv);
|
||||
return cert_path;
|
||||
}
|
||||
|
||||
ErrorOr<int> serenity_main(Main::Arguments arguments)
|
||||
{
|
||||
AK::set_rich_debug_enabled(true);
|
||||
|
||||
int fd_passing_socket { -1 };
|
||||
StringView serenity_resource_root;
|
||||
Vector<ByteString> certificates;
|
||||
|
||||
Core::ArgsParser args_parser;
|
||||
args_parser.add_option(fd_passing_socket, "File descriptor of the fd passing socket", "fd-passing-socket", 'c', "fd-passing-socket");
|
||||
args_parser.add_option(certificates, "Path to a certificate file", "certificate", 'C', "certificate");
|
||||
args_parser.add_option(serenity_resource_root, "Absolute path to directory for serenity resources", "serenity-resource-root", 'r', "serenity-resource-root");
|
||||
args_parser.parse(arguments);
|
||||
|
||||
// Ensure the certificates are read out here.
|
||||
if (certificates.is_empty())
|
||||
certificates.append(TRY(find_certificates(serenity_resource_root)));
|
||||
DefaultRootCACertificates::set_default_certificate_paths(certificates.span());
|
||||
[[maybe_unused]] auto& certs = DefaultRootCACertificates::the();
|
||||
|
||||
Core::EventLoop event_loop;
|
||||
|
||||
auto client = TRY(IPC::take_over_accepted_client_from_system_server<WebSocket::ConnectionFromClient>());
|
||||
client->set_fd_passing_socket(TRY(Core::LocalSocket::adopt_fd(fd_passing_socket)));
|
||||
|
||||
return event_loop.exec();
|
||||
}
|
|
@ -67,9 +67,5 @@ static ErrorOr<void> initialize_lagom_networking(Vector<ByteString> const& certi
|
|||
auto request_server_client = TRY(launch_request_server_process(candidate_request_server_paths, s_serenity_resource_root, certificates));
|
||||
Web::ResourceLoader::initialize(TRY(WebView::RequestServerAdapter::try_create(move(request_server_client))));
|
||||
|
||||
auto candidate_web_socket_paths = TRY(get_paths_for_helper_process("WebSocket"sv));
|
||||
auto web_socket_client = TRY(launch_web_socket_process(candidate_web_socket_paths, s_serenity_resource_root, certificates));
|
||||
Web::WebSockets::WebSocketClientManager::initialize(TRY(WebView::WebSocketClientManagerAdapter::try_create(move(web_socket_client))));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue