diff --git a/native/cpp/CommonCpp/grpc/GRPCStreamHostObject.cpp b/native/cpp/CommonCpp/grpc/GRPCStreamHostObject.cpp index 8b561e598..32a549c58 100644 --- a/native/cpp/CommonCpp/grpc/GRPCStreamHostObject.cpp +++ b/native/cpp/CommonCpp/grpc/GRPCStreamHostObject.cpp @@ -1,190 +1,187 @@ #include "GRPCStreamHostObject.h" #include "../NativeModules/InternalModules/GlobalNetworkSingleton.h" #include "../NativeModules/InternalModules/SocketStatus.h" using namespace facebook; GRPCStreamHostObject::GRPCStreamHostObject( jsi::Runtime &rt, std::shared_ptr jsInvoker) : readyState{0}, onopen{}, onmessage{}, onclose{}, send{jsi::Function::createFromHostFunction( rt, jsi::PropNameID::forUtf8(rt, "send"), 1, [](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { auto payload{args->asString(rt).utf8(rt)}; comm::GlobalNetworkSingleton::instance.scheduleOrRun( [=](comm::NetworkModule &networkModule) { std::vector blobHashes{}; networkModule.send( "sessionID-placeholder", "toDeviceID-placeholder", payload, blobHashes); }); return jsi::Value::undefined(); })}, close{jsi::Function::createFromHostFunction( rt, jsi::PropNameID::forUtf8(rt, "close"), 0, [](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) { comm::GlobalNetworkSingleton::instance.scheduleOrRun( [=](comm::NetworkModule &networkModule) { networkModule.closeGetStream(); }); return jsi::Value::undefined(); })}, jsInvoker{jsInvoker} { auto onReadDoneCallback = [this, &rt](std::string data) { this->jsInvoker->invokeAsync([this, &rt, data]() { if (this->onmessage.isNull()) { return; } auto msgObject = jsi::Object(rt); msgObject.setProperty(rt, "data", jsi::String::createFromUtf8(rt, data)); this->onmessage.asObject(rt).asFunction(rt).call(rt, msgObject, 1); }); }; auto onOpenCallback = [this, &rt]() { this->jsInvoker->invokeAsync([this, &rt]() { if (this->onopen.isNull()) { return; } this->onopen.asObject(rt).asFunction(rt).call( rt, jsi::Value::undefined(), 0); }); }; auto onCloseCallback = [this, &rt]() { this->jsInvoker->invokeAsync([this, &rt]() { if (this->onclose.isNull()) { return; } this->onclose.asObject(rt).asFunction(rt).call( rt, jsi::Value::undefined(), 0); }); }; // We pass the following lambda to the `NetworkModule` on the "network" // thread with a reference to `this` bound in. This allows us to directly // modify the value of `readyState` in a synchronous manner. auto setReadyStateCallback = [this](SocketStatus newSocketStatus) { if (!this) { // This handles the case where `GRPCStreamHostObj` may have been freed // by the JS garbage collector and `this` is no longer a valid reference. return; } this->readyState = newSocketStatus; }; // The reason we're queueing up the `.get()` call on the JS event loop is // to handle the case of an `.onopen` callback being set right after a // call to `openSocket(...)`. // // This isn't an issue with the existing `WebSocket` approach because the // socket will not actually open until the block of JS--which includes the // setting of the `.onopen` callback--finishes executing. // See the following for background: https://stackoverflow.com/a/49211579. // // Without wrapping the `scheduleOrRun(...)` in an `invokeAsync(...)`, // it is possible for the gRPC `Get()` stream to open before the `.onopen` // callback has been properly set. We queue the `get()` call on the JS // event loop to guarantee that the `.onopen` callback is set before the // socket can possibly open. This mimics the existing `WebSocket` behavior. this->jsInvoker->invokeAsync([=]() { comm::GlobalNetworkSingleton::instance.scheduleOrRun( [=](comm::NetworkModule &networkModule) { // The callbacks are set after the call to `.get()` because they // need to be passed to the `ClientGetReadReactor` object, which is // only constructed after a call to `.get()`. networkModule.initializeNetworkModule( "userId-placeholder", "deviceToken-placeholder", "localhost"); networkModule.get("sessionID-placeholder"); networkModule.setOnReadDoneCallback(onReadDoneCallback); networkModule.setOnOpenCallback(onOpenCallback); networkModule.setOnCloseCallback(onCloseCallback); networkModule.assignSetReadyStateCallback(setReadyStateCallback); }); }); } std::vector GRPCStreamHostObject::getPropertyNames(jsi::Runtime &rt) { std::vector names; names.reserve(6); names.push_back(jsi::PropNameID::forUtf8(rt, std::string{"readyState"})); names.push_back(jsi::PropNameID::forUtf8(rt, std::string{"onopen"})); names.push_back(jsi::PropNameID::forUtf8(rt, std::string{"onmessage"})); names.push_back(jsi::PropNameID::forUtf8(rt, std::string{"onclose"})); names.push_back(jsi::PropNameID::forUtf8(rt, std::string{"close"})); names.push_back(jsi::PropNameID::forUtf8(rt, std::string{"send"})); return names; } jsi::Value GRPCStreamHostObject::get(jsi::Runtime &runtime, const jsi::PropNameID &name) { auto propName = name.utf8(runtime); if (propName == "readyState") { return jsi::Value(this->readyState); } if (propName == "send") { return this->send.asObject(runtime).asFunction(runtime); } if (propName == "close") { return this->close.asObject(runtime).asFunction(runtime); } if (propName == "onopen") { return this->onopen.isNull() ? jsi::Value::null() : this->onopen.asObject(runtime).asFunction(runtime); } if (propName == "onmessage") { return this->onmessage.isNull() ? jsi::Value::null() : this->onmessage.asObject(runtime).asFunction(runtime); } if (propName == "onclose") { return this->onclose.isNull() ? jsi::Value::null() : this->onclose.asObject(runtime).asFunction(runtime); } return jsi::Value::undefined(); } void GRPCStreamHostObject::set( jsi::Runtime &runtime, const jsi::PropNameID &name, const jsi::Value &value) { auto propName = name.utf8(runtime); - if (propName == "readyState" && value.isNumber()) { - this->readyState = static_cast(value.asNumber()); - } else if ( - propName == "onopen" && value.isObject() && + if (propName == "onopen" && value.isObject() && value.asObject(runtime).isFunction(runtime)) { this->onopen = value.asObject(runtime).asFunction(runtime); } else if ( propName == "onmessage" && value.isObject() && value.asObject(runtime).isFunction(runtime)) { this->onmessage = value.asObject(runtime).asFunction(runtime); } else if ( propName == "onclose" && value.isObject() && value.asObject(runtime).isFunction(runtime)) { this->onclose = value.asObject(runtime).asFunction(runtime); } }