Static Webview - RTC - UDP - TCP - WSS
This commit is contained in:
@ -101,11 +101,19 @@ namespace WebSocketWrapper {
|
||||
|
||||
void processEvents() {
|
||||
try {
|
||||
std::vector<int> closed_ids;
|
||||
for (auto& [id, client] : active_websockets) {
|
||||
if (client) {
|
||||
client->processEvents();
|
||||
if (client->getReadyState() == CLOSED
|
||||
&& !client->hasPendingEvents()) {
|
||||
closed_ids.push_back(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int id : closed_ids) {
|
||||
active_websockets.erase(id);
|
||||
}
|
||||
} catch (...) {
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: CRASH in global processEvents");
|
||||
@ -120,137 +128,9 @@ namespace WebSocketWrapper {
|
||||
return id;
|
||||
}
|
||||
|
||||
class SimpleWebSocketClient {
|
||||
private:
|
||||
SOCKET socket_;
|
||||
std::thread* thread_;
|
||||
std::atomic<bool> connected_{false};
|
||||
std::atomic<bool> should_stop_{false};
|
||||
std::string host_;
|
||||
int port_;
|
||||
std::string path_;
|
||||
|
||||
public:
|
||||
SimpleWebSocketClient(const std::string& url) : socket_(INVALID_SOCKET), thread_(nullptr) {
|
||||
parseUrl(url);
|
||||
}
|
||||
|
||||
~SimpleWebSocketClient() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
bool connect() {
|
||||
socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (socket_ == INVALID_SOCKET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SocketOptimization::optimizeWebSocket(static_cast<int>(socket_));
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(port_);
|
||||
inet_pton(AF_INET, host_.c_str(), &addr.sin_addr);
|
||||
|
||||
if (::connect(socket_, (sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(socket_);
|
||||
socket_ = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string handshake =
|
||||
"GET " + path_ + " HTTP/1.1\r\n"
|
||||
"Host: " + host_ + ":" + std::to_string(port_) + "\r\n"
|
||||
"Upgrade: websocket\r\n"
|
||||
"Connection: Upgrade\r\n"
|
||||
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
|
||||
"Sec-WebSocket-Version: 13\r\n\r\n";
|
||||
|
||||
if (send(socket_, handshake.c_str(), static_cast<int>(handshake.length()), 0) == SOCKET_ERROR) {
|
||||
closesocket(socket_);
|
||||
socket_ = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
char buffer[SocketOptimization::SMALL_BUFFER_SIZE];
|
||||
int bytes = recv(socket_, buffer, sizeof(buffer) - 1, 0);
|
||||
if (bytes > 0) {
|
||||
buffer[bytes] = '\0';
|
||||
if (strstr(buffer, "101 Switching Protocols")) {
|
||||
connected_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
closesocket(socket_);
|
||||
socket_ = INVALID_SOCKET;
|
||||
return false;
|
||||
}
|
||||
|
||||
void disconnect() {
|
||||
should_stop_ = true;
|
||||
if (socket_ != INVALID_SOCKET) {
|
||||
closesocket(socket_);
|
||||
socket_ = INVALID_SOCKET;
|
||||
}
|
||||
if (thread_ && thread_->joinable()) {
|
||||
thread_->join();
|
||||
delete thread_;
|
||||
thread_ = nullptr;
|
||||
}
|
||||
connected_ = false;
|
||||
}
|
||||
|
||||
bool isConnected() const {
|
||||
return connected_;
|
||||
}
|
||||
|
||||
void sendText(const std::string& data) {
|
||||
if (!connected_ || socket_ == INVALID_SOCKET) return;
|
||||
|
||||
// text frame format FIN + text frame
|
||||
std::vector<uint8_t> frame;
|
||||
frame.push_back(0x81);
|
||||
|
||||
if (data.length() < 126) {
|
||||
frame.push_back(0x80 | static_cast<uint8_t>(data.length())); // MASK + length
|
||||
} else {
|
||||
// TODO: support longer messages
|
||||
return;
|
||||
}
|
||||
|
||||
// adds simplified masking key
|
||||
uint8_t mask[4] = {0x12, 0x34, 0x56, 0x78};
|
||||
frame.insert(frame.end(), mask, mask + 4);
|
||||
|
||||
// adds the masked payload
|
||||
for (size_t i = 0; i < data.length(); i++) {
|
||||
frame.push_back(data[i] ^ mask[i % 4]);
|
||||
}
|
||||
|
||||
send(socket_, (char*)frame.data(), static_cast<int>(frame.size()), 0);
|
||||
}
|
||||
|
||||
private:
|
||||
void parseUrl(const std::string& url) {
|
||||
std::regex url_regex(R"(^wss?://([^:/]+)(?::(\d+))?(/.*)?$)");
|
||||
std::smatch matches;
|
||||
|
||||
if (std::regex_match(url, matches, url_regex)) {
|
||||
host_ = matches[1].str();
|
||||
port_ = matches[2].matched ? std::stoi(matches[2].str()) : 80;
|
||||
path_ = matches[3].matched ? matches[3].str() : "/";
|
||||
} else {
|
||||
host_ = "localhost";
|
||||
port_ = 80;
|
||||
path_ = "/";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebSocketClient::WebSocketClient(Isolate* isolate, Global<Context>* global_context, const std::string& url)
|
||||
: isolate_(isolate), global_context_(global_context), url_(url), ready_state_(CONNECTING),
|
||||
is_ssl_(false), buffered_amount_(0), ws_(nullptr), ssl_ws_(nullptr)
|
||||
is_ssl_(false), buffered_amount_(0), ws_(-1)
|
||||
#ifdef WITH_SSL
|
||||
#ifdef _WIN32
|
||||
, ssl_cred_handle_(nullptr), ssl_context_handle_(nullptr), ssl_context_initialized_(false), ssl_socket_(-1)
|
||||
@ -278,21 +158,35 @@ namespace WebSocketWrapper {
|
||||
ready_state_ = CONNECTING;
|
||||
|
||||
// start connection using thread pool
|
||||
GlobalThreadPool::getInstance().enqueue([this, host, port, path]() {
|
||||
connect_future_ = GlobalThreadPool::getInstance().enqueue([this, host, port, path]() {
|
||||
try {
|
||||
if (ready_state_ == CLOSED) {
|
||||
return;
|
||||
}
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket connecting to %s:%d%s (SSL: %s)",
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket connecting to %s:%d%s (SSL: %s)",
|
||||
host.c_str(), port, path.c_str(), is_ssl_ ? "yes" : "no");
|
||||
#endif
|
||||
|
||||
|
||||
if (connectToServer(host, port, path)) {
|
||||
ready_state_ = OPEN;
|
||||
handleOpen();
|
||||
|
||||
messageLoop();
|
||||
if (ready_state_ == OPEN) {
|
||||
messageLoop();
|
||||
} else {
|
||||
if (is_ssl_) {
|
||||
cleanupWebSocketSSL();
|
||||
}
|
||||
if (ws_ >= 0) {
|
||||
#ifdef _WIN32
|
||||
closesocket((SOCKET)ws_);
|
||||
#else
|
||||
::close(ws_);
|
||||
#endif
|
||||
ws_ = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ready_state_ = CLOSED;
|
||||
handleError("Connection failed");
|
||||
ready_state_ = CLOSED;
|
||||
}
|
||||
} catch (...) {
|
||||
ready_state_ = CLOSED;
|
||||
@ -304,9 +198,39 @@ namespace WebSocketWrapper {
|
||||
if (ready_state_ == OPEN) {
|
||||
close();
|
||||
}
|
||||
ready_state_ = CLOSED;
|
||||
if (ws_ >= 0) {
|
||||
#ifdef _WIN32
|
||||
::shutdown((SOCKET)ws_, SD_BOTH);
|
||||
#else
|
||||
::shutdown(ws_, SHUT_RDWR);
|
||||
#endif
|
||||
}
|
||||
if (connect_future_.valid()) {
|
||||
connect_future_.wait();
|
||||
}
|
||||
if (ws_ >= 0) {
|
||||
#ifdef _WIN32
|
||||
closesocket((SOCKET)ws_);
|
||||
#else
|
||||
::close(ws_);
|
||||
#endif
|
||||
ws_ = -1;
|
||||
}
|
||||
#ifdef WITH_SSL
|
||||
cleanupWSL();
|
||||
#endif
|
||||
if (isolate_ != nullptr) {
|
||||
v8::Locker locker{isolate_};
|
||||
v8::Isolate::Scope isolate_scope(isolate_);
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
on_open_.Reset();
|
||||
on_message_.Reset();
|
||||
on_error_.Reset();
|
||||
on_close_.Reset();
|
||||
}
|
||||
WebSocketEvent ev;
|
||||
while (event_queue_.try_pop(ev)) {}
|
||||
}
|
||||
|
||||
bool WebSocketClient::parseUrl(const std::string& url, std::string& host, int& port, std::string& path) {
|
||||
@ -338,7 +262,7 @@ namespace WebSocketWrapper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws_ == nullptr) {
|
||||
if (ws_ < 0) {
|
||||
handleError("WebSocket: No socket available for sending");
|
||||
return;
|
||||
}
|
||||
@ -353,7 +277,7 @@ namespace WebSocketWrapper {
|
||||
// create and send WebSocket frame
|
||||
std::vector<uint8_t> frame = createWebSocketFrame(data);
|
||||
|
||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
||||
int sock = ws_;
|
||||
int sendResult;
|
||||
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
@ -388,7 +312,7 @@ namespace WebSocketWrapper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ws_ == nullptr) {
|
||||
if (ws_ < 0) {
|
||||
handleError("WebSocket: No socket available for sending");
|
||||
return;
|
||||
}
|
||||
@ -403,7 +327,7 @@ namespace WebSocketWrapper {
|
||||
// binary frame with opcode 0x02
|
||||
std::vector<uint8_t> frame = createWebSocketBinaryFrame(data);
|
||||
|
||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
||||
int sock = ws_;
|
||||
int sendResult;
|
||||
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
@ -434,8 +358,17 @@ namespace WebSocketWrapper {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ready_state_ == OPEN && ws_ >= 0) {
|
||||
std::vector<uint8_t> frame = createCloseFrame(code, reason);
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
webSocketSSLSend(frame.data(), static_cast<int>(frame.size()));
|
||||
} else {
|
||||
int sock = ws_;
|
||||
::send(sock, reinterpret_cast<const char*>(frame.data()), static_cast<int>(frame.size()), 0);
|
||||
}
|
||||
}
|
||||
|
||||
ready_state_ = CLOSING;
|
||||
ready_state_ = CLOSED;
|
||||
handleClose(code, reason);
|
||||
}
|
||||
|
||||
@ -457,16 +390,6 @@ namespace WebSocketWrapper {
|
||||
on_close_.Reset(isolate_, callback);
|
||||
}
|
||||
|
||||
// event handlers called from worker thread that queue events safely
|
||||
void WebSocketClient::handleOpen() {
|
||||
event_queue_.push(WebSocketEvent(EVENT_OPEN));
|
||||
}
|
||||
|
||||
void WebSocketClient::handleMessage(const std::string& message) {
|
||||
// push directly to lock free queue
|
||||
event_queue_.push(WebSocketEvent(EVENT_MESSAGE, message));
|
||||
}
|
||||
|
||||
void WebSocketClient::handleError(const std::string& error) {
|
||||
try {
|
||||
#ifdef DEBUG_NETWORK
|
||||
@ -479,8 +402,8 @@ namespace WebSocketWrapper {
|
||||
}
|
||||
|
||||
void WebSocketClient::handleClose(int code, const std::string& reason) {
|
||||
ready_state_ = CLOSED;
|
||||
event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason));
|
||||
ready_state_ = CLOSED;
|
||||
}
|
||||
|
||||
// process events on main thread with lock free queue and batching
|
||||
@ -928,6 +851,8 @@ namespace WebSocketWrapper {
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
ws_ = sock;
|
||||
|
||||
sockaddr_in addr{};
|
||||
addr.sin_family = AF_INET;
|
||||
@ -945,6 +870,7 @@ namespace WebSocketWrapper {
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
ws_ = -1;
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to resolve host %s", host.c_str());
|
||||
#endif
|
||||
@ -961,6 +887,7 @@ namespace WebSocketWrapper {
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
ws_ = -1;
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to connect to %s:%d", host.c_str(), port);
|
||||
#endif
|
||||
@ -971,7 +898,7 @@ namespace WebSocketWrapper {
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: TCP connected to %s:%d", host.c_str(), port);
|
||||
#endif
|
||||
|
||||
ws_ = reinterpret_cast<void*>(static_cast<intptr_t>(sock));
|
||||
ws_ = sock;
|
||||
|
||||
if (is_ssl_ && port == 443) {
|
||||
if (!initializeWebSocketSSL(sock, host)) {
|
||||
@ -980,7 +907,7 @@ namespace WebSocketWrapper {
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
ws_ = nullptr;
|
||||
ws_ = -1;
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_NETWORK
|
||||
@ -995,7 +922,7 @@ namespace WebSocketWrapper {
|
||||
#else
|
||||
close(sock);
|
||||
#endif
|
||||
ws_ = nullptr;
|
||||
ws_ = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1003,9 +930,11 @@ namespace WebSocketWrapper {
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Handshake completed successfully");
|
||||
#endif
|
||||
|
||||
ready_state_ = OPEN;
|
||||
fireOpenEvent();
|
||||
|
||||
if (ready_state_ == CONNECTING) {
|
||||
ready_state_ = OPEN;
|
||||
fireOpenEvent();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
@ -1060,23 +989,35 @@ namespace WebSocketWrapper {
|
||||
}
|
||||
|
||||
char buffer[SocketOptimization::SMALL_BUFFER_SIZE];
|
||||
int bytesReceived;
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1);
|
||||
} else {
|
||||
bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
|
||||
std::string response;
|
||||
for (int attempt = 0; attempt < 64
|
||||
&& response.find("\r\n\r\n") == std::string::npos; ++attempt) {
|
||||
int bytesReceived;
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
bytesReceived = webSocketSSLReceive(buffer, sizeof(buffer) - 1);
|
||||
if (bytesReceived == 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
bytesReceived = recv(sock, buffer, sizeof(buffer) - 1, 0);
|
||||
}
|
||||
|
||||
if (bytesReceived <= 0) {
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive handshake response");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
response.append(buffer, bytesReceived);
|
||||
}
|
||||
|
||||
if (bytesReceived <= 0) {
|
||||
|
||||
if (response.find("\r\n\r\n") == std::string::npos) {
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Failed to receive handshake response");
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: Handshake response never completed");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
buffer[bytesReceived] = '\0';
|
||||
std::string response(buffer);
|
||||
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Received handshake response");
|
||||
#endif
|
||||
@ -1155,8 +1096,8 @@ namespace WebSocketWrapper {
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed: %d - %s", code, reason.c_str());
|
||||
#endif
|
||||
ready_state_ = CLOSED;
|
||||
event_queue_.push(WebSocketEvent(EVENT_CLOSE, code, reason));
|
||||
ready_state_ = CLOSED;
|
||||
}
|
||||
|
||||
// complete frame helper for ring buffer messageLoop
|
||||
@ -1186,14 +1127,23 @@ namespace WebSocketWrapper {
|
||||
close_reason = std::string(payload.begin() + 2, payload.end());
|
||||
}
|
||||
}
|
||||
fireCloseEvent(close_code, close_reason);
|
||||
if (ready_state_ == OPEN) {
|
||||
std::vector<uint8_t> echoFrame = createCloseFrame(close_code, "");
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
webSocketSSLSend(echoFrame.data(), static_cast<int>(echoFrame.size()));
|
||||
} else {
|
||||
int sock = ws_;
|
||||
::send(sock, reinterpret_cast<const char*>(echoFrame.data()), static_cast<int>(echoFrame.size()), 0);
|
||||
}
|
||||
fireCloseEvent(close_code, close_reason);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x9: // ping frame
|
||||
{
|
||||
std::vector<uint8_t> pongFrame = createPongFrame(payload);
|
||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
||||
int sock = ws_;
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
webSocketSSLSend(pongFrame.data(), static_cast<int>(pongFrame.size()));
|
||||
} else {
|
||||
@ -1213,14 +1163,14 @@ namespace WebSocketWrapper {
|
||||
|
||||
// following uWebSockets
|
||||
void WebSocketClient::messageLoop() {
|
||||
if (ws_ == nullptr) {
|
||||
if (ws_ < 0) {
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "WebSocket: No socket available for message loop");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
||||
int sock = ws_;
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Starting message loop");
|
||||
#endif
|
||||
@ -1271,8 +1221,11 @@ namespace WebSocketWrapper {
|
||||
} else {
|
||||
bytesReceived = recv(sock, reinterpret_cast<char*>(writePtr), static_cast<int>(contiguousSpace), 0);
|
||||
}
|
||||
|
||||
|
||||
if (bytesReceived <= 0) {
|
||||
if (bytesReceived == 0 && is_ssl_ && isSSLReady()) {
|
||||
continue;
|
||||
}
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Connection closed by server");
|
||||
#endif
|
||||
@ -1357,6 +1310,10 @@ namespace WebSocketWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
if (ready_state_ == OPEN) {
|
||||
fireCloseEvent(1006, "");
|
||||
}
|
||||
|
||||
#ifdef DEBUG_NETWORK
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "WebSocket: Message loop completed");
|
||||
#endif
|
||||
@ -1464,7 +1421,7 @@ namespace WebSocketWrapper {
|
||||
case 0x9:
|
||||
{
|
||||
std::vector<uint8_t> pongFrame = createPongFrame(payload);
|
||||
int sock = static_cast<int>(reinterpret_cast<intptr_t>(ws_));
|
||||
int sock = ws_;
|
||||
|
||||
int sendResult;
|
||||
if (is_ssl_ && isSSLReady()) {
|
||||
@ -1964,7 +1921,7 @@ namespace WebSocketWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
if (status == SEC_I_CONTINUE_NEEDED && outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer != nullptr) {
|
||||
if ((status == SEC_I_CONTINUE_NEEDED || status == SEC_E_OK) && outbuffers[0].cbBuffer > 0 && outbuffers[0].pvBuffer != nullptr) {
|
||||
// validate buffer before sending
|
||||
if (outbuffers[0].cbBuffer < 0 || outbuffers[0].cbBuffer > 65536) {
|
||||
kinc_log(KINC_LOG_LEVEL_ERROR, "Invalid WebSocket SSL handshake buffer size: %d bytes (corrupted data)", outbuffers[0].cbBuffer);
|
||||
@ -2024,6 +1981,10 @@ namespace WebSocketWrapper {
|
||||
}
|
||||
|
||||
if (status == SEC_E_OK) {
|
||||
if (!handshake_buffer.empty()) {
|
||||
ssl_buffer_.insert(ssl_buffer_.end(), handshake_buffer.begin(),
|
||||
handshake_buffer.end());
|
||||
}
|
||||
ssl_context_initialized_ = true;
|
||||
kinc_log(KINC_LOG_LEVEL_INFO, "SChannel WebSocket SSL handshake completed successfully");
|
||||
return true;
|
||||
@ -2649,6 +2610,160 @@ void runt_websocket_set_onclose(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
// Native WebSocket class implementation for V8
|
||||
static void dispatchWebSocketEvent(const FunctionCallbackInfo<Value>& args, const char* type, const char* handler) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
HandleScope scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Local<Object> self = args.Data().As<Object>();
|
||||
|
||||
Local<Object> event;
|
||||
if (args.Length() > 0 && args[0]->IsObject()) {
|
||||
event = args[0].As<Object>();
|
||||
} else {
|
||||
event = Object::New(isolate);
|
||||
}
|
||||
event->Set(context, String::NewFromUtf8(isolate, "type").ToLocalChecked(), String::NewFromUtf8(isolate, type).ToLocalChecked());
|
||||
event->Set(context, String::NewFromUtf8(isolate, "target").ToLocalChecked(), self);
|
||||
event->Set(context, String::NewFromUtf8(isolate, "currentTarget").ToLocalChecked(), self);
|
||||
|
||||
Local<Value> argv[1] = { event };
|
||||
Local<Value> fn = self->Get(context, String::NewFromUtf8(isolate, handler).ToLocalChecked()).ToLocalChecked();
|
||||
if (fn->IsFunction()) {
|
||||
TryCatch try_catch(isolate);
|
||||
fn.As<Function>()->Call(context, self, 1, argv);
|
||||
if (try_catch.HasCaught()) {
|
||||
String::Utf8Value err(isolate, try_catch.Exception());
|
||||
kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket %s handler threw: %s", type,
|
||||
*err != nullptr ? *err : "unknown");
|
||||
}
|
||||
}
|
||||
|
||||
Local<Value> listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked();
|
||||
if (listenersVal->IsObject()) {
|
||||
Local<Value> arr = listenersVal.As<Object>()->Get(context, String::NewFromUtf8(isolate, type).ToLocalChecked()).ToLocalChecked();
|
||||
if (arr->IsArray()) {
|
||||
Local<Array> listeners = arr.As<Array>();
|
||||
uint32_t length = listeners->Length();
|
||||
std::vector<Local<Function>> snapshot;
|
||||
snapshot.reserve(length);
|
||||
for (uint32_t i = 0; i < length; ++i) {
|
||||
Local<Value> listener = listeners->Get(context, i).ToLocalChecked();
|
||||
if (listener->IsFunction()) {
|
||||
snapshot.push_back(listener.As<Function>());
|
||||
}
|
||||
}
|
||||
for (Local<Function>& listener : snapshot) {
|
||||
TryCatch try_catch(isolate);
|
||||
listener->Call(context, self, 1, argv);
|
||||
if (try_catch.HasCaught()) {
|
||||
String::Utf8Value err(isolate, try_catch.Exception());
|
||||
kinc_log(KINC_LOG_LEVEL_WARNING, "WebSocket %s listener threw: %s", type,
|
||||
*err != nullptr ? *err : "unknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WebSocketDispatchOpen(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
HandleScope scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Local<Object> self = args.Data().As<Object>();
|
||||
|
||||
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
|
||||
if (idVal->IsNumber()) {
|
||||
int wsId = idVal->Int32Value(context).FromJust();
|
||||
auto it = WebSocketWrapper::active_websockets.find(wsId);
|
||||
if (it != WebSocketWrapper::active_websockets.end()) {
|
||||
self->Set(context, String::NewFromUtf8(isolate, "protocol").ToLocalChecked(),
|
||||
String::NewFromUtf8(isolate, it->second->getProtocol().c_str()).ToLocalChecked());
|
||||
}
|
||||
}
|
||||
dispatchWebSocketEvent(args, "open", "onopen");
|
||||
}
|
||||
|
||||
static void WebSocketDispatchMessage(const FunctionCallbackInfo<Value>& args) {
|
||||
dispatchWebSocketEvent(args, "message", "onmessage");
|
||||
}
|
||||
|
||||
static void WebSocketDispatchError(const FunctionCallbackInfo<Value>& args) {
|
||||
dispatchWebSocketEvent(args, "error", "onerror");
|
||||
}
|
||||
|
||||
static void WebSocketDispatchClose(const FunctionCallbackInfo<Value>& args) {
|
||||
dispatchWebSocketEvent(args, "close", "onclose");
|
||||
}
|
||||
|
||||
static void WebSocketReadyStateGetter(Local<Name> property, const PropertyCallbackInfo<Value>& info) {
|
||||
Isolate* isolate = info.GetIsolate();
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
int state = WebSocketWrapper::CLOSED;
|
||||
Local<Value> idVal = info.Holder()->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
|
||||
if (idVal->IsNumber()) {
|
||||
auto it = WebSocketWrapper::active_websockets.find(idVal->Int32Value(context).FromJust());
|
||||
if (it != WebSocketWrapper::active_websockets.end()) {
|
||||
state = it->second->getReadyState();
|
||||
}
|
||||
}
|
||||
info.GetReturnValue().Set(Integer::New(isolate, state));
|
||||
}
|
||||
|
||||
static void WebSocketReadyStateSetter(Local<Name> property, Local<Value> value, const PropertyCallbackInfo<void>& info) {}
|
||||
|
||||
static void WebSocketAddEventListener(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
HandleScope scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Local<Object> self = args.Holder();
|
||||
|
||||
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) return;
|
||||
|
||||
Local<Value> listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked();
|
||||
if (!listenersVal->IsObject()) return;
|
||||
Local<Object> listeners = listenersVal.As<Object>();
|
||||
|
||||
Local<Value> arr = listeners->Get(context, args[0]).ToLocalChecked();
|
||||
Local<Array> listenerList;
|
||||
if (arr->IsArray()) {
|
||||
listenerList = arr.As<Array>();
|
||||
for (uint32_t i = 0; i < listenerList->Length(); ++i) {
|
||||
Local<Value> existing = listenerList->Get(context, i).ToLocalChecked();
|
||||
if (existing->StrictEquals(args[1])) return;
|
||||
}
|
||||
} else {
|
||||
listenerList = Array::New(isolate);
|
||||
listeners->Set(context, args[0], listenerList);
|
||||
}
|
||||
listenerList->Set(context, listenerList->Length(), args[1]);
|
||||
}
|
||||
|
||||
static void WebSocketRemoveEventListener(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
HandleScope scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Local<Object> self = args.Holder();
|
||||
|
||||
if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsFunction()) return;
|
||||
|
||||
Local<Value> listenersVal = self->Get(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked()).ToLocalChecked();
|
||||
if (!listenersVal->IsObject()) return;
|
||||
|
||||
Local<Value> arr = listenersVal.As<Object>()->Get(context, args[0]).ToLocalChecked();
|
||||
if (!arr->IsArray()) return;
|
||||
Local<Array> listenerList = arr.As<Array>();
|
||||
|
||||
for (uint32_t i = 0; i < listenerList->Length(); ++i) {
|
||||
Local<Value> listener = listenerList->Get(context, i).ToLocalChecked();
|
||||
if (listener->StrictEquals(args[1])) {
|
||||
Local<Value> splice = listenerList->Get(context, String::NewFromUtf8(isolate, "splice").ToLocalChecked()).ToLocalChecked();
|
||||
Local<Value> spliceArgs[2] = { Integer::New(isolate, i), Integer::New(isolate, 1) };
|
||||
splice.As<Function>()->Call(context, listenerList, 2, spliceArgs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
||||
Isolate* isolate = args.GetIsolate();
|
||||
HandleScope scope(isolate);
|
||||
@ -2672,7 +2787,6 @@ static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
||||
Local<Object> instance = args.This();
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked(), Integer::New(isolate, wsId));
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "url").ToLocalChecked(), args[0]);
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 0));
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "protocol").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "extensions").ToLocalChecked(), String::NewFromUtf8(isolate, "").ToLocalChecked());
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "binaryType").ToLocalChecked(), String::NewFromUtf8(isolate, "blob").ToLocalChecked());
|
||||
@ -2681,6 +2795,15 @@ static void WebSocketConstructor(const FunctionCallbackInfo<Value>& args) {
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "onmessage").ToLocalChecked(), Null(isolate));
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "onerror").ToLocalChecked(), Null(isolate));
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "onclose").ToLocalChecked(), Null(isolate));
|
||||
instance->Set(context, String::NewFromUtf8(isolate, "_listeners").ToLocalChecked(), Object::New(isolate));
|
||||
|
||||
auto it = WebSocketWrapper::active_websockets.find(wsId);
|
||||
if (it != WebSocketWrapper::active_websockets.end()) {
|
||||
it->second->setOnOpen(Function::New(context, WebSocketDispatchOpen, instance).ToLocalChecked());
|
||||
it->second->setOnMessage(Function::New(context, WebSocketDispatchMessage, instance).ToLocalChecked());
|
||||
it->second->setOnError(Function::New(context, WebSocketDispatchError, instance).ToLocalChecked());
|
||||
it->second->setOnClose(Function::New(context, WebSocketDispatchClose, instance).ToLocalChecked());
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(instance);
|
||||
}
|
||||
@ -2745,8 +2868,6 @@ static void WebSocketClose(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
if (readyState == 2 || readyState == 3) return;
|
||||
|
||||
self->Set(context, String::NewFromUtf8(isolate, "readyState").ToLocalChecked(), Integer::New(isolate, 2));
|
||||
|
||||
Local<Value> idVal = self->Get(context, String::NewFromUtf8(isolate, "_id").ToLocalChecked()).ToLocalChecked();
|
||||
int wsId = idVal->Int32Value(context).FromJust();
|
||||
|
||||
@ -2883,10 +3004,15 @@ void bind_websocket_class(Isolate* isolate, const Global<Context>& context) {
|
||||
Local<FunctionTemplate> wsTpl = FunctionTemplate::New(isolate, WebSocketConstructor);
|
||||
wsTpl->SetClassName(String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked());
|
||||
wsTpl->InstanceTemplate()->SetInternalFieldCount(1);
|
||||
wsTpl->InstanceTemplate()->SetNativeDataProperty(
|
||||
String::NewFromUtf8(isolate, "readyState").ToLocalChecked(),
|
||||
WebSocketReadyStateGetter, WebSocketReadyStateSetter);
|
||||
|
||||
Local<ObjectTemplate> proto = wsTpl->PrototypeTemplate();
|
||||
proto->Set(isolate, "send", FunctionTemplate::New(isolate, WebSocketSend));
|
||||
proto->Set(isolate, "close", FunctionTemplate::New(isolate, WebSocketClose));
|
||||
proto->Set(isolate, "addEventListener", FunctionTemplate::New(isolate, WebSocketAddEventListener));
|
||||
proto->Set(isolate, "removeEventListener", FunctionTemplate::New(isolate, WebSocketRemoveEventListener));
|
||||
|
||||
Local<Function> wsFunc = wsTpl->GetFunction(current_context).ToLocalChecked();
|
||||
wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CONNECTING").ToLocalChecked(), Integer::New(isolate, 0));
|
||||
@ -2895,6 +3021,11 @@ void bind_websocket_class(Isolate* isolate, const Global<Context>& context) {
|
||||
wsFunc->Set(current_context, String::NewFromUtf8(isolate, "CLOSED").ToLocalChecked(), Integer::New(isolate, 3));
|
||||
|
||||
global->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc);
|
||||
|
||||
Local<Value> windowVal = global->Get(current_context, String::NewFromUtf8(isolate, "window").ToLocalChecked()).ToLocalChecked();
|
||||
if (windowVal->IsObject()) {
|
||||
windowVal.As<Object>()->Set(current_context, String::NewFromUtf8(isolate, "WebSocket").ToLocalChecked(), wsFunc);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user